[ruby/reline] Windows fix (https://github.com/ruby/reline/pull/775)
* test_yamatanooroti: close tempfile before unlink * test_yamatanooroti: omit because of windows does not support job control * test_yamatanooroti: change startup message detection for windows * windows.rb: can call win32api using nil as NULL for pointer argument Exception occurred when interrupted with Ctrl+C on legacy conhost * windows.rb: fix get_screen_size return [window height, buffer width] insted of [buffer height, buffer width] * windows.rb: import scroll_down() from ansi.rb * windows.rb: add auto linewrap control if VT output not supported (legacy console) * unfreeze WIN32API pointer arguments They internally duplicate arguments so api functions write to another place. This breaks the console mode detection with ruby-head. * remove useless code from Win32API#call argument repacking and return value tweaking is not needed for Reline::Windows requirements. * Correctly handle top of console viewport * Revert "remove useless code from Win32API#call" This reverts commit https://github.com/ruby/reline/commit/060ba140ed43. * Revert "windows.rb: can call win32api using nil as NULL for pointer argument" This reverts commit https://github.com/ruby/reline/commit/93a23bc5d0c9. https://github.com/ruby/reline/commit/47c1ffbabe
This commit is contained in:
parent
9a9a586d62
commit
b99c17a95b
@ -157,6 +157,7 @@ class Reline::Windows < Reline::IO
|
||||
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.
|
||||
@ -170,7 +171,7 @@ class Reline::Windows < Reline::IO
|
||||
end
|
||||
|
||||
private def getconsolemode
|
||||
mode = "\000\000\000\000"
|
||||
mode = +"\0\0\0\0"
|
||||
call_with_console_handle(@GetConsoleMode, mode)
|
||||
mode.unpack1('L')
|
||||
end
|
||||
@ -344,35 +345,38 @@ class Reline::Windows < Reline::IO
|
||||
# [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 [1, 1]
|
||||
end
|
||||
csbi[0, 4].unpack('SS').reverse
|
||||
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
|
||||
@ -380,58 +384,39 @@ class Reline::Windows < Reline::IO
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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)
|
||||
@ -472,6 +457,28 @@ class Reline::Windows < Reline::IO
|
||||
# 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
|
||||
|
@ -472,8 +472,11 @@ class Reline::LineEditor
|
||||
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 @@ class Reline::LineEditor
|
||||
# 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 @@ class Reline::LineEditor
|
||||
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
|
||||
|
@ -25,8 +25,8 @@ begin
|
||||
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
|
||||
@ -1065,7 +1065,7 @@ begin
|
||||
|
||||
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
|
||||
@ -1796,6 +1796,7 @@ begin
|
||||
end
|
||||
|
||||
def test_stop_continue
|
||||
omit if Reline.core.io_gate.win?
|
||||
pidfile = Tempfile.create('pidfile')
|
||||
rubyfile = Tempfile.create('rubyfile')
|
||||
rubyfile.write <<~RUBY
|
||||
@ -1816,6 +1817,7 @@ begin
|
||||
close
|
||||
ensure
|
||||
File.delete(rubyfile.path) if rubyfile
|
||||
pidfile.close if pidfile
|
||||
File.delete(pidfile.path) if pidfile
|
||||
end
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user