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
@@ -49,7 +49,7 @@ module RawLine
|
|
49
49
|
results = []
|
50
50
|
index = 0
|
51
51
|
loop do
|
52
|
-
sequence = byte_sequence_for(bytes[index..-1])
|
52
|
+
sequence = byte_sequence_for(bytes[index..index], bytes[index+1..-1])
|
53
53
|
results.concat sequence
|
54
54
|
index += sequence.first.is_a?(Array) ? sequence.first.length : sequence.length
|
55
55
|
break if index >= bytes.length
|
@@ -63,16 +63,29 @@ module RawLine
|
|
63
63
|
# about a multi-byte sequence like :left_arrow then it will return
|
64
64
|
# [[27,91,68]]. If it does not have a matching byte sequence it will
|
65
65
|
# return a single element array, e.g. [12].
|
66
|
-
def byte_sequence_for(bytes)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
def byte_sequence_for(bytes, more_bytes)
|
67
|
+
results = []
|
68
|
+
more_bytes ||= []
|
69
|
+
longer_sequence_possible = false
|
70
|
+
if more_bytes && more_bytes.any?
|
71
|
+
longer_sequence_possible = @inverted_keymap.detect do |kbytes, _|
|
72
|
+
kbytes[0..bytes.length] == bytes + more_bytes[0..0]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if longer_sequence_possible
|
77
|
+
found_sequences = byte_sequence_for(bytes + [more_bytes.first], more_bytes[1..-1])
|
78
|
+
if found_sequences.any?
|
79
|
+
results.push *found_sequences
|
80
|
+
else
|
81
|
+
results.push *bytes
|
82
|
+
end
|
83
|
+
elsif @inverted_keymap[bytes]
|
84
|
+
results.push bytes
|
73
85
|
else
|
74
|
-
|
86
|
+
results.push *bytes
|
75
87
|
end
|
88
|
+
results
|
76
89
|
end
|
77
90
|
end
|
78
91
|
end
|
data/lib/rawline/renderer.rb
CHANGED
@@ -3,7 +3,9 @@ module RawLine
|
|
3
3
|
def initialize(dom:, output:, width:, height:)
|
4
4
|
@dom = dom
|
5
5
|
@output = output
|
6
|
-
@
|
6
|
+
@paused = false
|
7
|
+
@paused_attempts = []
|
8
|
+
@renderer = TerminalLayout::Renderer.new(output: output)
|
7
9
|
@render_tree = TerminalLayout::RenderTree.new(
|
8
10
|
dom,
|
9
11
|
parent: nil,
|
@@ -12,40 +14,76 @@ module RawLine
|
|
12
14
|
)
|
13
15
|
end
|
14
16
|
|
15
|
-
def pause
|
17
|
+
def pause(&blk)
|
16
18
|
@paused = true
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
20
|
-
@paused
|
21
|
-
if @render_on_unpause_kwargs
|
22
|
-
render(**@render_on_unpause_kwargs)
|
23
|
-
@render_on_unpause = nil
|
24
|
-
end
|
21
|
+
def paused?
|
22
|
+
@paused
|
25
23
|
end
|
26
24
|
|
27
|
-
def render(reset: false)
|
28
|
-
Treefell['editor'].puts %|#{self.class}##{__callee__} reset=#{reset}}|
|
25
|
+
def render(reset: false, &blk)
|
29
26
|
if @paused
|
30
|
-
|
31
|
-
if @render_on_unpause_kwargs && @render_on_unpause_kwargs[:reset]
|
32
|
-
reset = true
|
33
|
-
end
|
34
|
-
@render_on_unpause_kwargs = {reset: reset}
|
27
|
+
@paused_attempts << [:render, { reset: reset }]
|
35
28
|
else
|
36
|
-
Treefell['
|
29
|
+
Treefell['render'].puts %|\nRenderer:#{self.class}##{__callee__} reset=#{reset} caller=#{caller[0..5].join("\n")}}|
|
37
30
|
@render_tree.layout
|
38
31
|
@renderer.render(@render_tree, reset: reset)
|
39
32
|
end
|
40
33
|
end
|
41
34
|
|
42
35
|
def render_cursor
|
43
|
-
@
|
36
|
+
if @paused
|
37
|
+
@paused_attempts << [:render_cursor, {}]
|
38
|
+
else
|
39
|
+
@renderer.render_cursor(@dom.focused_input_box)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def rollup(&blk)
|
44
|
+
if block_given?
|
45
|
+
begin
|
46
|
+
pause
|
47
|
+
blk.call
|
48
|
+
ensure
|
49
|
+
unpause
|
50
|
+
rollup_render_paused_attempts
|
51
|
+
end
|
52
|
+
end
|
44
53
|
end
|
45
54
|
|
46
55
|
def update_dimensions(width:, height:)
|
47
56
|
@render_tree.width = width
|
48
57
|
@render_tree.height = height
|
49
58
|
end
|
59
|
+
|
60
|
+
def unpause
|
61
|
+
@paused = false
|
62
|
+
if @render_on_unpause_kwargs
|
63
|
+
render(**@render_on_unpause_kwargs)
|
64
|
+
@render_on_unpause_kwargs = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def rollup_render_paused_attempts
|
71
|
+
render_attempts = @paused_attempts.select do |attempt|
|
72
|
+
attempt.first == :render
|
73
|
+
end
|
74
|
+
|
75
|
+
# rendering handles render_cursor so only explicitly rerender the
|
76
|
+
# cursor if there were no render attempts
|
77
|
+
if render_attempts.any?
|
78
|
+
reset = render_attempts.any?{ |attempt| attempt.last[:reset] }
|
79
|
+
render reset: reset
|
80
|
+
else
|
81
|
+
if @paused_attempts.any? { |attempt| attempt.first == :render_cursor }
|
82
|
+
render_cursor
|
83
|
+
end
|
84
|
+
end
|
85
|
+
ensure
|
86
|
+
@paused_attempts.clear
|
87
|
+
end
|
50
88
|
end
|
51
89
|
end
|
data/lib/rawline/terminal.rb
CHANGED
data/lib/rawline/version.rb
CHANGED
data/spec/history_buffer_spec.rb
CHANGED
@@ -3,139 +3,360 @@
|
|
3
3
|
require_relative "../lib/rawline/history_buffer.rb"
|
4
4
|
|
5
5
|
describe RawLine::HistoryBuffer do
|
6
|
-
|
7
|
-
|
8
|
-
end
|
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
|
-
|
6
|
+
subject(:history) do
|
7
|
+
RawLine::HistoryBuffer.new(100)
|
8
|
+
end
|
9
|
+
let(:empty_history) { RawLine::HistoryBuffer.new(0) }
|
10
|
+
|
11
|
+
def build_history(*items)
|
12
|
+
described_class.new(items.length).tap do |history|
|
13
|
+
items.each { |item| history << item }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.new_with_infinite_size' do
|
18
|
+
let(:infinity) { 1.0 / 0.0 }
|
19
|
+
it 'returns a new HistoryBuffer of an infinite size' do
|
20
|
+
history = described_class.new_with_infinite_size
|
21
|
+
expect(history).to be_kind_of(described_class)
|
22
|
+
expect(history.size).to eq(infinity)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#[]' do
|
27
|
+
before { history << 'foo' << 'bar' }
|
28
|
+
|
29
|
+
it 'returns the history item at the given index' do
|
30
|
+
expect(history[0]).to eq 'foo'
|
31
|
+
expect(history[1]).to eq 'bar'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns nil when there is no item at the index' do
|
35
|
+
expect(history[2]).to be(nil)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#any?' do
|
40
|
+
it 'returns true when there is at least one item in the history' do
|
41
|
+
history << 'item 1'
|
42
|
+
expect(history.any?).to be(true)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns false otherwise' do
|
46
|
+
expect(history.any?).to be(false)
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'called with a block' do
|
50
|
+
before { history << 'item 1' << 'item 2' }
|
51
|
+
|
52
|
+
it 'returns true the block returns true for any item in the history' do
|
53
|
+
expect(history.any?{ |item| item =~ /1/ }).to be(true)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns false otherwise' do
|
57
|
+
expect(history.any?{ |item| item =~ /z/ }).to be(false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#beginning?' do
|
63
|
+
before do
|
64
|
+
history << 'item 1' << 'item 2'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns false when the position is nil' do
|
68
|
+
history.position = nil
|
69
|
+
expect(history.beginning?).to be(false)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns true when the position is at the beginning' do
|
73
|
+
history.position = 0
|
74
|
+
expect(history.beginning?).to be(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns false otherwise' do
|
78
|
+
history.position = 1
|
79
|
+
expect(history.beginning?).to be(false)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#clear' do
|
84
|
+
before { history << 'item 1' << 'item 2' }
|
85
|
+
|
86
|
+
it 'forgets all history items' do
|
87
|
+
history.clear
|
88
|
+
expect(history).to eq empty_history
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'clears the position' do
|
92
|
+
history.forward
|
93
|
+
expect do
|
94
|
+
history.clear
|
95
|
+
end.to change { history.position }.to nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#clear_position' do
|
100
|
+
before do
|
101
|
+
history << 'item 1' << 'item 2'
|
102
|
+
history.position = 1
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'clears the position' do
|
106
|
+
expect do
|
107
|
+
history.clear_position
|
108
|
+
end.to change { history.position }.to nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#detect' do
|
113
|
+
before { history << 'item 1' << 'item 2' << 'item 22' }
|
114
|
+
it 'returns the first item that matches the given block' do
|
115
|
+
expect(history.detect { |item| item =~ /2/ }).to eq('item 2')
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'returns nil when there is no matching item' do
|
119
|
+
expect(history.detect { |item| item =~ /z/ }).to be(nil)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#duplicates' do
|
124
|
+
it 'allows duplicates by default' do
|
125
|
+
expect(history.duplicates).to be(true)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#each' do
|
130
|
+
before { history << 'foo' << 'bar' << 'baz' }
|
131
|
+
|
132
|
+
it 'iterates over each item in the history, oldest to newest' do
|
133
|
+
results = []
|
134
|
+
history.each do |item|
|
135
|
+
results << item
|
136
|
+
end
|
137
|
+
expect(results).to eq ['foo', 'bar', 'baz']
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#empty?' do
|
142
|
+
it 'returns true when there are not history items' do
|
143
|
+
expect(empty_history.empty?).to be(true)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'returns false when there are history items' do
|
147
|
+
history << 'item 1'
|
148
|
+
expect(history.empty?).to be(false)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#end?' do
|
153
|
+
before do
|
154
|
+
history << 'item 1' << 'item 2'
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns false when the position is nil' do
|
158
|
+
history.position = nil
|
159
|
+
expect(history.end?).to be(false)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'returns true when the position is at the end' do
|
163
|
+
history.position = 1
|
164
|
+
expect(history.end?).to be(true)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'returns false otherwise' do
|
168
|
+
history.position = 0
|
169
|
+
expect(history.end?).to be(false)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#first' do
|
174
|
+
it 'returns the first history item' do
|
175
|
+
history << 'item 1' << 'item 2'
|
176
|
+
expect(history.first).to eq 'item 1'
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'returns nil when there are no history items' do
|
180
|
+
expect(history.first).to be(nil)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#first!' do
|
185
|
+
before do
|
186
|
+
history << 'item 1' << 'item 2' << 'item 3'
|
187
|
+
history.forward
|
188
|
+
history.forward
|
189
|
+
expect(history.beginning?).to be(false)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'sets the position to the beginning of the HistoryBuffer' do
|
193
|
+
history.first!
|
194
|
+
expect(history.beginning?).to be(true)
|
195
|
+
expect(history.position).to eq(0)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns the first history item' do
|
199
|
+
expect(history.first!).to eq('item 1')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '#get' do
|
204
|
+
before do
|
205
|
+
history << 'item 1'
|
206
|
+
history << 'item 2'
|
207
|
+
history << 'item 3'
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'and there is no position' do
|
211
|
+
before { expect(history.position).to be(nil) }
|
212
|
+
|
213
|
+
it 'returns the last item' do
|
214
|
+
expect(history.get).to eq('item 3')
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'sets the position to that of the last item' do
|
218
|
+
expect do
|
219
|
+
history.get
|
220
|
+
end.to change { history.position }.to history.length - 1
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'and there is a position' do
|
225
|
+
before { history.back }
|
226
|
+
|
227
|
+
it 'returns the item at the current position' do
|
228
|
+
expect(history.get).to eq('item 3')
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '#length' do
|
234
|
+
it 'is empty when newly created' do
|
235
|
+
expect(history.empty?).to be(true)
|
236
|
+
expect(history.length).to eq(0)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#<<' do
|
241
|
+
it 'appends the given item to the history' do
|
242
|
+
history << "line #1"
|
243
|
+
history << "line #2"
|
244
|
+
history << "line #2"
|
245
|
+
history << "line #3"
|
246
|
+
expect(history).to eq(build_history('line #1', 'line #2', 'line #2', 'line #3'))
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'and duplicates are disabled' do
|
250
|
+
before do
|
251
|
+
history.duplicates = false
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'does not append a duplicate item' do
|
255
|
+
history << "line #1"
|
256
|
+
history << "line #1"
|
257
|
+
history << "line #1"
|
258
|
+
expect(history).to eq(build_history('line #1'))
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'allows duplicate item that are not next to each other in the history' do
|
262
|
+
history << "line #1"
|
263
|
+
history << "line #2"
|
264
|
+
history << "line #1"
|
265
|
+
expect(history).to eq(build_history('line #1', 'line #2', 'line #1'))
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context 'and there is an exclusion filter' do
|
270
|
+
before do
|
271
|
+
history.exclude = -> (i) { i.match(/line #[13]/) }
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'does not append excluded items' do
|
275
|
+
history << "line #1"
|
276
|
+
history << "line #2"
|
277
|
+
history << "line #3"
|
278
|
+
history << "line #4"
|
279
|
+
expect(history).to eq(build_history('line #2', 'line #4'))
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context 'and the history has a set size' do
|
284
|
+
subject(:history) { described_class.new(size) }
|
285
|
+
let(:size) { 2 }
|
286
|
+
|
287
|
+
it 'does not overflow' do
|
288
|
+
3.times { history << 'apples' }
|
289
|
+
expect(history).to eq(build_history('apples', 'apples'))
|
290
|
+
end
|
291
|
+
end
|
104
292
|
end
|
105
293
|
|
106
294
|
describe 'finding matches in history, forward and backward' do
|
107
295
|
before do
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
296
|
+
history.resize(100)
|
297
|
+
history << 'echo foo'
|
298
|
+
history << 'echo bar'
|
299
|
+
history << 'echo baz'
|
300
|
+
history << 'echo food'
|
301
|
+
history << 'echo bark'
|
302
|
+
history << 'echo bonanza'
|
115
303
|
end
|
116
304
|
|
117
305
|
describe '#find_match_backward' do
|
118
306
|
context 'when the position starts as nil' do
|
119
307
|
it 'finds the first item back that matches' do
|
120
|
-
|
121
|
-
expect(
|
308
|
+
history.clear_position
|
309
|
+
expect(history.find_match_backward('bonanza')).to eq 'echo bonanza'
|
122
310
|
|
123
|
-
|
124
|
-
expect(
|
311
|
+
history.clear_position
|
312
|
+
expect(history.find_match_backward('foo')).to eq 'echo food'
|
125
313
|
end
|
126
314
|
|
127
315
|
it 'can find consecutive matches, skipping unmatched items' do
|
128
|
-
|
129
|
-
expect(
|
130
|
-
|
131
|
-
expect(@history.find_match_backward('foo')).to eq 'echo foo'
|
316
|
+
history.clear_position
|
317
|
+
expect(history.find_match_backward('foo')).to eq 'echo food'
|
318
|
+
expect(history.find_match_backward('foo')).to eq 'echo foo'
|
132
319
|
end
|
133
320
|
end
|
134
321
|
|
135
322
|
context 'when the position starts as non-nil' do
|
136
323
|
it 'finds the first item back that matches from the current position' do
|
137
|
-
3.times {
|
138
|
-
expect(
|
324
|
+
3.times { history.back }
|
325
|
+
expect(history.find_match_backward('bar')).to eq 'echo bar'
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context 'when the HistoryBuffer is not cyclic' do
|
330
|
+
before { history.cycle = false }
|
331
|
+
|
332
|
+
it 'stops searching at the very first item in the HistoryBuffer' do
|
333
|
+
history.find_match_backward('echo foo')
|
334
|
+
expect(history.get).to eq('echo food')
|
335
|
+
|
336
|
+
history.find_match_backward('echo foo')
|
337
|
+
expect(history.get).to eq('echo foo')
|
338
|
+
expect(history.beginning?).to be(true)
|
339
|
+
|
340
|
+
history.find_match_backward('echo foo')
|
341
|
+
expect(history.get).to eq('echo foo')
|
342
|
+
expect(history.beginning?).to be(true)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'when the HistoryBuffer is cyclic' do
|
347
|
+
before { history.cycle = true }
|
348
|
+
|
349
|
+
it 'wraps aorund and searches from the end of hte HistoryBuffer' do
|
350
|
+
history.find_match_backward('echo foo')
|
351
|
+
expect(history.get).to eq('echo food')
|
352
|
+
|
353
|
+
history.find_match_backward('echo foo')
|
354
|
+
expect(history.get).to eq('echo foo')
|
355
|
+
expect(history.beginning?).to be(true)
|
356
|
+
|
357
|
+
history.find_match_backward('echo foo')
|
358
|
+
expect(history.get).to eq('echo food')
|
359
|
+
expect(history.beginning?).to be(false)
|
139
360
|
end
|
140
361
|
end
|
141
362
|
end
|
@@ -143,42 +364,245 @@ describe RawLine::HistoryBuffer do
|
|
143
364
|
describe '#find_match_forward' do
|
144
365
|
context 'when the position starts as nil' do
|
145
366
|
it 'finds the first item from the beginning that matches' do
|
146
|
-
|
147
|
-
expect(
|
148
|
-
expect(
|
367
|
+
history.clear_position
|
368
|
+
expect(history.find_match_forward('bar')).to eq 'echo bar'
|
369
|
+
expect(history.position).to eq history.index('echo bar')
|
149
370
|
|
150
|
-
|
151
|
-
expect(
|
152
|
-
expect(
|
371
|
+
history.clear_position
|
372
|
+
expect(history.find_match_forward('foo')).to eq 'echo foo'
|
373
|
+
expect(history.position).to eq history.index('echo foo')
|
153
374
|
end
|
154
375
|
|
155
376
|
it 'can find consecutive matches, skipping unmatched items' do
|
156
|
-
|
157
|
-
expect(
|
158
|
-
expect(
|
377
|
+
history.clear_position
|
378
|
+
expect(history.find_match_forward('foo')).to eq 'echo foo'
|
379
|
+
expect(history.position).to eq history.index('echo foo')
|
159
380
|
|
160
|
-
expect(
|
161
|
-
expect(
|
381
|
+
expect(history.find_match_forward('foo')).to eq 'echo food'
|
382
|
+
expect(history.position).to eq history.index('echo food')
|
162
383
|
end
|
163
384
|
end
|
164
385
|
|
165
386
|
context 'when the position starts as non-nil' do
|
166
387
|
it 'finds the first item back that matches from the current position' do
|
167
|
-
3.times {
|
168
|
-
expect(
|
169
|
-
expect(
|
388
|
+
3.times { history.back }
|
389
|
+
expect(history.find_match_forward('bar')).to eq 'echo bark'
|
390
|
+
expect(history.position).to eq history.index('echo bark')
|
170
391
|
end
|
171
392
|
end
|
172
393
|
|
173
394
|
context 'when its gone all the way back, and we want to go forward' do
|
174
|
-
it '
|
175
|
-
|
176
|
-
|
395
|
+
it 'finds the first item forward that matches from the current position' do
|
396
|
+
history.length.times do
|
397
|
+
history.find_match_backward('bar')
|
177
398
|
end
|
178
|
-
expect(
|
399
|
+
expect(history.find_match_forward('bar')).to eq 'echo bark'
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
context 'when the HistoryBuffer is not cyclic' do
|
404
|
+
before do
|
405
|
+
history.cycle = false
|
406
|
+
history.position = 0
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'stops searching at the very last item matched in the HistoryBuffer' do
|
410
|
+
history.find_match_forward('echo b')
|
411
|
+
history.find_match_forward('echo b')
|
412
|
+
history.find_match_forward('echo b')
|
413
|
+
expect(history.get).to eq('echo bark')
|
414
|
+
|
415
|
+
history.find_match_forward('echo b')
|
416
|
+
expect(history.get).to eq('echo bonanza')
|
417
|
+
expect(history.end?).to be(true)
|
418
|
+
|
419
|
+
history.find_match_forward('echo b')
|
420
|
+
expect(history.end?).to be(true)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
context 'when the HistoryBuffer is cyclic' do
|
425
|
+
before { history.cycle = true }
|
426
|
+
|
427
|
+
it 'wraps aorund and searches from the end of hte HistoryBuffer' do
|
428
|
+
history.find_match_forward('echo b')
|
429
|
+
history.find_match_forward('echo b')
|
430
|
+
history.find_match_forward('echo b')
|
431
|
+
expect(history.get).to eq('echo bark')
|
432
|
+
history.find_match_forward('echo b')
|
433
|
+
expect(history.get).to eq('echo bonanza')
|
434
|
+
expect(history.end?).to be(true)
|
435
|
+
|
436
|
+
history.find_match_forward('echo bar')
|
437
|
+
expect(history.end?).to be(false)
|
179
438
|
end
|
180
439
|
end
|
440
|
+
|
181
441
|
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe 'navigating the history' do
|
445
|
+
before do
|
446
|
+
history << 'item 1'
|
447
|
+
history << 'item 2'
|
448
|
+
history << 'item 3'
|
449
|
+
history << 'item 4'
|
450
|
+
history << 'item 5'
|
182
451
|
|
452
|
+
expect(history.position).to be(nil)
|
453
|
+
end
|
454
|
+
|
455
|
+
describe '#back' do
|
456
|
+
it 'allows you to move back thru the history' do
|
457
|
+
history.back
|
458
|
+
expect(history.position).to be(4)
|
459
|
+
history.back
|
460
|
+
history.back
|
461
|
+
expect(history.position).to be(2)
|
462
|
+
end
|
463
|
+
|
464
|
+
it 'does not allow you to go past the very first item' do
|
465
|
+
100.times { history.back }
|
466
|
+
expect(history.position).to be(0)
|
467
|
+
end
|
468
|
+
|
469
|
+
context 'and the history is set to cycle' do
|
470
|
+
before { history.cycle = true }
|
471
|
+
|
472
|
+
it 'wraps around to the very last item when you move beyond the first item' do
|
473
|
+
history.position = 0
|
474
|
+
expect do
|
475
|
+
history.back
|
476
|
+
end.to change { history.end? }.to true
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
describe '#forward' do
|
482
|
+
context 'and you are starting with no position' do
|
483
|
+
before { expect(history.position).to be(nil) }
|
484
|
+
|
485
|
+
it 'puts you at the position of the last item' do
|
486
|
+
expect do
|
487
|
+
history.back
|
488
|
+
end.to change { history.end? }.to true
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
context 'and you are starting from a known position' do
|
493
|
+
before do
|
494
|
+
history.length.times { history.back }
|
495
|
+
expect(history.position).to be(0)
|
496
|
+
end
|
497
|
+
|
498
|
+
it 'allows you to move forward thru the history' do
|
499
|
+
history.forward
|
500
|
+
expect(history.position).to be(1)
|
501
|
+
history.forward
|
502
|
+
history.forward
|
503
|
+
expect(history.position).to be(3)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
it 'does not allow you to go past the very last item (by default)' do
|
508
|
+
100.times { history.forward }
|
509
|
+
expect do
|
510
|
+
history.forward
|
511
|
+
end.to_not change { history.end? }.from true
|
512
|
+
end
|
513
|
+
|
514
|
+
context 'and the history is set to cycle' do
|
515
|
+
before { history.cycle = true }
|
516
|
+
|
517
|
+
it 'wraps around to the very first item when you move beyond the last item' do
|
518
|
+
history.position = history.length - 2
|
519
|
+
history.forward
|
520
|
+
expect do
|
521
|
+
history.forward
|
522
|
+
end.to change { history.beginning? }.to true
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
describe '#map' do
|
529
|
+
before { history << 'foo' << 'bar' << 'baz' }
|
530
|
+
|
531
|
+
it 'iterates over each item in the history, oldest to newest' do
|
532
|
+
results = history.map do |item|
|
533
|
+
"echo #{item}"
|
534
|
+
end
|
535
|
+
expect(results).to eq ['echo foo', 'echo bar', 'echo baz']
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
describe '#replace' do
|
540
|
+
before { history << 'item 1' << 'item 2' }
|
541
|
+
|
542
|
+
it 'replaces the current history items with an array of history items' do
|
543
|
+
history.replace(['foo', 'bar'])
|
544
|
+
expect(history).to eq build_history('foo', 'bar')
|
545
|
+
end
|
546
|
+
|
547
|
+
it 'replaces the current history items with a given HistoryBuffer' do
|
548
|
+
new_history = build_history('foo', 'bar')
|
549
|
+
history.replace(new_history)
|
550
|
+
expect(history).to eq new_history
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
describe '#resize' do
|
555
|
+
before do
|
556
|
+
history << 'item 1' << 'item 2' << 'item 3' << 'item 4'
|
557
|
+
end
|
558
|
+
|
559
|
+
it 'resizes the history' do
|
560
|
+
expect do
|
561
|
+
history.resize(2)
|
562
|
+
end.to change { history.size }.to 2
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'forgets any history items older than the new size allows' do
|
566
|
+
history.resize(2)
|
567
|
+
expect(history).to eq build_history('item 3', 'item 4')
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
describe '#reverse' do
|
572
|
+
before { history << 'item 1' << 'item 2' }
|
573
|
+
|
574
|
+
it 'returns a new HistoryBuffer in reverse order' do
|
575
|
+
reversed_history = history.reverse
|
576
|
+
expect(reversed_history).to eq build_history('item 2', 'item 1');
|
577
|
+
end
|
578
|
+
|
579
|
+
it 'does not modify the original HistoryBuffer' do
|
580
|
+
reversed_history = history.reverse
|
581
|
+
expect(history).to eq build_history('item 1', 'item 2')
|
582
|
+
end
|
183
583
|
end
|
584
|
+
|
585
|
+
describe '#size' do
|
586
|
+
it 'returns the size of the HistoryBuffer (regardless of history items)' do
|
587
|
+
expect(described_class.new(4).size).to eq(4)
|
588
|
+
expect(described_class.new(128).size).to eq(128)
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'defaults to 1024 when provided as 0 or nil' do
|
592
|
+
expect(described_class.new(0).size).to eq(1024)
|
593
|
+
expect(described_class.new(nil).size).to eq(1024)
|
594
|
+
end
|
595
|
+
|
596
|
+
it 'defaults to 1024 when not provided' do
|
597
|
+
expect(described_class.new.size).to eq(1024)
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
describe '#to_a' do
|
602
|
+
it 'returns the history as an array' do
|
603
|
+
history << 'item 1' << 'item 2'
|
604
|
+
expect(history.to_a).to eq ['item 1', 'item 2']
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
184
608
|
end
|