aboutsummaryrefslogtreecommitdiffstats
path: root/lib/reline/windows.rb
blob: 868679ef9b5878303480d904e0ddb07ad748d219 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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