aboutsummaryrefslogtreecommitdiffstats
path: root/lib/reline/windows.rb
diff options
context:
space:
mode:
authoraycabta <aycabta@gmail.com>2019-04-27 14:53:09 +0900
committeraycabta <aycabta@gmail.com>2019-04-30 11:44:20 +0900
commit17350c7e5534c8678097d70698fe08614a6c3997 (patch)
tree0f41959093014a97d50aeb06a052f5450b4cb6b1 /lib/reline/windows.rb
parenteb45ba61160dbae412407f232fe9b3252eb99362 (diff)
downloadruby-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.rb174
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