yap-rawline 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rawline/completer.rb +7 -17
- data/lib/rawline/editor.rb +12 -15
- data/lib/rawline/event_loop.rb +6 -3
- data/lib/rawline/history_buffer.rb +210 -82
- data/lib/rawline/keycode_parser.rb +22 -9
- data/lib/rawline/renderer.rb +55 -17
- data/lib/rawline/terminal.rb +0 -4
- data/lib/rawline/version.rb +1 -1
- data/spec/history_buffer_spec.rb +557 -133
- data/spec/keycode_parser_spec.rb +28 -16
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92c874a27903902d286dbf30ed5a9650766b09cd
|
4
|
+
data.tar.gz: b88b897fd79df0714fe681765296577e617189ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8356ff5e616f22d71c360271c16b54b17ae431d3473f22b5105894ab605d05c88d308bde5cbf2eeffa596f702aa4e8eb62cceda6cba858717a420ea7c64b5e1f
|
7
|
+
data.tar.gz: 2d07d64f41788cd96655f2b227e4361f727ce9b393b3473633c06ab51a0e05604bc5920c79fb9060ac233c2af7866fd9974f5835abd94a5532c85a98f700dfe9
|
data/lib/rawline/completer.rb
CHANGED
@@ -11,11 +11,10 @@ module RawLine
|
|
11
11
|
@done_proc = done
|
12
12
|
@keys = keys
|
13
13
|
|
14
|
-
@completion_matches = HistoryBuffer.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@completion_matches.empty
|
14
|
+
@completion_matches = HistoryBuffer.new_with_infinite_size(
|
15
|
+
cycle: true,
|
16
|
+
duplicates: false
|
17
|
+
)
|
19
18
|
|
20
19
|
@first_time = true
|
21
20
|
@word_start = @line.word[:start]
|
@@ -30,7 +29,8 @@ module RawLine
|
|
30
29
|
|
31
30
|
if @first_time
|
32
31
|
matches = fetch_completions
|
33
|
-
|
32
|
+
@completion_matches.replace(matches)
|
33
|
+
@completion_matches.first!
|
34
34
|
|
35
35
|
if matches.length == 1
|
36
36
|
handle_one_match
|
@@ -77,7 +77,7 @@ module RawLine
|
|
77
77
|
|
78
78
|
def handle_one_match
|
79
79
|
Treefell['editor'].puts "completer, exactly one possible completion found: #{@completion_matches.inspect}"
|
80
|
-
@completion_selected_proc.call(@completion_matches.
|
80
|
+
@completion_selected_proc.call(@completion_matches.get)
|
81
81
|
|
82
82
|
Treefell['editor'].puts "completer, done"
|
83
83
|
@done_proc.call
|
@@ -85,9 +85,6 @@ module RawLine
|
|
85
85
|
|
86
86
|
def handle_more_than_one_match
|
87
87
|
Treefell['editor'].puts "completer, more than one possible completion"
|
88
|
-
|
89
|
-
# Get first match
|
90
|
-
@completion_matches.forward
|
91
88
|
match = @completion_matches.get
|
92
89
|
|
93
90
|
Treefell['editor'].puts "completer: first completion: #{match} possible: #{@completion_matches.inspect}"
|
@@ -102,13 +99,6 @@ module RawLine
|
|
102
99
|
@done_proc.call
|
103
100
|
end
|
104
101
|
|
105
|
-
def resize(matches)
|
106
|
-
if matches.any?
|
107
|
-
@completion_matches.resize(matches.length)
|
108
|
-
matches.each { |w| @completion_matches << w }
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
102
|
def select_next
|
113
103
|
@completion_matches.forward
|
114
104
|
match = @completion_matches.get
|
data/lib/rawline/editor.rb
CHANGED
@@ -213,17 +213,17 @@ module RawLine
|
|
213
213
|
|
214
214
|
Signal.trap("SIGWINCH") do
|
215
215
|
Treefell['editor'].puts "terminal-resized trap=SIGWINCH"
|
216
|
-
@event_loop.add_event name: "terminal-resized"
|
216
|
+
@event_loop.add_event name: "terminal-resized"
|
217
217
|
end
|
218
218
|
|
219
219
|
@event_registry.subscribe("terminal-resized") do
|
220
220
|
width, height = terminal_width, terminal_height
|
221
221
|
Treefell['editor'].puts "terminal-resizing width=#{width} height=#{height}"
|
222
222
|
@renderer.update_dimensions(width: width, height: height)
|
223
|
-
@event_loop.add_event name: "render"
|
223
|
+
@event_loop.add_event name: "render"
|
224
224
|
end
|
225
225
|
|
226
|
-
@event_loop.add_event name: "render"
|
226
|
+
@event_loop.add_event name: "render"
|
227
227
|
@event_loop.start
|
228
228
|
end
|
229
229
|
|
@@ -248,7 +248,7 @@ module RawLine
|
|
248
248
|
if bytes.any?
|
249
249
|
keyboard_input_processor.read_bytes(bytes)
|
250
250
|
end
|
251
|
-
@event_loop.add_event name: 'check_for_keyboard_input'
|
251
|
+
@event_loop.add_event name: 'check_for_keyboard_input'
|
252
252
|
end
|
253
253
|
|
254
254
|
def read_bytes(bytes)
|
@@ -266,20 +266,17 @@ module RawLine
|
|
266
266
|
if @char == @terminal.keys[:enter] || !@char
|
267
267
|
Treefell['editor'].puts "processing line: #{@line.text.inspect}"
|
268
268
|
|
269
|
-
@renderer.
|
270
|
-
process_line
|
269
|
+
@renderer.rollup { process_line }
|
271
270
|
else
|
272
271
|
process_character
|
273
272
|
new_position = @line.position
|
274
273
|
end
|
275
274
|
end
|
276
|
-
ensure
|
277
|
-
@renderer.unpause
|
278
275
|
end
|
279
276
|
end
|
280
277
|
|
281
278
|
def process_line
|
282
|
-
@event_loop.immediately(name: "process_line"
|
279
|
+
@event_loop.immediately(name: "process_line") do
|
283
280
|
add_to_history
|
284
281
|
|
285
282
|
@terminal.snapshot_tty_attrs
|
@@ -288,14 +285,14 @@ module RawLine
|
|
288
285
|
@terminal.move_to_beginning_of_row
|
289
286
|
@terminal.puts
|
290
287
|
end
|
291
|
-
@event_loop.immediately(name: "line_read",
|
292
|
-
@event_loop.immediately(name: "prepare_new_line"
|
288
|
+
@event_loop.immediately(name: "line_read", payload: { line: @line.text.without_ansi.dup })
|
289
|
+
@event_loop.immediately(name: "prepare_new_line") do
|
293
290
|
history.clear_position
|
294
291
|
reset_line
|
295
292
|
move_to_beginning_of_input
|
296
293
|
end
|
297
|
-
@event_loop.immediately(name: "restore_tty_attrs"
|
298
|
-
@event_loop.immediately(name: "render",
|
294
|
+
@event_loop.immediately(name: "restore_tty_attrs") { @terminal.restore_tty_attrs }
|
295
|
+
@event_loop.immediately(name: "render", payload: { reset: true })
|
299
296
|
end
|
300
297
|
|
301
298
|
#
|
@@ -833,7 +830,7 @@ module RawLine
|
|
833
830
|
|
834
831
|
@dom.on(:content_changed) do |*args|
|
835
832
|
Treefell['editor'].puts 'DOM content changed, re-rendering'
|
836
|
-
@event_loop.
|
833
|
+
@event_loop.immediately name: "render"
|
837
834
|
end
|
838
835
|
|
839
836
|
@dom.on(:child_changed) do |*args|
|
@@ -843,7 +840,7 @@ module RawLine
|
|
843
840
|
|
844
841
|
@dom.on :position_changed do |*args|
|
845
842
|
Treefell['editor'].puts 'DOM position changed, rendering cursor'
|
846
|
-
@renderer.
|
843
|
+
@renderer.render_cursor
|
847
844
|
end
|
848
845
|
|
849
846
|
@dom.on :focus_changed do |*args|
|
data/lib/rawline/event_loop.rb
CHANGED
@@ -10,7 +10,6 @@ module RawLine
|
|
10
10
|
|
11
11
|
# event looks like:
|
12
12
|
# * name
|
13
|
-
# * source
|
14
13
|
# * target
|
15
14
|
# * payload
|
16
15
|
def add_event(**event, &blk)
|
@@ -18,6 +17,10 @@ module RawLine
|
|
18
17
|
event[:_event_callback] = blk if blk
|
19
18
|
end
|
20
19
|
|
20
|
+
unless event.has_key?(:_event_caller)
|
21
|
+
event[:_event_caller] = caller[0..5]
|
22
|
+
end
|
23
|
+
|
21
24
|
unless event.has_key?(:_event_id)
|
22
25
|
@counter += 1
|
23
26
|
event[:_event_id] = @counter
|
@@ -39,7 +42,7 @@ module RawLine
|
|
39
42
|
end
|
40
43
|
|
41
44
|
def immediately(**event_kwargs, &blk)
|
42
|
-
dispatch_event(event_kwargs.merge(_event_callback: blk))
|
45
|
+
dispatch_event(event_kwargs.merge(_event_callback: blk, _event_caller: caller[0..5]))
|
43
46
|
end
|
44
47
|
|
45
48
|
def reset
|
@@ -100,7 +103,7 @@ module RawLine
|
|
100
103
|
end
|
101
104
|
|
102
105
|
def default_event
|
103
|
-
{ name: 'default',
|
106
|
+
{ name: 'default', _event_id: -1 }
|
104
107
|
end
|
105
108
|
|
106
109
|
def recur_at(interval_in_ms)
|
@@ -16,124 +16,171 @@ module RawLine
|
|
16
16
|
# The HistoryBuffer class is used to hold the editor and line histories, as well
|
17
17
|
# as word completion matches.
|
18
18
|
#
|
19
|
-
class HistoryBuffer
|
19
|
+
class HistoryBuffer
|
20
|
+
DefaultSize = 1024
|
21
|
+
Infinity = 1.0 / 0
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
# +size+ is used to determine how many history items should be kept.
|
24
|
+
attr_reader :size
|
25
|
+
|
26
|
+
# +duplicates+ is used to determine if the history can house duplicate \
|
27
|
+
# items, consecutively
|
28
|
+
attr_accessor :duplicates
|
29
|
+
|
30
|
+
# +exclude+ can be set to a lambda/proc filter to determine
|
31
|
+
# if an item should be excluded from the history
|
32
|
+
attr_accessor :exclude
|
33
|
+
|
34
|
+
# +cycle+ is used to determine if the the buffer is cyclic
|
35
|
+
attr_accessor :cycle
|
36
|
+
|
37
|
+
# +position+ can be used to set or get the current location of the history
|
38
|
+
attr_accessor :position
|
39
|
+
|
40
|
+
def self.new_with_infinite_size(**kwargs)
|
41
|
+
new(Infinity, **kwargs)
|
42
|
+
end
|
23
43
|
|
24
44
|
#
|
25
45
|
# Create an instance of RawLine::HistoryBuffer.
|
26
46
|
# This method takes an optional block used to override the
|
27
|
-
# following
|
28
|
-
# * <tt>@duplicates</tt> - whether or not duplicate items will be stored in the buffer.
|
29
|
-
# * <tt>@exclude</tt> - a Proc object defining exclusion rules to prevent items from being added to the buffer.
|
30
|
-
# * <tt>@cycle</tt> - Whether or not the buffer is cyclic.
|
31
|
-
#
|
32
|
-
def initialize(size)
|
33
|
-
@duplicates = true
|
34
|
-
@exclude = lambda{|a|}
|
35
|
-
@cycle = false
|
36
|
-
yield self if block_given?
|
37
|
-
@size = size
|
38
|
-
@position = nil
|
39
|
-
end
|
40
|
-
|
47
|
+
# following properties:
|
41
48
|
#
|
42
|
-
#
|
43
|
-
# to
|
49
|
+
# * <tt>duplicates</tt> - whether or not duplicate items will be stored in the buffer.
|
50
|
+
# * <tt>exclude</tt> - a Proc object defining exclusion rules to prevent items from being added to the buffer.
|
51
|
+
# * <tt>cycle</tt> - Whether or not the buffer is cyclic.
|
44
52
|
#
|
45
|
-
def
|
53
|
+
def initialize(size=DefaultSize, cycle: false, duplicates: true, exclude: nil)
|
54
|
+
@history = []
|
55
|
+
@duplicates = duplicates
|
56
|
+
@exclude = exclude || -> (item){ }
|
57
|
+
@cycle = cycle
|
58
|
+
yield self if block_given?
|
59
|
+
@size = size && size > 0 ? size : DefaultSize
|
46
60
|
@position = nil
|
47
61
|
end
|
48
62
|
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
def resize(new_size)
|
53
|
-
if new_size < @size
|
54
|
-
@size-new_size.times { pop }
|
55
|
-
end
|
56
|
-
@size = new_size
|
57
|
-
@position = nil
|
63
|
+
def [](index)
|
64
|
+
@history[index]
|
58
65
|
end
|
59
66
|
|
60
|
-
def
|
61
|
-
|
62
|
-
offset = @position ? length - position : 0
|
63
|
-
reverse[offset..-1].detect.with_index do |item, index|
|
64
|
-
if item.match(regex)
|
65
|
-
@position = length - index - (offset + 1)
|
66
|
-
end
|
67
|
-
end
|
67
|
+
def any?(&blk)
|
68
|
+
@history.any?(&blk)
|
68
69
|
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
#
|
72
|
+
# Decrement <tt>@position</tt>. By default the history will become
|
73
|
+
# positioned at the previous item.
|
74
|
+
#
|
75
|
+
# If <tt>@cycle</tt> is set to true then the history will cycle to the end
|
76
|
+
# when it finds itself at the beginning. If false calling this when
|
77
|
+
# at the beginning will result in the position not changing.
|
78
|
+
#
|
79
|
+
# If a search strategy is assigned then the method <tt>search_backward</tt> will be
|
80
|
+
# called on the search strategy to determine the position. This method is
|
81
|
+
# given any passed in <tt>options</tt> as well as a <tt>:history</tt> option. The
|
82
|
+
# <tt>:history</tt> option will be a reference to self.
|
83
|
+
#
|
84
|
+
def back(options={})
|
85
|
+
return nil unless length > 0
|
86
|
+
|
87
|
+
case @position
|
88
|
+
when nil then
|
89
|
+
@position = length - 1
|
90
|
+
when 0 then
|
91
|
+
@position = length - 1 if @cycle
|
92
|
+
else
|
93
|
+
@position -= 1
|
77
94
|
end
|
78
95
|
end
|
79
96
|
|
80
97
|
#
|
81
|
-
# Clear the content of the buffer and
|
98
|
+
# Clear the content of the buffer and resets <tt>position</tt> to nil.
|
82
99
|
#
|
83
|
-
def
|
100
|
+
def clear
|
84
101
|
@position = nil
|
85
|
-
clear
|
102
|
+
@history.clear
|
86
103
|
end
|
87
104
|
|
88
105
|
#
|
89
|
-
#
|
106
|
+
# Clears the current position on the history object. Useful when deciding
|
107
|
+
# to cancel/reset history navigation.
|
90
108
|
#
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
109
|
+
def clear_position
|
110
|
+
@position = nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def detect(&blk)
|
114
|
+
@history.detect(&blk)
|
115
|
+
end
|
116
|
+
|
117
|
+
def each(&blk)
|
118
|
+
@history.each(&blk)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return true if the history is empty, otherwise false.
|
122
|
+
def empty?
|
123
|
+
@history.empty?
|
95
124
|
end
|
96
125
|
|
97
126
|
#
|
98
127
|
# Return true if <tt>@position</tt> is at the end of the buffer.
|
99
128
|
#
|
100
129
|
def end?
|
101
|
-
@position == length-1
|
130
|
+
@position == length - 1
|
102
131
|
end
|
103
132
|
|
104
133
|
#
|
105
|
-
#
|
134
|
+
# Override equals check. Will be the same as another HistoryBuffer
|
135
|
+
# object with the same history items. Ignores position, exclusion filter,
|
136
|
+
# and so on.
|
106
137
|
#
|
107
|
-
def
|
108
|
-
|
138
|
+
def ==(other)
|
139
|
+
other.is_a?(self.class) &&
|
140
|
+
other.instance_variable_get(:@history) == @history
|
109
141
|
end
|
110
142
|
|
111
143
|
#
|
112
|
-
#
|
113
|
-
#
|
144
|
+
# Finds and returns the first item matching the given text, starting
|
145
|
+
# from the current <tt>position</tt>, moving backwards. The given text
|
146
|
+
# will be successfully matched if it matches any part of a history item.
|
114
147
|
#
|
115
|
-
|
116
|
-
|
117
|
-
|
148
|
+
def find_match_backward(text)
|
149
|
+
regex = to_regex(text)
|
150
|
+
@position = nil if @position == 0 && @cycle
|
151
|
+
offset = @position ? length - position : 0
|
152
|
+
@history.reverse[offset..-1].detect.with_index do |item, index|
|
153
|
+
if item.match(regex)
|
154
|
+
@position = length - index - (offset + 1)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
118
159
|
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
# <tt>:history</tt> option will be a reference to self.
|
160
|
+
# Finds and returns the first item matching the given text, starting
|
161
|
+
# from the current <tt>position</tt>, moving forwards. The given text
|
162
|
+
# will be successfully matched if it matches any part of a history item.
|
123
163
|
#
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
else
|
133
|
-
@position -= 1
|
164
|
+
def find_match_forward(text)
|
165
|
+
regex = to_regex(text)
|
166
|
+
@position = -1 if @position == length - 1 && @cycle
|
167
|
+
offset = @position ? @position + 1 : 0
|
168
|
+
@history[offset..-1].detect.with_index do |item, index|
|
169
|
+
if item.match(regex)
|
170
|
+
@position = index + offset
|
171
|
+
end
|
134
172
|
end
|
135
173
|
end
|
136
174
|
|
175
|
+
def first
|
176
|
+
@history.first
|
177
|
+
end
|
178
|
+
|
179
|
+
def first!
|
180
|
+
@position = 0
|
181
|
+
get
|
182
|
+
end
|
183
|
+
|
137
184
|
#
|
138
185
|
# Increment <tt>@position</tt>. By default the history will become
|
139
186
|
# positioned at the next item.
|
@@ -152,7 +199,7 @@ module RawLine
|
|
152
199
|
|
153
200
|
case @position
|
154
201
|
when nil then
|
155
|
-
@position =
|
202
|
+
@position = length - 1
|
156
203
|
when length-1 then
|
157
204
|
@position = 0 if @cycle
|
158
205
|
else
|
@@ -160,29 +207,110 @@ module RawLine
|
|
160
207
|
end
|
161
208
|
end
|
162
209
|
|
210
|
+
#
|
211
|
+
# Retrieve a copy of the history item at the current <tt>position</tt>.
|
212
|
+
#
|
213
|
+
def get
|
214
|
+
return nil unless length > 0
|
215
|
+
@position = length - 1 unless @position
|
216
|
+
@history.at(@position).dup
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
# Returns the index of the given item if it exists in the history,
|
221
|
+
# otherwise nil.
|
222
|
+
#
|
223
|
+
def index(item)
|
224
|
+
@history.index(item)
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Retrieve a copy of the last history item
|
229
|
+
#
|
230
|
+
def last
|
231
|
+
return nil unless @history.last
|
232
|
+
@history.last.dup
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# Returns the number of items in the HistoryBuffer.
|
237
|
+
#
|
238
|
+
def length
|
239
|
+
@history.length
|
240
|
+
end
|
241
|
+
|
242
|
+
def map(&blk)
|
243
|
+
@history.map(&blk)
|
244
|
+
end
|
245
|
+
|
163
246
|
#
|
164
247
|
# Add a new item to the buffer.
|
165
248
|
#
|
166
249
|
def push(item)
|
167
|
-
if !@duplicates &&
|
250
|
+
if !@duplicates && last == item
|
168
251
|
# skip adding this line
|
169
|
-
return
|
252
|
+
return self
|
170
253
|
end
|
171
254
|
|
172
255
|
unless @exclude.call(item)
|
173
256
|
# Remove the oldest element if size is exceeded
|
174
257
|
if @size <= length
|
175
|
-
reverse!.pop
|
176
|
-
reverse!
|
258
|
+
@history.reverse!.pop
|
259
|
+
@history.reverse!
|
177
260
|
end
|
178
261
|
# Add the new item and reset the position
|
179
|
-
|
262
|
+
@history.push item
|
180
263
|
@position = nil
|
181
264
|
end
|
265
|
+
self
|
182
266
|
end
|
183
|
-
|
184
267
|
alias << push
|
185
268
|
|
269
|
+
#
|
270
|
+
# Replaces the current history with the given <tt>new_history</tt>. The
|
271
|
+
# new_history can be given as an array of history items or a HistoryBuffer
|
272
|
+
# object.
|
273
|
+
def replace(new_history)
|
274
|
+
if new_history.is_a?(HistoryBuffer)
|
275
|
+
new_history = new_history.instance_variable_get(:@history)
|
276
|
+
end
|
277
|
+
@history.replace(new_history)
|
278
|
+
end
|
279
|
+
|
280
|
+
#
|
281
|
+
# Resize the buffer, resetting <tt>position</tt> to nil.
|
282
|
+
#
|
283
|
+
def resize(new_size)
|
284
|
+
if new_size < length
|
285
|
+
(length - new_size).times { @history.shift }
|
286
|
+
end
|
287
|
+
@size = new_size
|
288
|
+
@position = nil
|
289
|
+
self
|
290
|
+
end
|
291
|
+
|
292
|
+
def reverse
|
293
|
+
self.class.new(size).tap do |new_history|
|
294
|
+
@history.reverse.each do |item|
|
295
|
+
new_history << item
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
#
|
301
|
+
# Return true if <tt>position</tt> is at the start of the buffer.
|
302
|
+
#
|
303
|
+
def beginning?
|
304
|
+
@position == 0
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# Returns a copy of the HistoryBuffer as an array.
|
309
|
+
#
|
310
|
+
def to_a
|
311
|
+
@history.dup
|
312
|
+
end
|
313
|
+
|
186
314
|
private
|
187
315
|
|
188
316
|
def to_regex(text)
|