diff options
Diffstat (limited to 'lib/reline')
-rw-r--r-- | lib/reline/ansi.rb | 58 | ||||
-rw-r--r-- | lib/reline/config.rb | 19 | ||||
-rw-r--r-- | lib/reline/general_io.rb | 8 | ||||
-rw-r--r-- | lib/reline/history.rb | 6 | ||||
-rw-r--r-- | lib/reline/line_editor.rb | 134 | ||||
-rw-r--r-- | lib/reline/version.rb | 2 | ||||
-rw-r--r-- | lib/reline/windows.rb | 45 |
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) |