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.
@@ -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
- if @inverted_keymap[bytes]
68
- [bytes]
69
- elsif bytes.length == 1
70
- bytes
71
- elsif bytes.length == 0
72
- fail "Bytes cannot be zero length"
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
- byte_sequence_for(bytes[0..-2])
86
+ results.push *bytes
75
87
  end
88
+ results
76
89
  end
77
90
  end
78
91
  end
@@ -3,7 +3,9 @@ module RawLine
3
3
  def initialize(dom:, output:, width:, height:)
4
4
  @dom = dom
5
5
  @output = output
6
- @renderer = TerminalLayout::TerminalRenderer.new(output: output)
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 unpause
20
- @paused = false
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
- Treefell['editor'].puts " paused"
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['editor'].puts " unpaused"
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
- @renderer.render_cursor(@dom.focused_input_box)
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
@@ -209,10 +209,6 @@ module RawLine
209
209
  terminal_size[1]
210
210
  end
211
211
 
212
- def cursor_position
213
- cursor_position
214
- end
215
-
216
212
  #
217
213
  # Update the terminal escape sequences. This method is called automatically
218
214
  # by RawLine::Editor#bind().
@@ -1,3 +1,3 @@
1
1
  module RawLine
2
- VERSION = "0.6.3"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -3,139 +3,360 @@
3
3
  require_relative "../lib/rawline/history_buffer.rb"
4
4
 
5
5
  describe RawLine::HistoryBuffer do
6
- before do
7
- @history = RawLine::HistoryBuffer.new(5)
8
- end
9
-
10
- it "instantiates an empty array when created" do
11
- expect(@history.length).to eq(0)
12
- end
13
-
14
- it "allows items to be added to the history" do
15
- @history.duplicates = false
16
- @history << "line #1"
17
- @history << "line #2"
18
- @history << "line #3"
19
- @history << "line #2"
20
- expect(@history).to eq(["line #1", "line #2", "line #3"])
21
- @history.duplicates = true
22
- @history << "line #3"
23
- expect(@history).to eq(["line #1", "line #2", "line #3", "line #3"])
24
- @history.exclude = lambda { |i| i.match(/line #[456]/) }
25
- @history << "line #4"
26
- @history << "line #5"
27
- @history << "line #6"
28
- expect(@history).to eq(["line #1", "line #2", "line #3", "line #3"])
29
- end
30
-
31
- it "does not overflow" do
32
- @history << "line #1"
33
- @history << "line #2"
34
- @history << "line #3"
35
- @history << "line #4"
36
- @history << "line #5"
37
- @history << "line #6"
38
- expect(@history.length).to eq(5)
39
- end
40
-
41
- it "allows navigation back and forward" do
42
- @history.back
43
- @history.forward
44
- expect(@history.position).to eq(nil)
45
- @history << "line #1"
46
- @history << "line #2"
47
- @history << "line #3"
48
- @history << "line #4"
49
- @history << "line #5"
50
- @history.back
51
- @history.back
52
- @history.back
53
- @history.back
54
- @history.back
55
- expect(@history.position).to eq(0)
56
- @history.back
57
- expect(@history.position).to eq(0)
58
- @history.forward
59
- expect(@history.position).to eq(1)
60
- @history.forward
61
- @history.forward
62
- @history.forward
63
- @history.forward
64
- expect(@history.position).to eq(4)
65
- @history.forward
66
- expect(@history.position).to eq(4)
67
- @history.cycle = true
68
- @history.forward
69
- @history.forward
70
- expect(@history.position).to eq(1)
71
- end
72
-
73
- it "can retrieve the last element or the element at @position via 'get'" do
74
- expect(@history.get).to eq(nil)
75
- @history << "line #1"
76
- @history << "line #2"
77
- @history << "line #3"
78
- @history << "line #4"
79
- @history << "line #5"
80
- expect(@history.get).to eq("line #5")
81
- @history.back
82
- expect(@history.get).to eq("line #4")
83
- @history.forward
84
- expect(@history.get).to eq("line #5")
85
- end
86
-
87
- it "can be cleared and resized" do
88
- @history << "line #1"
89
- @history << "line #2"
90
- @history << "line #3"
91
- @history << "line #4"
92
- @history << "line #5"
93
- @history.back
94
- @history.back
95
- expect(@history.get).to eq("line #4")
96
- @history.resize(6)
97
- expect(@history.position).to eq(nil)
98
- @history << "line #6"
99
- expect(@history.get).to eq("line #6")
100
- @history.empty
101
- expect(@history).to eq([])
102
- expect(@history.size).to eq(6)
103
- expect(@history.position).to eq(nil)
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
- @history.resize(100)
109
- @history << 'echo foo'
110
- @history << 'echo bar'
111
- @history << 'echo baz'
112
- @history << 'echo food'
113
- @history << 'echo bark'
114
- @history << 'echo bonanza'
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
- @history.clear_position
121
- expect(@history.find_match_backward('bonanza')).to eq 'echo bonanza'
308
+ history.clear_position
309
+ expect(history.find_match_backward('bonanza')).to eq 'echo bonanza'
122
310
 
123
- @history.clear_position
124
- expect(@history.find_match_backward('foo')).to eq 'echo food'
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
- @history.clear_position
129
- expect(@history.find_match_backward('foo')).to eq 'echo food'
130
- $z = true
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 { @history.back }
138
- expect(@history.find_match_backward('bar')).to eq 'echo bar'
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
- @history.clear_position
147
- expect(@history.find_match_forward('bar')).to eq 'echo bar'
148
- expect(@history.position).to eq @history.index('echo bar')
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
- @history.clear_position
151
- expect(@history.find_match_forward('foo')).to eq 'echo foo'
152
- expect(@history.position).to eq @history.index('echo foo')
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
- @history.clear_position
157
- expect(@history.find_match_forward('foo')).to eq 'echo foo'
158
- expect(@history.position).to eq @history.index('echo foo')
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(@history.find_match_forward('foo')).to eq 'echo food'
161
- expect(@history.position).to eq @history.index('echo food')
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 { @history.back }
168
- expect(@history.find_match_forward('bar')).to eq 'echo bark'
169
- expect(@history.position).to eq @history.index('echo bark')
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 'd' do
175
- @history.length.times do
176
- @history.find_match_backward('bar')
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(@history.find_match_forward('bar')).to eq 'echo bark'
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