summaryrefslogtreecommitdiff
path: root/lib/reline
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reline')
-rw-r--r--lib/reline/ansi.rb58
-rw-r--r--lib/reline/config.rb19
-rw-r--r--lib/reline/general_io.rb8
-rw-r--r--lib/reline/history.rb6
-rw-r--r--lib/reline/line_editor.rb134
-rw-r--r--lib/reline/version.rb2
-rw-r--r--lib/reline/windows.rb45
7 files changed, 233 insertions, 39 deletions
diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
index 8d83da854c..3ef02d6e7a 100644
--- a/lib/reline/ansi.rb
+++ b/lib/reline/ansi.rb
@@ -1,20 +1,49 @@
require 'io/console'
class Reline::ANSI
+ def self.encoding
+ Encoding.default_external
+ end
+
+ def self.win?
+ false
+ end
+
RAW_KEYSTROKE_CONFIG = {
+ # Console (80x25)
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
+ [27, 91, 52, 126] => :ed_move_to_end, # End
+ [27, 91, 51, 126] => :key_delete, # Del
[27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char, # ←
- [27, 91, 51, 126] => :key_delete, # Del
- [27, 91, 49, 126] => :ed_move_to_beg, # Home
- [27, 91, 52, 126] => :ed_move_to_end, # End
+
+ # KDE
[27, 91, 72] => :ed_move_to_beg, # Home
[27, 91, 70] => :ed_move_to_end, # End
+ # Del is 0x08
+ [27, 71, 65] => :ed_prev_history, # ↑
+ [27, 71, 66] => :ed_next_history, # ↓
+ [27, 71, 67] => :ed_next_char, # →
+ [27, 71, 68] => :ed_prev_char, # ←
+
+ # GNOME
+ [27, 79, 72] => :ed_move_to_beg, # Home
+ [27, 79, 70] => :ed_move_to_end, # End
+ # Del is 0x08
+ # Arrow keys are the same of KDE
+
+ # others
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
+
+ [27, 79, 65] => :ed_prev_history, # ↑
+ [27, 79, 66] => :ed_next_history, # ↓
+ [27, 79, 67] => :ed_next_char, # →
+ [27, 79, 68] => :ed_prev_char, # ←
}
@@input = STDIN
@@ -41,16 +70,23 @@ class Reline::ANSI
end
def self.retrieve_keybuffer
+ begin
result = select([@@input], [], [], 0.001)
return if result.nil?
str = @@input.read_nonblock(1024)
str.bytes.each do |c|
@@buf.push(c)
end
+ rescue EOFError
+ end
end
def self.get_screen_size
- @@input.winsize
+ s = @@input.winsize
+ return s if s[0] > 0 && s[1] > 0
+ s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
+ return s if s[0] > 0 && s[1] > 0
+ [24, 80]
rescue Errno::ENOTTY
[24, 80]
end
@@ -88,12 +124,12 @@ class Reline::ANSI
end
def self.move_cursor_column(x)
- print "\e[#{x + 1}G"
+ @@output.write "\e[#{x + 1}G"
end
def self.move_cursor_up(x)
if x > 0
- print "\e[#{x}A" if x > 0
+ @@output.write "\e[#{x}A" if x > 0
elsif x < 0
move_cursor_down(-x)
end
@@ -101,24 +137,24 @@ class Reline::ANSI
def self.move_cursor_down(x)
if x > 0
- print "\e[#{x}B" if x > 0
+ @@output.write "\e[#{x}B" if x > 0
elsif x < 0
move_cursor_up(-x)
end
end
def self.erase_after_cursor
- print "\e[K"
+ @@output.write "\e[K"
end
def self.scroll_down(x)
return if x.zero?
- print "\e[#{x}S"
+ @@output.write "\e[#{x}S"
end
def self.clear_screen
- print "\e[2J"
- print "\e[1;1H"
+ @@output.write "\e[2J"
+ @@output.write "\e[1;1H"
end
@@old_winch_handler = nil
diff --git a/lib/reline/config.rb b/lib/reline/config.rb
index fdc2b39c1b..53b868fd2e 100644
--- a/lib/reline/config.rb
+++ b/lib/reline/config.rb
@@ -83,8 +83,17 @@ class Reline::Config
@key_actors[@keymap_label]
end
+ def inputrc_path
+ case ENV['INPUTRC']
+ when nil, ''
+ DEFAULT_PATH
+ else
+ ENV['INPUTRC']
+ end
+ end
+
def read(file = nil)
- file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH)
+ file ||= File.expand_path(inputrc_path)
begin
if file.respond_to?(:readlines)
lines = file.readlines
@@ -184,9 +193,8 @@ class Reline::Config
def bind_variable(name, value)
case name
- when *VARIABLE_NAMES then
- variable_name = :"@#{name.tr(?-, ?_)}"
- instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
+ when 'history-size'
+ @history_size = value.to_i
when 'bell-style'
@bell_style =
case value
@@ -225,6 +233,9 @@ class Reline::Config
end
when 'keyseq-timeout'
@keyseq_timeout = value.to_i
+ when *VARIABLE_NAMES then
+ variable_name = :"@#{name.tr(?-, ?_)}"
+ instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
end
end
diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb
index 291c14c7b3..85f1f13eed 100644
--- a/lib/reline/general_io.rb
+++ b/lib/reline/general_io.rb
@@ -1,6 +1,14 @@
require 'timeout'
class Reline::GeneralIO
+ def self.encoding
+ RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
+ end
+
+ def self.win?
+ false
+ end
+
RAW_KEYSTROKE_CONFIG = {}
@@buf = []
diff --git a/lib/reline/history.rb b/lib/reline/history.rb
index 238fcf2a76..d95f1cebc3 100644
--- a/lib/reline/history.rb
+++ b/lib/reline/history.rb
@@ -19,7 +19,7 @@ class Reline::History < Array
def []=(index, val)
index = check_index(index)
- super(index, String.new(val, encoding: Encoding::default_external))
+ super(index, String.new(val, encoding: Reline.encoding_system_needs))
end
def concat(*val)
@@ -39,12 +39,12 @@ class Reline::History < Array
val.shift(diff)
end
end
- super(*(val.map{ |v| String.new(v, encoding: Encoding::default_external) }))
+ super(*(val.map{ |v| String.new(v, encoding: Reline.encoding_system_needs) }))
end
def <<(val)
shift if size + 1 > @config.history_size
- super(String.new(val, encoding: Encoding::default_external))
+ super(String.new(val, encoding: Reline.encoding_system_needs))
end
private def check_index(index)
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 75af50a908..095a7b5a09 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -57,10 +57,10 @@ class Reline::LineEditor
NON_PRINTING_END = "\2"
WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
- def initialize(config)
+ def initialize(config, encoding)
@config = config
@completion_append_character = ''
- reset_variables
+ reset_variables(encoding: encoding)
end
private def check_multiline_prompt(buffer, prompt)
@@ -85,10 +85,10 @@ class Reline::LineEditor
end
end
- def reset(prompt = '', encoding = Encoding.default_external)
+ def reset(prompt = '', encoding:)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@screen_size = Reline::IOGate.get_screen_size
- reset_variables(prompt, encoding)
+ reset_variables(prompt, encoding: encoding)
@old_trap = Signal.trap('SIGINT') {
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
raise Interrupt
@@ -139,7 +139,7 @@ class Reline::LineEditor
@eof
end
- def reset_variables(prompt = '', encoding = Encoding.default_external)
+ def reset_variables(prompt = '', encoding:)
@prompt = prompt
@mark_pointer = nil
@encoding = encoding
@@ -317,9 +317,9 @@ class Reline::LineEditor
if @menu_info
scroll_down(@highest_in_all - @first_line_started_from)
@rerender_all = true
- @menu_info.list.each do |item|
+ @menu_info.list.sort!.each do |item|
Reline::IOGate.move_cursor_column(0)
- @output.print item
+ @output.write item
@output.flush
scroll_down(1)
end
@@ -507,12 +507,20 @@ class Reline::LineEditor
Reline::IOGate.move_cursor_column(0)
visual_lines.each_with_index do |line, index|
if line.nil?
- Reline::IOGate.erase_after_cursor
- move_cursor_down(1)
- Reline::IOGate.move_cursor_column(0)
+ if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
+ # A newline is automatically inserted if a character is rendered at eol on command prompt.
+ else
+ Reline::IOGate.erase_after_cursor
+ move_cursor_down(1)
+ Reline::IOGate.move_cursor_column(0)
+ end
next
end
- @output.print line
+ @output.write line
+ if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
+ # A newline is automatically inserted if a character is rendered at eol on command prompt.
+ @rest_height -= 1 if @rest_height > 0
+ end
@output.flush
if @first_prompt
@first_prompt = false
@@ -535,7 +543,7 @@ class Reline::LineEditor
return before if before.nil? || before.empty?
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
- after.lines(chomp: true)
+ after.lines("\n", chomp: true)
else
before
end
@@ -905,7 +913,6 @@ class Reline::LineEditor
quote = nil
i += 1
rest = nil
- break_pointer = nil
elsif quote and slice.start_with?(escaped_quote)
# skip
i += 2
@@ -915,7 +922,7 @@ class Reline::LineEditor
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
escaped_quote = /\\#{Regexp.escape(quote)}/
i += 1
- break_pointer = i
+ break_pointer = i - 1
elsif not quote and slice =~ word_break_regexp
rest = $'
i += 1
@@ -937,6 +944,11 @@ class Reline::LineEditor
end
else
preposing = ''
+ if break_pointer
+ preposing = @line.byteslice(0, break_pointer)
+ else
+ preposing = ''
+ end
target = before
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
@@ -1091,6 +1103,11 @@ class Reline::LineEditor
private def ed_insert(key)
if key.instance_of?(String)
+ begin
+ key.encode(Encoding::UTF_8)
+ rescue Encoding::UndefinedConversionError
+ return
+ end
width = Reline::Unicode.get_mbchar_width(key)
if @cursor == @cursor_max
@line += key
@@ -1101,6 +1118,11 @@ class Reline::LineEditor
@cursor += width
@cursor_max += width
else
+ begin
+ key.chr.encode(Encoding::UTF_8)
+ rescue Encoding::UndefinedConversionError
+ return
+ end
if @cursor == @cursor_max
@line += key.chr
else
@@ -1876,6 +1898,16 @@ class Reline::LineEditor
end
end
+ private def vi_insert_at_bol(key)
+ ed_move_to_beg(key)
+ @config.editing_mode = :vi_insert
+ end
+
+ private def vi_add_at_eol(key)
+ ed_move_to_end(key)
+ @config.editing_mode = :vi_insert
+ end
+
private def ed_delete_prev_char(key, arg: 1)
deleted = ''
arg.times do
@@ -1898,6 +1930,18 @@ class Reline::LineEditor
end
private def vi_change_meta(key)
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
+ if byte_pointer_diff > 0
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
+ elsif byte_pointer_diff < 0
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
+ end
+ copy_for_vi(cut)
+ @cursor += cursor_diff if cursor_diff < 0
+ @cursor_max -= cursor_diff.abs
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
+ @config.editing_mode = :vi_insert
+ }
end
private def vi_delete_meta(key)
@@ -2063,12 +2107,17 @@ class Reline::LineEditor
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
end
- private def search_next_char(key, arg)
+ private def vi_to_next_char(key, arg: 1)
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
+ end
+
+ private def search_next_char(key, arg, need_prev_char = false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
+ prev_total = nil
total = nil
found = false
@line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
@@ -2086,13 +2135,66 @@ class Reline::LineEditor
end
end
width = Reline::Unicode.get_mbchar_width(mbchar)
+ prev_total = total
total = [total.first + mbchar.bytesize, total.last + width]
end
end
- if found and total
+ if not need_prev_char and found and total
byte_size, width = total
@byte_pointer += byte_size
@cursor += width
+ elsif need_prev_char and found and prev_total
+ byte_size, width = prev_total
+ @byte_pointer += byte_size
+ @cursor += width
+ end
+ @waiting_proc = nil
+ end
+
+ private def vi_prev_char(key, arg: 1)
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
+ end
+
+ private def vi_to_prev_char(key, arg: 1)
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
+ end
+
+ private def search_prev_char(key, arg, need_next_char = false)
+ if key.instance_of?(String)
+ inputed_char = key
+ else
+ inputed_char = key.chr
+ end
+ prev_total = nil
+ total = nil
+ found = false
+ @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
+ # total has [byte_size, cursor]
+ unless total
+ # skip cursor point
+ width = Reline::Unicode.get_mbchar_width(mbchar)
+ total = [mbchar.bytesize, width]
+ else
+ if inputed_char == mbchar
+ arg -= 1
+ if arg.zero?
+ found = true
+ break
+ end
+ end
+ width = Reline::Unicode.get_mbchar_width(mbchar)
+ prev_total = total
+ total = [total.first + mbchar.bytesize, total.last + width]
+ end
+ end
+ if not need_next_char and found and total
+ byte_size, width = total
+ @byte_pointer -= byte_size
+ @cursor -= width
+ elsif need_next_char and found and prev_total
+ byte_size, width = prev_total
+ @byte_pointer -= byte_size
+ @cursor -= width
end
@waiting_proc = nil
end
diff --git a/lib/reline/version.rb b/lib/reline/version.rb
index 47b29bd946..1bf544d74b 100644
--- a/lib/reline/version.rb
+++ b/lib/reline/version.rb
@@ -1,3 +1,3 @@
module Reline
- VERSION = '0.1.2'
+ VERSION = '0.1.3'
end
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
index aef3073a7e..c229c8536f 100644
--- a/lib/reline/windows.rb
+++ b/lib/reline/windows.rb
@@ -1,6 +1,14 @@
require 'fiddle/import'
class Reline::Windows
+ def self.encoding
+ Encoding::UTF_8
+ end
+
+ def self.win?
+ true
+ end
+
RAW_KEYSTROKE_CONFIG = {
[224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓
@@ -68,6 +76,8 @@ class Reline::Windows
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
WINDOW_BUFFER_SIZE_EVENT = 0x04
+ FILE_TYPE_PIPE = 0x0003
+ FILE_NAME_INFO = 2
@@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
@@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@@ -80,9 +90,36 @@ class Reline::Windows
@@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
@@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
@@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
+ @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
+ @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
+
@@input_buf = []
@@output_buf = []
+ def self.msys_tty?(io=@@hConsoleInputHandle)
+ # check if fd is a pipe
+ if @@GetFileType.call(io) != FILE_TYPE_PIPE
+ return false
+ end
+
+ bufsize = 1024
+ p_buffer = "\0" * bufsize
+ res = @@GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
+ return false if res == 0
+
+ # get pipe name: p_buffer layout is:
+ # struct _FILE_NAME_INFO {
+ # DWORD FileNameLength;
+ # WCHAR FileName[1];
+ # } FILE_NAME_INFO
+ len = p_buffer[0, 4].unpack("L")[0]
+ name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
+
+ # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
+ # or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX')
+ name =~ /(msys-|cygwin-).*-pty/ ? true : false
+ end
+
def self.getwch
unless @@input_buf.empty?
return @@input_buf.shift
@@ -99,7 +136,7 @@ class Reline::Windows
return @@input_buf.shift
end
begin
- bytes = ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes
+ bytes = ret.chr(Encoding::UTF_8).bytes
@@input_buf.push(*bytes)
rescue Encoding::UndefinedConversionError
@@input_buf << ret
@@ -205,7 +242,7 @@ class Reline::Windows
def self.scroll_down(val)
return if val.zero?
- scroll_rectangle = [0, val, get_screen_size.first, get_screen_size.last].pack('s4')
+ scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
destination_origin = 0 # y * 65536 + x
fill = [' '.ord, 0].pack('SS')
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
@@ -213,8 +250,8 @@ class Reline::Windows
def self.clear_screen
# TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute
- print "\e[2J"
- print "\e[1;1H"
+ write "\e[2J"
+ write "\e[1;1H"
end
def self.set_screen_size(rows, columns)