From 3e1cfa2f90080c23062c5261222f97bebb261697 Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 16 Oct 2024 22:15:04 +0900 Subject: [PATCH 01/12] test_yamatanooroti: close tempfile before unlink --- test/reline/yamatanooroti/test_rendering.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 414bca5b7c..349f48b2aa 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -25,8 +25,8 @@ def iterate_over_face_configs(&block) config_file = Tempfile.create(%w{face_config- .rb}) config_file.write face_config block.call(config_name, config_file) - config_file.close ensure + config_file.close File.delete(config_file) end end @@ -1816,6 +1816,7 @@ def test_stop_continue close ensure File.delete(rubyfile.path) if rubyfile + pidfile.close if pidfile File.delete(pidfile.path) if pidfile end From 805023992d0394d0dfa60d43f535d2119bc4f7f7 Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 16 Oct 2024 22:16:03 +0900 Subject: [PATCH 02/12] test_yamatanooroti: omit because of windows does not support job control --- test/reline/yamatanooroti/test_rendering.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 349f48b2aa..7cac30ed63 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -1796,6 +1796,7 @@ def test_thread_safe end def test_stop_continue + omit if Reline.core.io_gate.win? pidfile = Tempfile.create('pidfile') rubyfile = Tempfile.create('rubyfile') rubyfile.write <<~RUBY From 235367d6f87249e9dcd0b4410198a611cd201e8e Mon Sep 17 00:00:00 2001 From: YO4 Date: Sun, 13 Oct 2024 02:14:19 +0900 Subject: [PATCH 03/12] test_yamatanooroti: change startup message detection for windows --- test/reline/yamatanooroti/test_rendering.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 7cac30ed63..657a071eb9 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -1065,7 +1065,7 @@ def test_dialog_scroll_pushup_condition def test_simple_dialog_with_scroll_screen iterate_over_face_configs do |config_name, config_file| - start_terminal(5, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.') + start_terminal(5, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: /prompt>/) write("if 1\n 2\n 3\n 4\n 5\n 6") write("\C-p\C-n\C-p\C-p\C-p#") close From 93a23bc5d0c92e527890134e1f845bbc8cb6d505 Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 16 Oct 2024 22:03:11 +0900 Subject: [PATCH 04/12] windows.rb: can call win32api using nil as NULL for pointer argument Exception occurred when interrupted with Ctrl+C on legacy conhost --- lib/reline/io/windows.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index f718743193..175ba537f6 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -115,7 +115,7 @@ def initialize(dllname, func, import, export = "0", calltype = :stdcall) def call(*args) import = @proto.split("") args.each_with_index do |x, i| - args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" + args[i], = [x == 0 ? nil : x&.+@].pack("p").unpack(POINTER_TYPE) if import[i] == "S" args[i], = [x].pack("I").unpack("i") if import[i] == "I" end ret, = @func.call(*args) From 3875659661b96ba9ec2cb21e80c924e482bd8bdd Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 16 Oct 2024 22:04:37 +0900 Subject: [PATCH 05/12] windows.rb: fix get_screen_size return [window height, buffer width] insted of [buffer height, buffer width] --- lib/reline/io/windows.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index 175ba537f6..a5bb70eb19 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -350,9 +350,12 @@ def get_console_screen_buffer_info def get_screen_size unless csbi = get_console_screen_buffer_info - return [1, 1] + return [24, 80] end - csbi[0, 4].unpack('SS').reverse + top = csbi[12, 2].unpack1('S') + bottom = csbi[16, 2].unpack1('S') + width = csbi[0, 2].unpack1('S') + [bottom - top + 1, width] end def cursor_pos From 9b3ffd8123b8b5fc50e7c552d76c49c9cdcc28b5 Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 16 Oct 2024 22:06:45 +0900 Subject: [PATCH 06/12] windows.rb: import scroll_down() from ansi.rb --- lib/reline/io/windows.rb | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index a5bb70eb19..7b0646b28b 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -402,31 +402,12 @@ def erase_after_cursor call_with_console_handle(@FillConsoleOutputAttribute, attributes, get_screen_size.last - cursor_pos.x, cursor, written) end - 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 - # unless ENABLE_VIRTUAL_TERMINAL, - # if srWindow.Left != 0 then it's conhost.exe hosted console - # and puts "\n" causes horizontal scroll. its glitch. - # FYI irb write from culumn 1, so this gives no gain. - scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4') - destination_origin = 0 # y * 65536 + x - fill = [' '.ord, attributes].pack('SS') - call_with_console_handle(@ScrollConsoleScreenBuffer, scroll_rectangle, nil, destination_origin, fill) - else - origin_x = x + 1 - origin_y = y - window_top + 1 - @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 - ].join - 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 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 end def clear_screen From 053bbdb48416a2fee8ab63070d5eccc686bcf0c0 Mon Sep 17 00:00:00 2001 From: YO4 Date: Sat, 19 Oct 2024 22:38:06 +0900 Subject: [PATCH 07/12] windows.rb: add auto linewrap control if VT output not supported (legacy console) --- lib/reline/io/windows.rb | 23 +++++++++++++++++++++++ lib/reline/line_editor.rb | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index 7b0646b28b..a4898954f4 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -157,6 +157,7 @@ def call(*args) STD_OUTPUT_HANDLE = -11 FILE_TYPE_PIPE = 0x0003 FILE_NAME_INFO = 2 + ENABLE_WRAP_AT_EOL_OUTPUT = 2 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 # Calling Win32API with console handle is reported to fail after executing some external command. @@ -456,6 +457,28 @@ def deprep(otio) # do nothing end + def disable_auto_linewrap(setting = true, &block) + mode = getconsolemode + if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + if block + begin + setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) + block.call + ensure + setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) + end + else + if setting + setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) + else + setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) + end + end + else + block.call if block + end + end + class KeyEventRecord attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 4d0eb393e9..709246980d 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -472,8 +472,11 @@ def render_finished end def print_nomultiline_prompt + Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence. @output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline + ensure + Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? end def render @@ -509,6 +512,7 @@ def render # by calculating the difference from the previous render. private def render_differential(new_lines, new_cursor_x, new_cursor_y) + Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? rendered_lines = @rendered_screen.lines cursor_y = @rendered_screen.cursor_y if new_lines != rendered_lines @@ -539,6 +543,8 @@ def render Reline::IOGate.move_cursor_column new_cursor_x Reline::IOGate.move_cursor_down new_cursor_y - cursor_y @rendered_screen.cursor_y = new_cursor_y + ensure + Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? end private def clear_rendered_screen_cache From 242916580a4933354961bf4107ae7f2105e49df6 Mon Sep 17 00:00:00 2001 From: YO4 Date: Thu, 31 Oct 2024 21:43:12 +0900 Subject: [PATCH 08/12] unfreeze WIN32API pointer arguments They internally duplicate arguments so api functions write to another place. This breaks the console mode detection with ruby-head. --- lib/reline/io/windows.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index a4898954f4..1525f26855 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -171,7 +171,7 @@ def call(*args) end private def getconsolemode - mode = "\000\000\000\000" + mode = +"\0\0\0\0" call_with_console_handle(@GetConsoleMode, mode) mode.unpack1('L') end From 060ba140ed431d368ab322746c12acd175e61c7b Mon Sep 17 00:00:00 2001 From: YO4 Date: Tue, 5 Nov 2024 20:32:50 +0900 Subject: [PATCH 09/12] remove useless code from Win32API#call argument repacking and return value tweaking is not needed for Reline::Windows requirements. --- lib/reline/io/windows.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index 1525f26855..cdbc6dc3c4 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -113,13 +113,7 @@ def initialize(dllname, func, import, export = "0", calltype = :stdcall) end def call(*args) - import = @proto.split("") - args.each_with_index do |x, i| - args[i], = [x == 0 ? nil : x&.+@].pack("p").unpack(POINTER_TYPE) if import[i] == "S" - args[i], = [x].pack("I").unpack("i") if import[i] == "I" - end - ret, = @func.call(*args) - return ret || 0 + @func.call(*args) end end end From df7054193d53d0b55f7787d2420af59c284c64c8 Mon Sep 17 00:00:00 2001 From: YO4 Date: Tue, 5 Nov 2024 22:15:27 +0900 Subject: [PATCH 10/12] Correctly handle top of console viewport --- lib/reline/io/windows.rb | 62 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index cdbc6dc3c4..b84b4cb36d 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -339,38 +339,38 @@ def get_console_screen_buffer_info # [18,2] dwMaximumWindowSize.X # [20,2] dwMaximumWindowSize.Y csbi = 0.chr * 22 - return if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) == 0 - csbi + if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0 + # returns [width, height, x, y, attributes, left, top, right, bottom] + csbi.unpack("s9") + else + return nil + end end + ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze + def get_screen_size - unless csbi = get_console_screen_buffer_info - return [24, 80] - end - top = csbi[12, 2].unpack1('S') - bottom = csbi[16, 2].unpack1('S') - width = csbi[0, 2].unpack1('S') + width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI [bottom - top + 1, width] end def cursor_pos - unless csbi = get_console_screen_buffer_info - return Reline::CursorPos.new(0, 0) - end - x = csbi[4, 2].unpack1('s') - y = csbi[6, 2].unpack1('s') - Reline::CursorPos.new(x, y) + _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI + Reline::CursorPos.new(x, y - top) end def move_cursor_column(val) - call_with_console_handle(@SetConsoleCursorPosition, cursor_pos.y * 65536 + val) + _, _, _, y, = get_console_screen_buffer_info + call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y end def move_cursor_up(val) if val > 0 - y = cursor_pos.y - val + _, _, x, y, _, _, top, = get_console_screen_buffer_info + return unless y + y = (y - top) - val y = 0 if y < 0 - call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + cursor_pos.x) + call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_down(-val) end @@ -378,23 +378,23 @@ def move_cursor_up(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) - call_with_console_handle(@SetConsoleCursorPosition, (cursor_pos.y + val) * 65536 + cursor_pos.x) + _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info + return unless y + screen_height = bottom - top + y = (y - top) + val + y = screen_height if y > screen_height + call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_up(-val) end end def erase_after_cursor - return unless csbi = get_console_screen_buffer_info - attributes = csbi[8, 2].unpack1('S') - cursor = csbi[4, 4].unpack1('L') + width, _, x, y, attributes, = get_console_screen_buffer_info + return unless x written = 0.chr * 4 - call_with_console_handle(@FillConsoleOutputCharacter, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) - call_with_console_handle(@FillConsoleOutputAttribute, attributes, get_screen_size.last - cursor_pos.x, cursor, written) + call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written) + call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written) end # This only works when the cursor is at the bottom of the scroll range @@ -407,10 +407,10 @@ def scroll_down(x) 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 + width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info + return unless width + fill_length = width * (bottom - top + 1) + screen_topleft = top * 65536 written = 0.chr * 4 call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) From eb5959c11c0fdd935658457a2398a0f4a48bde8a Mon Sep 17 00:00:00 2001 From: YO4 Date: Thu, 7 Nov 2024 07:42:10 +0900 Subject: [PATCH 11/12] Revert "remove useless code from Win32API#call" This reverts commit 060ba140ed431d368ab322746c12acd175e61c7b. --- lib/reline/io/windows.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index b84b4cb36d..8bd15b17a6 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -113,7 +113,13 @@ def initialize(dllname, func, import, export = "0", calltype = :stdcall) end def call(*args) - @func.call(*args) + import = @proto.split("") + args.each_with_index do |x, i| + args[i], = [x == 0 ? nil : x&.+@].pack("p").unpack(POINTER_TYPE) if import[i] == "S" + args[i], = [x].pack("I").unpack("i") if import[i] == "I" + end + ret, = @func.call(*args) + return ret || 0 end end end From 5443ab5225e1d046bf37d23e459d6ac71e17d5aa Mon Sep 17 00:00:00 2001 From: YO4 Date: Thu, 7 Nov 2024 07:44:38 +0900 Subject: [PATCH 12/12] Revert "windows.rb: can call win32api using nil as NULL for pointer argument" This reverts commit 93a23bc5d0c92e527890134e1f845bbc8cb6d505. --- lib/reline/io/windows.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb index 8bd15b17a6..29eab28073 100644 --- a/lib/reline/io/windows.rb +++ b/lib/reline/io/windows.rb @@ -115,7 +115,7 @@ def initialize(dllname, func, import, export = "0", calltype = :stdcall) def call(*args) import = @proto.split("") args.each_with_index do |x, i| - args[i], = [x == 0 ? nil : x&.+@].pack("p").unpack(POINTER_TYPE) if import[i] == "S" + args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" args[i], = [x].pack("I").unpack("i") if import[i] == "I" end ret, = @func.call(*args)