yap-rawline 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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