diff --git a/lib/reline.rb b/lib/reline.rb index fb00b96531..796e637e85 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -7,6 +7,7 @@ require 'reline/line_editor' require 'reline/history' require 'reline/terminfo' +require 'reline/io' require 'reline/face' require 'rbconfig' @@ -336,7 +337,7 @@ def readline(prompt = '', add_hist = false) line_editor.auto_indent_proc = auto_indent_proc line_editor.dig_perfect_match_proc = dig_perfect_match_proc pre_input_hook&.call - unless Reline::IOGate == Reline::GeneralIO + unless Reline::IOGate.dumb? @dialog_proc_list.each_pair do |name_sym, d| line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) end @@ -473,7 +474,7 @@ def ambiguous_width end private def may_req_ambiguous_char_width - @ambiguous_width = 2 if io_gate == Reline::GeneralIO or !STDOUT.tty? + @ambiguous_width = 2 if io_gate.dumb? or !STDOUT.tty? return if defined? @ambiguous_width io_gate.move_cursor_column(0) begin @@ -573,31 +574,19 @@ def self.update_iogate # Need to change IOGate when `$stdout.tty?` change from false to true by `$stdout.reopen` # Example: rails/spring boot the application in non-tty, then run console in tty. - if ENV['TERM'] != 'dumb' && core.io_gate == Reline::GeneralIO && $stdout.tty? - require 'reline/ansi' + if ENV['TERM'] != 'dumb' && core.io_gate.dumb? && $stdout.tty? + require 'reline/io/ansi' remove_const(:IOGate) - const_set(:IOGate, Reline::ANSI) + const_set(:IOGate, Reline::ANSI.new) end end end -require 'reline/general_io' -io = Reline::GeneralIO -unless ENV['TERM'] == 'dumb' - case RbConfig::CONFIG['host_os'] - when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - require 'reline/windows' - tty = (io = Reline::Windows).msys_tty? - else - tty = $stdout.tty? - end -end -Reline::IOGate = if tty - require 'reline/ansi' - Reline::ANSI -else - io -end + +Reline::IOGate = Reline::IO.decide_io_gate + +# Deprecated +Reline::GeneralIO = Reline::Dumb.new Reline::Face.load_initial_configs diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb deleted file mode 100644 index d52151ad3c..0000000000 --- a/lib/reline/general_io.rb +++ /dev/null @@ -1,111 +0,0 @@ -require 'io/wait' - -class Reline::GeneralIO - RESET_COLOR = '' # Do not send color reset sequence - - def self.reset(encoding: nil) - @@pasting = false - if encoding - @@encoding = encoding - elsif defined?(@@encoding) - remove_class_variable(:@@encoding) - end - end - - def self.encoding - if defined?(@@encoding) - @@encoding - elsif RUBY_PLATFORM =~ /mswin|mingw/ - Encoding::UTF_8 - else - Encoding::default_external - end - end - - def self.win? - false - end - - def self.set_default_key_bindings(_) - end - - @@buf = [] - @@input = STDIN - - def self.input=(val) - @@input = val - end - - def self.with_raw_input - yield - end - - def self.getc(_timeout_second) - unless @@buf.empty? - return @@buf.shift - end - c = nil - loop do - Reline.core.line_editor.handle_signal - result = @@input.wait_readable(0.1) - next if result.nil? - c = @@input.read(1) - break - end - c&.ord - end - - def self.ungetc(c) - @@buf.unshift(c) - end - - def self.get_screen_size - [24, 80] - end - - def self.cursor_pos - Reline::CursorPos.new(1, 1) - end - - def self.hide_cursor - end - - def self.show_cursor - end - - def self.move_cursor_column(val) - end - - def self.move_cursor_up(val) - end - - def self.move_cursor_down(val) - end - - def self.erase_after_cursor - end - - def self.scroll_down(val) - end - - def self.clear_screen - end - - def self.set_screen_size(rows, columns) - end - - def self.set_winch_handler(&handler) - end - - @@pasting = false - - def self.in_pasting? - @@pasting - end - - def self.prep - end - - def self.deprep(otio) - end -end diff --git a/lib/reline/io.rb b/lib/reline/io.rb new file mode 100644 index 0000000000..7fca0c338a --- /dev/null +++ b/lib/reline/io.rb @@ -0,0 +1,45 @@ + +module Reline + class IO + RESET_COLOR = "\e[0m" + + def self.decide_io_gate + if ENV['TERM'] == 'dumb' + Reline::Dumb.new + else + require 'reline/io/ansi' + + case RbConfig::CONFIG['host_os'] + when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + require 'reline/io/windows' + io = Reline::Windows.new + if io.msys_tty? + Reline::ANSI.new + else + io + end + else + if $stdout.tty? + Reline::ANSI.new + else + Reline::Dumb.new + end + end + end + end + + def dumb? + false + end + + def win? + false + end + + def reset_color_sequence + self.class::RESET_COLOR + end + end +end + +require 'reline/io/dumb' diff --git a/lib/reline/ansi.rb b/lib/reline/io/ansi.rb similarity index 75% rename from lib/reline/ansi.rb rename to lib/reline/io/ansi.rb index fa9feb2630..cf3c9965dd 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/io/ansi.rb @@ -1,10 +1,7 @@ require 'io/console' require 'io/wait' -require_relative 'terminfo' - -class Reline::ANSI - RESET_COLOR = "\e[0m" +class Reline::ANSI < Reline::IO CAPNAME_KEY_BINDINGS = { 'khome' => :ed_move_to_beg, 'kend' => :ed_move_to_end, @@ -36,15 +33,18 @@ class Reline::ANSI Reline::Terminfo.setupterm(0, 2) end - def self.encoding - Encoding.default_external + def initialize + @input = STDIN + @output = STDOUT + @buf = [] + @old_winch_handler = nil end - def self.win? - false + def encoding + Encoding.default_external end - def self.set_default_key_bindings(config, allow_terminfo: true) + def set_default_key_bindings(config, allow_terminfo: true) set_bracketed_paste_key_bindings(config) set_default_key_bindings_ansi_cursor(config) if allow_terminfo && Reline::Terminfo.enabled? @@ -67,13 +67,13 @@ def self.set_default_key_bindings(config, allow_terminfo: true) end end - def self.set_bracketed_paste_key_bindings(config) + def set_bracketed_paste_key_bindings(config) [:emacs, :vi_insert, :vi_command].each do |keymap| config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start) end end - def self.set_default_key_bindings_ansi_cursor(config) + def set_default_key_bindings_ansi_cursor(config) ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)| bindings = [["\e[#{char}", default_func]] # CSI + char if modifiers[:ctrl] @@ -95,7 +95,7 @@ def self.set_default_key_bindings_ansi_cursor(config) end end - def self.set_default_key_bindings_terminfo(config) + def set_default_key_bindings_terminfo(config) key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding| begin key_code = Reline::Terminfo.tigetstr(capname) @@ -112,7 +112,7 @@ def self.set_default_key_bindings_terminfo(config) end end - def self.set_default_key_bindings_comprehensive_list(config) + def set_default_key_bindings_comprehensive_list(config) { # Console (80x25) [27, 91, 49, 126] => :ed_move_to_beg, # Home @@ -147,37 +147,34 @@ def self.set_default_key_bindings_comprehensive_list(config) end end - @@input = STDIN - def self.input=(val) - @@input = val + def input=(val) + @input = val end - @@output = STDOUT - def self.output=(val) - @@output = val + def output=(val) + @output = val end - def self.with_raw_input - if @@input.tty? - @@input.raw(intr: true) { yield } + def with_raw_input + if @input.tty? + @input.raw(intr: true) { yield } else yield end end - @@buf = [] - def self.inner_getc(timeout_second) - unless @@buf.empty? - return @@buf.shift + def inner_getc(timeout_second) + unless @buf.empty? + return @buf.shift end - until @@input.wait_readable(0.01) + until @input.wait_readable(0.01) timeout_second -= 0.01 return nil if timeout_second <= 0 Reline.core.line_editor.handle_signal end - c = @@input.getbyte - (c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c + c = @input.getbyte + (c == 0x16 && @input.raw(min: 0, time: 0, &:getbyte)) || c rescue Errno::EIO # Maybe the I/O has been closed. nil @@ -187,7 +184,7 @@ def self.inner_getc(timeout_second) START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT) END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT) - def self.read_bracketed_paste + def read_bracketed_paste buffer = String.new(encoding: Encoding::ASCII_8BIT) until buffer.end_with?(END_BRACKETED_PASTE) c = inner_getc(Float::INFINITY) @@ -199,38 +196,38 @@ def self.read_bracketed_paste end # if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second - def self.getc(timeout_second) + def getc(timeout_second) inner_getc(timeout_second) end - def self.in_pasting? + def in_pasting? not empty_buffer? end - def self.empty_buffer? - unless @@buf.empty? + def empty_buffer? + unless @buf.empty? return false end - !@@input.wait_readable(0) + !@input.wait_readable(0) end - def self.ungetc(c) - @@buf.unshift(c) + def ungetc(c) + @buf.unshift(c) end - def self.retrieve_keybuffer + def retrieve_keybuffer begin - return unless @@input.wait_readable(0.001) - str = @@input.read_nonblock(1024) + return unless @input.wait_readable(0.001) + str = @input.read_nonblock(1024) str.bytes.each do |c| - @@buf.push(c) + @buf.push(c) end rescue EOFError end end - def self.get_screen_size - s = @@input.winsize + def get_screen_size + 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 @@ -239,20 +236,20 @@ def self.get_screen_size [24, 80] end - def self.set_screen_size(rows, columns) - @@input.winsize = [rows, columns] + def set_screen_size(rows, columns) + @input.winsize = [rows, columns] self rescue Errno::ENOTTY self end - def self.cursor_pos + def cursor_pos begin res = +'' m = nil - @@input.raw do |stdin| - @@output << "\e[6n" - @@output.flush + @input.raw do |stdin| + @output << "\e[6n" + @output.flush loop do c = stdin.getc next if c.nil? @@ -268,7 +265,7 @@ def self.cursor_pos row = m[:row].to_i - 1 rescue Errno::ENOTTY begin - buf = @@output.pread(@@output.pos, 0) + buf = @output.pread(@output.pos, 0) row = buf.count("\n") column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0 rescue Errno::ESPIPE, IOError @@ -281,30 +278,30 @@ def self.cursor_pos Reline::CursorPos.new(column, row) end - def self.move_cursor_column(x) - @@output.write "\e[#{x + 1}G" + def move_cursor_column(x) + @output.write "\e[#{x + 1}G" end - def self.move_cursor_up(x) + def move_cursor_up(x) if x > 0 - @@output.write "\e[#{x}A" + @output.write "\e[#{x}A" elsif x < 0 move_cursor_down(-x) end end - def self.move_cursor_down(x) + def move_cursor_down(x) if x > 0 - @@output.write "\e[#{x}B" + @output.write "\e[#{x}B" elsif x < 0 move_cursor_up(-x) end end - def self.hide_cursor + def hide_cursor if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin - @@output.write Reline::Terminfo.tigetstr('civis') + @output.write Reline::Terminfo.tigetstr('civis') rescue Reline::Terminfo::TerminfoError # civis is undefined end @@ -313,10 +310,10 @@ def self.hide_cursor end end - def self.show_cursor + def show_cursor if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin - @@output.write Reline::Terminfo.tigetstr('cnorm') + @output.write Reline::Terminfo.tigetstr('cnorm') rescue Reline::Terminfo::TerminfoError # cnorm is undefined end @@ -325,38 +322,37 @@ def self.show_cursor end end - def self.erase_after_cursor - @@output.write "\e[K" + def erase_after_cursor + @output.write "\e[K" end # This only works when the cursor is at the bottom of the scroll range # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 - def self.scroll_down(x) + def scroll_down(x) return if x.zero? # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 - @@output.write "\n" * x + @output.write "\n" * x end - def self.clear_screen - @@output.write "\e[2J" - @@output.write "\e[1;1H" + def clear_screen + @output.write "\e[2J" + @output.write "\e[1;1H" end - @@old_winch_handler = nil - def self.set_winch_handler(&handler) - @@old_winch_handler = Signal.trap('WINCH', &handler) + def set_winch_handler(&handler) + @old_winch_handler = Signal.trap('WINCH', &handler) end - def self.prep + def prep # Enable bracketed paste - @@output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste + @output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste retrieve_keybuffer nil end - def self.deprep(otio) + def deprep(otio) # Disable bracketed paste - @@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste - Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler + @output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste + Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler end end diff --git a/lib/reline/io/dumb.rb b/lib/reline/io/dumb.rb new file mode 100644 index 0000000000..6ed69ffdfa --- /dev/null +++ b/lib/reline/io/dumb.rb @@ -0,0 +1,106 @@ +require 'io/wait' + +class Reline::Dumb < Reline::IO + RESET_COLOR = '' # Do not send color reset sequence + + def initialize(encoding: nil) + @input = STDIN + @buf = [] + @pasting = false + @encoding = encoding + @screen_size = [24, 80] + end + + def dumb? + true + end + + def encoding + if @encoding + @encoding + elsif RUBY_PLATFORM =~ /mswin|mingw/ + Encoding::UTF_8 + else + Encoding::default_external + end + end + + def set_default_key_bindings(_) + end + + def input=(val) + @input = val + end + + def with_raw_input + yield + end + + def getc(_timeout_second) + unless @buf.empty? + return @buf.shift + end + c = nil + loop do + Reline.core.line_editor.handle_signal + result = @input.wait_readable(0.1) + next if result.nil? + c = @input.read(1) + break + end + c&.ord + end + + def ungetc(c) + @buf.unshift(c) + end + + def get_screen_size + @screen_size + end + + def cursor_pos + Reline::CursorPos.new(1, 1) + end + + def hide_cursor + end + + def show_cursor + end + + def move_cursor_column(val) + end + + def move_cursor_up(val) + end + + def move_cursor_down(val) + end + + def erase_after_cursor + end + + def scroll_down(val) + end + + def clear_screen + end + + def set_screen_size(rows, columns) + @screen_size = [rows, columns] + end + + def set_winch_handler(&handler) + end + + def in_pasting? + @pasting + end + + def prep + end + + def deprep(otio) + end +end diff --git a/lib/reline/windows.rb b/lib/reline/io/windows.rb similarity index 68% rename from lib/reline/windows.rb rename to lib/reline/io/windows.rb index ee3f73e383..6ba4b830d6 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/io/windows.rb @@ -1,21 +1,49 @@ require 'fiddle/import' -class Reline::Windows - RESET_COLOR = "\e[0m" +class Reline::Windows < Reline::IO + def initialize + @input_buf = [] + @output_buf = [] + + @output = STDOUT + @hsg = nil + @getwch = Win32API.new('msvcrt', '_getwch', [], 'I') + @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') + @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') + @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') + @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') + @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') + @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') + @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') + @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) + @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE) + @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') + @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') + @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') + @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') + @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') + @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') + + @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') + @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') + @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') + + @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 + end - def self.encoding + def encoding Encoding::UTF_8 end - def self.win? + def win? true end - def self.win_legacy_console? - @@legacy_console + def win_legacy_console? + @legacy_console end - def self.set_default_key_bindings(config) + def set_default_key_bindings(config) { [224, 72] => :ed_prev_history, # ↑ [224, 80] => :ed_next_history, # ↓ @@ -129,58 +157,32 @@ def call(*args) STD_OUTPUT_HANDLE = -11 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') - @@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') - @@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') - @@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') - @@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') - @@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') - @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE) - @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE) - @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') - @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') - @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') - @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') - @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') - @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') - - @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') - @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') - @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 - private_class_method def self.getconsolemode + private def getconsolemode mode = "\000\000\000\000" - @@GetConsoleMode.call(@@hConsoleHandle, mode) + @GetConsoleMode.call(@hConsoleHandle, mode) mode.unpack1('L') end - private_class_method def self.setconsolemode(mode) - @@SetConsoleMode.call(@@hConsoleHandle, mode) + private def setconsolemode(mode) + @SetConsoleMode.call(@hConsoleHandle, mode) end - @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) - #if @@legacy_console + #if @legacy_console # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - # @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) + # @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) #end - @@input_buf = [] - @@output_buf = [] - - @@output = STDOUT - - def self.msys_tty?(io = @@hConsoleInputHandle) + def msys_tty?(io = @hConsoleInputHandle) # check if fd is a pipe - if @@GetFileType.call(io) != FILE_TYPE_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) + res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2) return false if res == 0 # get pipe name: p_buffer layout is: @@ -217,65 +219,63 @@ def self.msys_tty?(io = @@hConsoleInputHandle) [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ], ] - @@hsg = nil - - def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) + def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) # high-surrogate if 0xD800 <= char_code and char_code <= 0xDBFF - @@hsg = char_code + @hsg = char_code return end # low-surrogate if 0xDC00 <= char_code and char_code <= 0xDFFF - if @@hsg - char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00 - @@hsg = nil + if @hsg + char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00 + @hsg = nil else # no high-surrogate. ignored. return end else # ignore high-surrogate without low-surrogate if there - @@hsg = nil + @hsg = nil end key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state) match = KEY_MAP.find { |args,| key.matches?(**args) } unless match.nil? - @@output_buf.concat(match.last) + @output_buf.concat(match.last) return end # no char, only control keys return if key.char_code == 0 and key.control_keys.any? - @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) + @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) - @@output_buf.concat(key.char.bytes) + @output_buf.concat(key.char.bytes) end - def self.check_input_event + def check_input_event num_of_events = 0.chr * 8 - while @@output_buf.empty? + while @output_buf.empty? Reline.core.line_editor.handle_signal - if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec + if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec # prevent for background consolemode change - @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) + @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 next end - next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 + next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 input_records = 0.chr * 20 * 80 read_event = 0.chr * 4 - if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0 + if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0 read_events = read_event.unpack1('L') 0.upto(read_events) do |idx| input_record = input_records[idx * 20, 20] event = input_record[0, 2].unpack1('s*') case event when WINDOW_BUFFER_SIZE_EVENT - @@winch_handler.() + @winch_handler.() when KEY_EVENT key_down = input_record[4, 4].unpack1('l*') repeat_count = input_record[8, 2].unpack1('s*') @@ -293,34 +293,34 @@ def self.check_input_event end end - def self.with_raw_input + def with_raw_input yield end - def self.getc(_timeout_second) + def getc(_timeout_second) check_input_event - @@output_buf.shift + @output_buf.shift end - def self.ungetc(c) - @@output_buf.unshift(c) + def ungetc(c) + @output_buf.unshift(c) end - def self.in_pasting? - not self.empty_buffer? + def in_pasting? + not empty_buffer? end - def self.empty_buffer? - if not @@output_buf.empty? + def empty_buffer? + if not @output_buf.empty? false - elsif @@kbhit.call == 0 + elsif @kbhit.call == 0 true else false end end - def self.get_console_screen_buffer_info + def get_console_screen_buffer_info # CONSOLE_SCREEN_BUFFER_INFO # [ 0,2] dwSize.X # [ 2,2] dwSize.Y @@ -334,18 +334,18 @@ def self.get_console_screen_buffer_info # [18,2] dwMaximumWindowSize.X # [20,2] dwMaximumWindowSize.Y csbi = 0.chr * 22 - return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0 + return if @GetConsoleScreenBufferInfo.call(@hConsoleHandle, csbi) == 0 csbi end - def self.get_screen_size + def get_screen_size unless csbi = get_console_screen_buffer_info return [1, 1] end csbi[0, 4].unpack('SS').reverse end - def self.cursor_pos + def cursor_pos unless csbi = get_console_screen_buffer_info return Reline::CursorPos.new(0, 0) end @@ -354,49 +354,49 @@ def self.cursor_pos Reline::CursorPos.new(x, y) end - def self.move_cursor_column(val) - @@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val) + def move_cursor_column(val) + @SetConsoleCursorPosition.call(@hConsoleHandle, cursor_pos.y * 65536 + val) end - def self.move_cursor_up(val) + def move_cursor_up(val) if val > 0 y = cursor_pos.y - val y = 0 if y < 0 - @@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x) + @SetConsoleCursorPosition.call(@hConsoleHandle, y * 65536 + cursor_pos.x) elsif val < 0 move_cursor_down(-val) end end - def self.move_cursor_down(val) + def move_cursor_down(val) if val > 0 return unless csbi = get_console_screen_buffer_info screen_height = get_screen_size.first y = cursor_pos.y + val y = screen_height - 1 if y > (screen_height - 1) - @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x) + @SetConsoleCursorPosition.call(@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x) elsif val < 0 move_cursor_up(-val) end end - def self.erase_after_cursor + def erase_after_cursor return unless csbi = get_console_screen_buffer_info attributes = csbi[8, 2].unpack1('S') cursor = csbi[4, 4].unpack1('L') written = 0.chr * 4 - @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) - @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written) + @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) + @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written) end - def self.scroll_down(val) + def scroll_down(val) return if val < 0 return unless csbi = get_console_screen_buffer_info buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s') screen_height = window_bottom - window_top + 1 val = screen_height if val > screen_height - if @@legacy_console || window_left != 0 + if @legacy_console || window_left != 0 # unless ENABLE_VIRTUAL_TERMINAL, # if srWindow.Left != 0 then it's conhost.exe hosted console # and puts "\n" causes horizontal scroll. its glitch. @@ -404,11 +404,11 @@ def self.scroll_down(val) scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4') destination_origin = 0 # y * 65536 + x fill = [' '.ord, attributes].pack('SS') - @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) + @ScrollConsoleScreenBuffer.call(@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) else origin_x = x + 1 origin_y = y - window_top + 1 - @@output.write [ + @output.write [ (origin_y != screen_height) ? "\e[#{screen_height};H" : nil, "\n" * val, (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil @@ -416,49 +416,49 @@ def self.scroll_down(val) end end - def self.clear_screen - if @@legacy_console + def clear_screen + if @legacy_console return unless csbi = get_console_screen_buffer_info buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s') fill_length = buffer_width * (window_bottom - window_top + 1) screen_topleft = window_top * 65536 written = 0.chr * 4 - @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written) - @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written) - @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft) + @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, fill_length, screen_topleft, written) + @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, fill_length, screen_topleft, written) + @SetConsoleCursorPosition.call(@hConsoleHandle, screen_topleft) else - @@output.write "\e[2J" "\e[H" + @output.write "\e[2J" "\e[H" end end - def self.set_screen_size(rows, columns) + def set_screen_size(rows, columns) raise NotImplementedError end - def self.hide_cursor + def hide_cursor size = 100 visible = 0 # 0 means false cursor_info = [size, visible].pack('Li') - @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info) + @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info) end - def self.show_cursor + def show_cursor size = 100 visible = 1 # 1 means true cursor_info = [size, visible].pack('Li') - @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info) + @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info) end - def self.set_winch_handler(&handler) - @@winch_handler = handler + def set_winch_handler(&handler) + @winch_handler = handler end - def self.prep + def prep # do nothing nil end - def self.deprep(otio) + def deprep(otio) # do nothing end diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 8f6421fb10..c2f5f0622e 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -412,7 +412,7 @@ def render_line_differential(old_items, new_items) # do nothing elsif level == :blank Reline::IOGate.move_cursor_column base_x - @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}" + @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}" else x, w, content = new_items[level] cover_begin = base_x != 0 && new_levels[base_x - 1] == level @@ -422,7 +422,7 @@ def render_line_differential(old_items, new_items) content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true) end Reline::IOGate.move_cursor_column x + pos - @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}" + @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}" end base_x += width end diff --git a/test/reline/helper.rb b/test/reline/helper.rb index 26fe834482..a5f850e838 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -22,29 +22,36 @@ module Reline class < expected but was - <#{chunk.inspect} (#{chunk.encoding.inspect})> in + <#{chunk.inspect} (#{chunk.encoding.inspect})> in EOM end diff --git a/test/reline/test_ansi_with_terminfo.rb b/test/reline/test_ansi_with_terminfo.rb index e1c56b9ee1..3adda10716 100644 --- a/test/reline/test_ansi_with_terminfo.rb +++ b/test/reline/test_ansi_with_terminfo.rb @@ -1,7 +1,7 @@ require_relative 'helper' -require 'reline/ansi' +require 'reline' -class Reline::ANSI::TestWithTerminfo < Reline::TestCase +class Reline::ANSI::WithTerminfoTest < Reline::TestCase def setup Reline.send(:test_mode, ansi: true) @config = Reline::Config.new diff --git a/test/reline/test_ansi_without_terminfo.rb b/test/reline/test_ansi_without_terminfo.rb index 3d153514f3..2db14cf0a0 100644 --- a/test/reline/test_ansi_without_terminfo.rb +++ b/test/reline/test_ansi_without_terminfo.rb @@ -1,7 +1,7 @@ require_relative 'helper' -require 'reline/ansi' +require 'reline' -class Reline::ANSI::TestWithoutTerminfo < Reline::TestCase +class Reline::ANSI::WithoutTerminfoTest < Reline::TestCase def setup Reline.send(:test_mode, ansi: true) @config = Reline::Config.new diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb index 03e4178f32..8c4b513600 100644 --- a/test/reline/test_config.rb +++ b/test/reline/test_config.rb @@ -85,15 +85,13 @@ def test_string_value_with_parens_and_quotes def test_encoding_is_ascii @config.reset - Reline.core.io_gate.reset(encoding: Encoding::US_ASCII) + Reline.core.io_gate.instance_variable_set(:@encoding, Encoding::US_ASCII) @config = Reline::Config.new assert_equal true, @config.convert_meta end def test_encoding_is_not_ascii - @config.reset - Reline.core.io_gate.reset(encoding: Encoding::UTF_8) @config = Reline::Config.new assert_equal nil, @config.convert_meta diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb index 7a38ecd596..1859da8199 100644 --- a/test/reline/test_line_editor.rb +++ b/test/reline/test_line_editor.rb @@ -4,14 +4,12 @@ class Reline::LineEditor class RenderLineDifferentialTest < Reline::TestCase - module TestIO - RESET_COLOR = "\e[0m" - - def self.move_cursor_column(col) + class TestIO < Reline::IO + def move_cursor_column(col) @output << "[COL_#{col}]" end - def self.erase_after_cursor + def erase_after_cursor @output << '[ERASE]' end end @@ -24,7 +22,7 @@ def setup @line_editor.instance_variable_set(:@screen_size, [24, 80]) @line_editor.instance_variable_set(:@output, @output) Reline.send(:remove_const, :IOGate) - Reline.const_set(:IOGate, TestIO) + Reline.const_set(:IOGate, TestIO.new) Reline::IOGate.instance_variable_set(:@output, @output) ensure $VERBOSE = verbose diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb index a20a5c9f44..c664ab74b0 100644 --- a/test/reline/test_reline.rb +++ b/test/reline/test_reline.rb @@ -375,7 +375,7 @@ def test_read_io def test_dumb_terminal lib = File.expand_path("../../lib", __dir__) out = IO.popen([{"TERM"=>"dumb"}, Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", "p Reline.core.io_gate"], &:read) - assert_equal("Reline::GeneralIO", out.chomp) + assert_match(/#