1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[ruby/reline] Use ReadConsoleInputW() instead of getwch()

This needs https://github.com/aycabta/yamatanooroti/pull/19, which is released
by yamatanooroti gem 0.0.7, to test with yamatanooroti.

https://github.com/ruby/reline/commit/06c1f45da1
This commit is contained in:
aycabta 2021-04-16 19:59:36 +09:00
parent a049dfd10a
commit c59bbd86a6
2 changed files with 97 additions and 62 deletions

View file

@ -89,9 +89,30 @@ class Reline::Windows
VK_LMENU = 0xA4
VK_CONTROL = 0x11
VK_SHIFT = 0x10
KEY_EVENT = 0x01
WINDOW_BUFFER_SIZE_EVENT = 0x04
CAPSLOCK_ON = 0x0080
ENHANCED_KEY = 0x0100
LEFT_ALT_PRESSED = 0x0002
LEFT_CTRL_PRESSED = 0x0008
NUMLOCK_ON = 0x0020
RIGHT_ALT_PRESSED = 0x0001
RIGHT_CTRL_PRESSED = 0x0004
SCROLLLOCK_ON = 0x0040
SHIFT_PRESSED = 0x0010
VK_END = 0x23
VK_HOME = 0x24
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
VK_DELETE = 0x2E
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')
@ -105,7 +126,7 @@ class Reline::Windows
@@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
@@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
@@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
@@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', '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')
@ -157,79 +178,70 @@ class Reline::Windows
name =~ /(msys-|cygwin-).*-pty/ ? true : false
end
def self.getwch
unless @@input_buf.empty?
return @@input_buf.shift
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
char = char_code.chr(Encoding::UTF_8)
if char_code == 0x0D and control_key_state.anybits?(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | SHIFT_PRESSED)
# It's treated as Meta+Enter on Windows.
@@output_buf.push("\e".ord)
@@output_buf.push(char_code)
elsif control_key_state.anybits?(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
@@output_buf.push("\e".ord)
@@output_buf.concat(char.bytes)
elsif control_key_state.anybits?(ENHANCED_KEY)
case virtual_key_code # Emulate getwch() key sequences.
when VK_END
@@output_buf.push(0, 79)
when VK_HOME
@@output_buf.push(0, 71)
when VK_LEFT
@@output_buf.push(0, 75)
when VK_UP
@@output_buf.push(0, 72)
when VK_RIGHT
@@output_buf.push(0, 77)
when VK_DOWN
@@output_buf.push(0, 80)
when VK_DELETE
@@output_buf.push(0, 83)
end
elsif char_code == 0 and control_key_state != 0
# unknown
else
@@output_buf.concat(char.bytes)
end
end
def self.check_input_event
num_of_events = 0.chr * 8
while @@output_buf.empty? #or true
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
input_record = 0.chr * 18
read_event = 0.chr * 4
if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
event = input_record[0, 2].unpack('s*').first
case event
when WINDOW_BUFFER_SIZE_EVENT
@@winch_handler.()
when KEY_EVENT
key_down = input_record[4, 4].unpack('l*').first
repeat_count = input_record[8, 2].unpack('s*').first
virtual_key_code = input_record[10, 2].unpack('s*').first
virtual_scan_code = input_record[12, 2].unpack('s*').first
char_code = input_record[14, 2].unpack('S*').first
control_key_state = input_record[16, 2].unpack('S*').first
is_key_down = key_down.zero? ? false : true
if is_key_down
process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
end
end
while @@kbhit.call == 0
sleep(0.001)
end
until @@kbhit.call == 0
ret = @@getwch.call
if ret == 0 or ret == 0xE0
@@input_buf << ret
ret = @@getwch.call
@@input_buf << ret
return @@input_buf.shift
end
begin
bytes = ret.chr(Encoding::UTF_8).bytes
@@input_buf.push(*bytes)
rescue Encoding::UndefinedConversionError
@@input_buf << ret
@@input_buf << @@getwch.call if ret == 224
end
end
@@input_buf.shift
end
def self.getc
num_of_events = 0.chr * 8
while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and num_of_events.unpack('L').first > 0
input_record = 0.chr * 18
read_event = 0.chr * 4
if @@ReadConsoleInput.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
event = input_record[0, 2].unpack('s*').first
if event == WINDOW_BUFFER_SIZE_EVENT
@@winch_handler.()
end
end
end
unless @@output_buf.empty?
return @@output_buf.shift
end
input = getwch
meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0
shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0
force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D
if force_enter
# It's treated as Meta+Enter on Windows
@@output_buf.push("\e".ord)
@@output_buf.push(input)
else
case input
when 0x00
meta = false
@@output_buf.push(input)
input = getwch
@@output_buf.push(*input)
when 0xE0
@@output_buf.push(input)
input = getwch
@@output_buf.push(*input)
when 0x03
@@output_buf.push(input)
else
@@output_buf.push(input)
end
end
if meta
"\e".ord
else
check_input_event
@@output_buf.shift
end
end
def self.ungetc(c)
@@output_buf.unshift(c)

View file

@ -730,6 +730,29 @@ begin
EOC
end
def test_meta_key
start_terminal(50, 200, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("def ge\M-bho")
close
assert_screen(<<~EOC)
Multiline REPL.
prompt> def hoge
EOC
end
def test_force_enter
start_terminal(50, 200, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("def hoge\nend\C-p\C-e")
write("\M-\x0D")
close
assert_screen(<<~EOC)
Multiline REPL.
prompt> def hoge
prompt>
prompt> end
EOC
end
private def write_inputrc(content)
File.open(@inputrc_file, 'w') do |f|
f.write content