diff options
author | aycabta <aycabta@gmail.com> | 2019-04-27 14:53:09 +0900 |
---|---|---|
committer | aycabta <aycabta@gmail.com> | 2019-04-30 11:44:20 +0900 |
commit | 17350c7e5534c8678097d70698fe08614a6c3997 (patch) | |
tree | 0f41959093014a97d50aeb06a052f5450b4cb6b1 /lib/reline/windows.rb | |
parent | eb45ba61160dbae412407f232fe9b3252eb99362 (diff) | |
download | ruby-17350c7e5534c8678097d70698fe08614a6c3997.tar.gz |
Add Reline as a fallback library for Readline
* lib/reine.rb, lib/reline/*: Reline is a readline stdlib compatible
library.
* lib/readline.rb: Readline uses a fallback to Reline when ext/readline
doesn't exist.
* tool/sync_default_gems.rb: add ruby/reline as a default gem.
* appveyor.yml: add "set RELINE_TEST_ENCODING=Windows-31J" for test suit
of Reline, and add "--exclude readline" to "nmake test-all" on Visual
Studio builds because of strange behavior.
* spec/ruby/library/readline/spec_helper.rb: skip Reline as with
RbReadline.
Diffstat (limited to 'lib/reline/windows.rb')
-rw-r--r-- | lib/reline/windows.rb | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb new file mode 100644 index 0000000000..868679ef9b --- /dev/null +++ b/lib/reline/windows.rb @@ -0,0 +1,174 @@ +require 'fiddle/import' + +class Win32API + DLL = {} + TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG} + POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*' + + WIN32_TYPES = "VPpNnLlIi" + DL_TYPES = "0SSI" + + def initialize(dllname, func, import, export = "0", calltype = :stdcall) + @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1') + import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]} + export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)] + calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype] + + handle = DLL[dllname] ||= + begin + Fiddle.dlopen(dllname) + rescue Fiddle::DLError + raise unless File.extname(dllname).empty? + Fiddle.dlopen(dllname + ".dll") + end + + @func = Fiddle::Function.new(handle[func], import, export, calltype) + rescue Fiddle::DLError => e + raise LoadError, e.message, e.backtrace + 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 + end + + alias Call call +end + +module Reline + VK_LMENU = 0xA4 + STD_OUTPUT_HANDLE = -11 + @@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) + @@buf = [] + + def getwch + while @@kbhit.call == 0 + sleep(0.001) + end + result = [] + until @@kbhit.call == 0 + ret = @@getwch.call + begin + result.concat(ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes) + rescue Encoding::UndefinedConversionError + result << ret + result << @@getwch.call if ret == 224 + end + end + result + end + + def getc + unless @@buf.empty? + return @@buf.shift + end + input = getwch + alt = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0 + if input.size > 1 + @@buf.concat(input) + else # single byte + case input[0] + when 0x00 + getwch + alt = false + input = getwch + @@buf.concat(input) + when 0xE0 + @@buf.concat(input) + input = getwch + @@buf.concat(input) + when 0x03 + @@buf.concat(input) + else + @@buf.concat(input) + end + end + if alt + "\e".ord + else + @@buf.shift + end + end + + def self.get_screen_size + csbi = 0.chr * 24 + @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) + csbi[0, 4].unpack('SS') + end + + def self.cursor_pos + csbi = 0.chr * 24 + @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) + x = csbi[4, 2].unpack('s*').first + y = csbi[6, 4].unpack('s*').first + CursorPos.new(x, y) + end + + def self.move_cursor_column(val) + @@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val) + end + + def self.move_cursor_up(val) + if val > 0 + @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y - val) * 65536 + cursor_pos.x) + elsif val < 0 + move_cursor_down(-val) + end + end + + def self.move_cursor_down(val) + if val > 0 + @@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 + csbi = 0.chr * 24 + @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) + cursor = csbi[4, 4].unpack('L').first + written = 0.chr * 4 + @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.first - cursor_pos.x, cursor, written) + end + + def self.scroll_down(val) + return if val.zero? + scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4') + destination_origin = 0 # y * 65536 + x + fill = [' '.ord, 0].pack('SS') + @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) + end + + def self.clear_screen + # TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute + print "\e[2J" + print "\e[1;1H" + end + + def self.set_screen_size(rows, columns) + raise NotImplementedError + end + + def prep + # do nothing + nil + end + + def deprep(otio) + # do nothing + end +end |