/
readline.cr
151 lines (123 loc) · 3.87 KB
/
readline.cr
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
@[Link("readline")]
{% if flag?(:openbsd) %}
@[Link("termcap")]
{% end %}
lib LibReadline
alias Int = LibC::Int
fun readline(prompt : UInt8*) : UInt8*
fun add_history(line : UInt8*)
fun rl_bind_key(key : Int, f : Int, Int -> Int) : Int
fun rl_unbind_key(key : Int) : Int
alias CPP = (UInt8*, Int, Int) -> UInt8**
$rl_attempted_completion_function : CPP
$rl_line_buffer : UInt8*
$rl_point : Int
$rl_done : Int
end
private def malloc_match(match)
match_ptr = LibC.malloc(match.bytesize + 1).as(UInt8*)
match_ptr.copy_from(match.to_unsafe, match.bytesize)
match_ptr[match.bytesize] = 0_u8
match_ptr
end
module Readline
extend self
alias CompletionProc = String -> Array(String)?
alias KeyBindingProc = Int32, Int32 -> Int32
KeyBindingHandler = ->(count : LibReadline::Int, key : LibReadline::Int) do
if (handlers = @@key_bind_handlers) && handlers[key.to_i32]?
res = handlers[key].call(count.to_i32, key.to_i32)
LibReadline::Int.new(res)
else
LibReadline::Int.new(1)
end
end
def readline(prompt = "", add_history = false)
line = LibReadline.readline(prompt)
if line
LibReadline.add_history(line) if add_history
String.new(line).tap { LibC.free(line.as(Void*)) }
else
nil
end
end
def autocomplete(&@@completion_proc : CompletionProc)
end
def line_buffer
line = LibReadline.rl_line_buffer
return nil unless line
String.new(line)
end
def point
LibReadline.rl_point
end
def bind_key(c : Char, &f : KeyBindingProc)
raise ArgumentError.new "Not a valid ASCII character: '#{c.inspect}'" unless 0 <= c.ord <= 255
handlers = (@@key_bind_handlers ||= {} of LibReadline::Int => KeyBindingProc)
handlers[c.ord] = f
res = LibReadline.rl_bind_key(c.ord, KeyBindingHandler).to_i32
raise ArgumentError.new "Invalid key: '#{c.inspect}'" unless res == 0
end
def unbind_key(c : Char)
if (handlers = @@key_bind_handlers) && handlers[c.ord]?
handlers.delete(c.ord)
res = LibReadline.rl_unbind_key(c.ord).to_i32
raise Exception.new "Error unbinding key: '#{c.inspect}'" unless res == 0
else
raise KeyError.new "Key not bound: '#{c.inspect}'"
end
end
def done
LibReadline.rl_done != 0
end
def done=(val : Bool)
LibReadline.rl_done = val.hash
end
# :nodoc:
def common_prefix_bytesize(str1 : String, str2 : String)
r1 = Char::Reader.new str1
r2 = Char::Reader.new str2
while r1.has_next? && r2.has_next?
break if r1.current_char != r2.current_char
r1.next_char
r2.next_char
end
r1.pos
end
# :nodoc:
def common_prefix_bytesize(strings : Array)
str1 = strings[0]
low = str1.bytesize
1.upto(strings.size - 1).each do |i|
str2 = strings[i]
low2 = common_prefix_bytesize(str1, str2)
low = low2 if low2 < low
end
low
end
LibReadline.rl_attempted_completion_function = ->(text_ptr, start, finish) {
completion_proc = @@completion_proc
return Pointer(UInt8*).null unless completion_proc
text = String.new(text_ptr)
matches = completion_proc.call(text)
return Pointer(UInt8*).null unless matches
return Pointer(UInt8*).null if matches.empty?
# We *must* to create the results using malloc (readline later frees that).
# We create an extra result for the first element.
result = LibC.malloc(sizeof(UInt8*) * (matches.size + 2)).as(UInt8**)
matches.each_with_index do |match, i|
result[i + 1] = malloc_match(match)
end
result[matches.size + 1] = Pointer(UInt8).null
# The first element is the completion if it's oe
if matches.size == 1
result[0] = malloc_match(matches[0])
else
# Otherwise, we compute the common prefix of all matches
low = Readline.common_prefix_bytesize(matches)
sub = matches[0].byte_slice(0, low)
result[0] = malloc_match(sub)
end
result
}
end