[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
|
STD_OUTPUT_HANDLE = -11
|
||||||
FILE_TYPE_PIPE = 0x0003
|
FILE_TYPE_PIPE = 0x0003
|
||||||
FILE_NAME_INFO = 2
|
FILE_NAME_INFO = 2
|
||||||
|
ENABLE_WRAP_AT_EOL_OUTPUT = 2
|
||||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
||||||
|
|
||||||
# Calling Win32API with console handle is reported to fail after executing some external command.
|
# Calling Win32API with console handle is reported to fail after executing some external command.
|
||||||
@ -170,7 +171,7 @@ class Reline::Windows < Reline::IO
|
|||||||
end
|
end
|
||||||
|
|
||||||
private def getconsolemode
|
private def getconsolemode
|
||||||
mode = "\000\000\000\000"
|
mode = +"\0\0\0\0"
|
||||||
call_with_console_handle(@GetConsoleMode, mode)
|
call_with_console_handle(@GetConsoleMode, mode)
|
||||||
mode.unpack1('L')
|
mode.unpack1('L')
|
||||||
end
|
end
|
||||||
@ -344,35 +345,38 @@ class Reline::Windows < Reline::IO
|
|||||||
# [18,2] dwMaximumWindowSize.X
|
# [18,2] dwMaximumWindowSize.X
|
||||||
# [20,2] dwMaximumWindowSize.Y
|
# [20,2] dwMaximumWindowSize.Y
|
||||||
csbi = 0.chr * 22
|
csbi = 0.chr * 22
|
||||||
return if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) == 0
|
if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0
|
||||||
csbi
|
# returns [width, height, x, y, attributes, left, top, right, bottom]
|
||||||
|
csbi.unpack("s9")
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze
|
||||||
|
|
||||||
def get_screen_size
|
def get_screen_size
|
||||||
unless csbi = get_console_screen_buffer_info
|
width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI
|
||||||
return [1, 1]
|
[bottom - top + 1, width]
|
||||||
end
|
|
||||||
csbi[0, 4].unpack('SS').reverse
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cursor_pos
|
def cursor_pos
|
||||||
unless csbi = get_console_screen_buffer_info
|
_, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI
|
||||||
return Reline::CursorPos.new(0, 0)
|
Reline::CursorPos.new(x, y - top)
|
||||||
end
|
|
||||||
x = csbi[4, 2].unpack1('s')
|
|
||||||
y = csbi[6, 2].unpack1('s')
|
|
||||||
Reline::CursorPos.new(x, y)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def move_cursor_column(val)
|
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
|
end
|
||||||
|
|
||||||
def move_cursor_up(val)
|
def move_cursor_up(val)
|
||||||
if val > 0
|
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
|
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
|
elsif val < 0
|
||||||
move_cursor_down(-val)
|
move_cursor_down(-val)
|
||||||
end
|
end
|
||||||
@ -380,58 +384,39 @@ class Reline::Windows < Reline::IO
|
|||||||
|
|
||||||
def move_cursor_down(val)
|
def move_cursor_down(val)
|
||||||
if val > 0
|
if val > 0
|
||||||
return unless csbi = get_console_screen_buffer_info
|
_, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info
|
||||||
screen_height = get_screen_size.first
|
return unless y
|
||||||
y = cursor_pos.y + val
|
screen_height = bottom - top
|
||||||
y = screen_height - 1 if y > (screen_height - 1)
|
y = (y - top) + val
|
||||||
call_with_console_handle(@SetConsoleCursorPosition, (cursor_pos.y + val) * 65536 + cursor_pos.x)
|
y = screen_height if y > screen_height
|
||||||
|
call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
|
||||||
elsif val < 0
|
elsif val < 0
|
||||||
move_cursor_up(-val)
|
move_cursor_up(-val)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def erase_after_cursor
|
def erase_after_cursor
|
||||||
return unless csbi = get_console_screen_buffer_info
|
width, _, x, y, attributes, = get_console_screen_buffer_info
|
||||||
attributes = csbi[8, 2].unpack1('S')
|
return unless x
|
||||||
cursor = csbi[4, 4].unpack1('L')
|
|
||||||
written = 0.chr * 4
|
written = 0.chr * 4
|
||||||
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, 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, get_screen_size.last - cursor_pos.x, cursor, written)
|
call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written)
|
||||||
end
|
end
|
||||||
|
|
||||||
def scroll_down(val)
|
# This only works when the cursor is at the bottom of the scroll range
|
||||||
return if val < 0
|
# For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
|
||||||
return unless csbi = get_console_screen_buffer_info
|
def scroll_down(x)
|
||||||
buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
|
return if x.zero?
|
||||||
screen_height = window_bottom - window_top + 1
|
# We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
|
||||||
val = screen_height if val > screen_height
|
@output.write "\n" * x
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_screen
|
def clear_screen
|
||||||
if @legacy_console
|
if @legacy_console
|
||||||
return unless csbi = get_console_screen_buffer_info
|
width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info
|
||||||
buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
|
return unless width
|
||||||
fill_length = buffer_width * (window_bottom - window_top + 1)
|
fill_length = width * (bottom - top + 1)
|
||||||
screen_topleft = window_top * 65536
|
screen_topleft = top * 65536
|
||||||
written = 0.chr * 4
|
written = 0.chr * 4
|
||||||
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
|
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
|
||||||
call_with_console_handle(@FillConsoleOutputAttribute, attributes, 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
|
# do nothing
|
||||||
end
|
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
|
class KeyEventRecord
|
||||||
|
|
||||||
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
|
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
|
||||||
|
@ -472,8 +472,11 @@ class Reline::LineEditor
|
|||||||
end
|
end
|
||||||
|
|
||||||
def print_nomultiline_prompt
|
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.
|
# 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
|
@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
|
end
|
||||||
|
|
||||||
def render
|
def render
|
||||||
@ -509,6 +512,7 @@ class Reline::LineEditor
|
|||||||
# by calculating the difference from the previous render.
|
# by calculating the difference from the previous render.
|
||||||
|
|
||||||
private def render_differential(new_lines, new_cursor_x, new_cursor_y)
|
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
|
rendered_lines = @rendered_screen.lines
|
||||||
cursor_y = @rendered_screen.cursor_y
|
cursor_y = @rendered_screen.cursor_y
|
||||||
if new_lines != rendered_lines
|
if new_lines != rendered_lines
|
||||||
@ -539,6 +543,8 @@ class Reline::LineEditor
|
|||||||
Reline::IOGate.move_cursor_column new_cursor_x
|
Reline::IOGate.move_cursor_column new_cursor_x
|
||||||
Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
|
Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
|
||||||
@rendered_screen.cursor_y = new_cursor_y
|
@rendered_screen.cursor_y = new_cursor_y
|
||||||
|
ensure
|
||||||
|
Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
|
||||||
end
|
end
|
||||||
|
|
||||||
private def clear_rendered_screen_cache
|
private def clear_rendered_screen_cache
|
||||||
|
@ -25,8 +25,8 @@ begin
|
|||||||
config_file = Tempfile.create(%w{face_config- .rb})
|
config_file = Tempfile.create(%w{face_config- .rb})
|
||||||
config_file.write face_config
|
config_file.write face_config
|
||||||
block.call(config_name, config_file)
|
block.call(config_name, config_file)
|
||||||
config_file.close
|
|
||||||
ensure
|
ensure
|
||||||
|
config_file.close
|
||||||
File.delete(config_file)
|
File.delete(config_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1065,7 +1065,7 @@ begin
|
|||||||
|
|
||||||
def test_simple_dialog_with_scroll_screen
|
def test_simple_dialog_with_scroll_screen
|
||||||
iterate_over_face_configs do |config_name, config_file|
|
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("if 1\n 2\n 3\n 4\n 5\n 6")
|
||||||
write("\C-p\C-n\C-p\C-p\C-p#")
|
write("\C-p\C-n\C-p\C-p\C-p#")
|
||||||
close
|
close
|
||||||
@ -1796,6 +1796,7 @@ begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_stop_continue
|
def test_stop_continue
|
||||||
|
omit if Reline.core.io_gate.win?
|
||||||
pidfile = Tempfile.create('pidfile')
|
pidfile = Tempfile.create('pidfile')
|
||||||
rubyfile = Tempfile.create('rubyfile')
|
rubyfile = Tempfile.create('rubyfile')
|
||||||
rubyfile.write <<~RUBY
|
rubyfile.write <<~RUBY
|
||||||
@ -1816,6 +1817,7 @@ begin
|
|||||||
close
|
close
|
||||||
ensure
|
ensure
|
||||||
File.delete(rubyfile.path) if rubyfile
|
File.delete(rubyfile.path) if rubyfile
|
||||||
|
pidfile.close if pidfile
|
||||||
File.delete(pidfile.path) if pidfile
|
File.delete(pidfile.path) if pidfile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user