syntax_suggest 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20590130dde6c0a40a17b7d01ad9fca063c7044c83d7f04609f9d75df564f9ce
4
- data.tar.gz: 73bed388644be5a7a71fe5ab892c8925d50f9dbcad5d918ca07512ffd85188af
3
+ metadata.gz: 0aa200b221c59254250acfa3343365ead0a7e8a0e699aec254e248a880bf4084
4
+ data.tar.gz: e343f13b8cfb3091b7e6f2ee97575fbe7e8e9b24d0f36168524a098e5d596098
5
5
  SHA512:
6
- metadata.gz: 42a28115779d75b9ff89e58b2a26a9c4807e834a91f8671ffdb2725f9dea954b017310c3b67a9bfe7b97ea6bdbb28e84c59f8bf5c951b0692e99f74ab3e9a5a3
7
- data.tar.gz: 51c04529ff19c2181b7f025ff04165f436afaf22e7c9daffb23767176b48b8216006a9fd669b96a435abd8c9886b046224212e9f6c50435008a8ef677477f771
6
+ metadata.gz: 5b5fe319b21f699251eb9d2a1580808fd8b454c8c5d04f1e0ac63619898c429726579596e2f182d98299f2850387ee34b245b6e4a9cfcfb17a1c76e160000a01
7
+ data.tar.gz: f1181078cb45c2c333fc96ff04efd0513b0da4088623ce9899eeda0ada7ba342d7a146bde872515678005cad6f200fe6116f4d33f600cd98f4aa520a13ccda21
@@ -13,7 +13,7 @@ jobs:
13
13
  !contains(github.event.pull_request.body, '[skip ci]') &&
14
14
  !contains(github.event.pull_request.labels.*.name, 'skip changelog')
15
15
  steps:
16
- - uses: actions/checkout@v3.3.0
16
+ - uses: actions/checkout@v3.5.2
17
17
  - name: Check that CHANGELOG is touched
18
18
  run: |
19
19
  git fetch origin ${{ github.base_ref }} --depth 1 && \
@@ -9,7 +9,7 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
11
  - name: Checkout code
12
- uses: actions/checkout@v3.3.0
12
+ uses: actions/checkout@v3.5.2
13
13
  - name: Set up Ruby
14
14
  uses: ruby/setup-ruby@v1
15
15
  with:
@@ -33,7 +33,7 @@ jobs:
33
33
  - head
34
34
  steps:
35
35
  - name: Checkout code
36
- uses: actions/checkout@v3.3.0
36
+ uses: actions/checkout@v3.5.2
37
37
  - name: Set up Ruby
38
38
  uses: ruby/setup-ruby@v1
39
39
  with:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 1.1.0
4
+
5
+ - Handle if/else with comment or empty line in branch (https://github.com/ruby/syntax_suggest/pull/193)
6
+ - Use `SYNTAX_SUGGEST_DEBUG` instead of `DEBUG` env var value in timeout warning message (https://github.com/ruby/syntax_suggest/pull/194)
7
+ - Reduce line output for increased clarity (https://github.com/ruby/syntax_suggest/pull/190)
8
+
9
+ ## 1.0.4
10
+
11
+ - Fix rendering a file without a newline ending (https://github.com/ruby/syntax_suggest/pull/182)
12
+
3
13
  ## 1.0.3
4
14
 
5
15
  - Output improvement: Handle methods with only newlines or comments in them (https://github.com/ruby/syntax_suggest/pull/179)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_suggest (1.0.3)
4
+ syntax_suggest (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -186,6 +186,7 @@ Because `syntax_suggest` is a default gem you can get conflicts when working on
186
186
 
187
187
  There are some binstubs that already have this done for you. Instead of running `bundle exec rake` you can run `bin/rake`. Binstubs provided:
188
188
 
189
+ - `bin/bundle`
189
190
  - `bin/rake`
190
191
  - `bin/rspec`
191
192
 
data/bin/bundle ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Make sure syntax_suggest default gem is not loaded first
5
+ RUBYOPT="${RUBYOPT-} --disable=syntax_suggest"
6
+ RUBYOPT="$RUBYOPT" bundle "$@"
@@ -78,7 +78,7 @@ module SyntaxSuggest
78
78
  code_lines: search.code_lines
79
79
  ).call
80
80
  rescue Timeout::Error => e
81
- io.puts "Search timed out SYNTAX_SUGGEST_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
81
+ io.puts "Search timed out SYNTAX_SUGGEST_TIMEOUT=#{timeout}, run with SYNTAX_SUGGEST_DEBUG=1 for more info"
82
82
  io.puts e.backtrace.first(3).join($/)
83
83
  end
84
84
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "scan_history"
4
+
3
5
  module SyntaxSuggest
4
6
  # This class is useful for exploring contents before and after
5
7
  # a block
@@ -24,22 +26,17 @@ module SyntaxSuggest
24
26
  # puts scan.before_index # => 0
25
27
  # puts scan.after_index # => 3
26
28
  #
27
- # Contents can also be filtered using AroundBlockScan#skip
28
- #
29
- # To grab the next surrounding indentation use AroundBlockScan#scan_adjacent_indent
30
29
  class AroundBlockScan
31
30
  def initialize(code_lines:, block:)
32
31
  @code_lines = code_lines
33
- @orig_before_index = block.lines.first.index
34
- @orig_after_index = block.lines.last.index
35
32
  @orig_indent = block.current_indent
36
- @skip_array = []
37
- @after_array = []
38
- @before_array = []
39
- @stop_after_kw = false
40
33
 
41
- @force_add_hidden = false
34
+ @stop_after_kw = false
42
35
  @force_add_empty = false
36
+ @force_add_hidden = false
37
+ @target_indent = nil
38
+
39
+ @scanner = ScanHistory.new(code_lines: code_lines, block: block)
43
40
  end
44
41
 
45
42
  # When using this flag, `scan_while` will
@@ -89,150 +86,35 @@ module SyntaxSuggest
89
86
  # stopping if we've found a keyword/end mis-match in one direction
90
87
  # or the other.
91
88
  def scan_while
92
- stop_next = false
93
- kw_count = 0
94
- end_count = 0
95
- index = before_lines.reverse_each.take_while do |line|
96
- next false if stop_next
97
- next true if @force_add_hidden && line.hidden?
98
- next true if @force_add_empty && line.empty?
99
-
100
- kw_count += 1 if line.is_kw?
101
- end_count += 1 if line.is_end?
102
- if @stop_after_kw && kw_count > end_count
103
- stop_next = true
104
- end
105
-
106
- yield line
107
- end.last&.index
108
-
109
- if index && index < before_index
110
- @before_index = index
111
- end
112
-
113
- stop_next = false
114
- kw_count = 0
115
- end_count = 0
116
- index = after_lines.take_while do |line|
117
- next false if stop_next
118
- next true if @force_add_hidden && line.hidden?
119
- next true if @force_add_empty && line.empty?
120
-
121
- kw_count += 1 if line.is_kw?
122
- end_count += 1 if line.is_end?
123
- if @stop_after_kw && end_count > kw_count
124
- stop_next = true
125
- end
126
-
127
- yield line
128
- end.last&.index
129
-
130
- if index && index > after_index
131
- @after_index = index
132
- end
133
- self
134
- end
135
-
136
- # Shows surrounding kw/end pairs
137
- #
138
- # The purpose of showing these extra pairs is due to cases
139
- # of ambiguity when only one visible line is matched.
140
- #
141
- # For example:
142
- #
143
- # 1 class Dog
144
- # 2 def bark
145
- # 4 def eat
146
- # 5 end
147
- # 6 end
148
- #
149
- # In this case either line 2 could be missing an `end` or
150
- # line 4 was an extra line added by mistake (it happens).
151
- #
152
- # When we detect the above problem it shows the issue
153
- # as only being on line 2
154
- #
155
- # 2 def bark
156
- #
157
- # Showing "neighbor" keyword pairs gives extra context:
158
- #
159
- # 2 def bark
160
- # 4 def eat
161
- # 5 end
162
- #
163
- def capture_neighbor_context
164
- lines = []
165
- kw_count = 0
166
- end_count = 0
167
- before_lines.reverse_each do |line|
168
- next if line.empty?
169
- break if line.indent < @orig_indent
170
- next if line.indent != @orig_indent
171
-
172
- kw_count += 1 if line.is_kw?
173
- end_count += 1 if line.is_end?
174
- if kw_count != 0 && kw_count == end_count
175
- lines << line
176
- break
177
- end
178
-
179
- lines << line
180
- end
89
+ stop_next_up = false
90
+ stop_next_down = false
181
91
 
182
- lines.reverse!
92
+ @scanner.scan(
93
+ up: ->(line, kw_count, end_count) {
94
+ next false if stop_next_up
95
+ next true if @force_add_hidden && line.hidden?
96
+ next true if @force_add_empty && line.empty?
183
97
 
184
- kw_count = 0
185
- end_count = 0
186
- after_lines.each do |line|
187
- next if line.empty?
188
- break if line.indent < @orig_indent
189
- next if line.indent != @orig_indent
98
+ if @stop_after_kw && kw_count > end_count
99
+ stop_next_up = true
100
+ end
190
101
 
191
- kw_count += 1 if line.is_kw?
192
- end_count += 1 if line.is_end?
193
- if kw_count != 0 && kw_count == end_count
194
- lines << line
195
- break
196
- end
197
-
198
- lines << line
199
- end
102
+ yield line
103
+ },
104
+ down: ->(line, kw_count, end_count) {
105
+ next false if stop_next_down
106
+ next true if @force_add_hidden && line.hidden?
107
+ next true if @force_add_empty && line.empty?
200
108
 
201
- lines
202
- end
109
+ if @stop_after_kw && end_count > kw_count
110
+ stop_next_down = true
111
+ end
203
112
 
204
- # Shows the context around code provided by "falling" indentation
205
- #
206
- # Converts:
207
- #
208
- # it "foo" do
209
- #
210
- # into:
211
- #
212
- # class OH
213
- # def hello
214
- # it "foo" do
215
- # end
216
- # end
217
- #
218
- def on_falling_indent
219
- last_indent = @orig_indent
220
- before_lines.reverse_each do |line|
221
- next if line.empty?
222
- if line.indent < last_indent
223
113
  yield line
224
- last_indent = line.indent
225
- end
226
- end
114
+ }
115
+ )
227
116
 
228
- last_indent = @orig_indent
229
- after_lines.each do |line|
230
- next if line.empty?
231
- if line.indent < last_indent
232
- yield line
233
- last_indent = line.indent
234
- end
235
- end
117
+ self
236
118
  end
237
119
 
238
120
  # Scanning is intentionally conservative because
@@ -266,39 +148,46 @@ module SyntaxSuggest
266
148
 
267
149
  return self if kw_count == end_count # nothing to balance
268
150
 
269
- # More ends than keywords, check if we can balance expanding up
270
- if (end_count - kw_count) == 1 && next_up
271
- return self unless next_up.is_kw?
272
- return self unless next_up.indent >= @orig_indent
273
-
274
- @before_index = next_up.index
151
+ @scanner.commit_if_changed # Rollback point if we don't find anything to optimize
275
152
 
276
- # More keywords than ends, check if we can balance by expanding down
277
- elsif (kw_count - end_count) == 1 && next_down
278
- return self unless next_down.is_end?
279
- return self unless next_down.indent >= @orig_indent
153
+ # Try to eat up empty lines
154
+ @scanner.scan(
155
+ up: ->(line, _, _) { line.hidden? || line.empty? },
156
+ down: ->(line, _, _) { line.hidden? || line.empty? }
157
+ )
280
158
 
281
- @after_index = next_down.index
159
+ # More ends than keywords, check if we can balance expanding up
160
+ next_up = @scanner.next_up
161
+ next_down = @scanner.next_down
162
+ case end_count - kw_count
163
+ when 1
164
+ if next_up&.is_kw? && next_up.indent >= @target_indent
165
+ @scanner.scan(
166
+ up: ->(line, _, _) { line == next_up },
167
+ down: ->(line, _, _) { false }
168
+ )
169
+ @scanner.commit_if_changed
170
+ end
171
+ when -1
172
+ if next_down&.is_end? && next_down.indent >= @target_indent
173
+ @scanner.scan(
174
+ up: ->(line, _, _) { false },
175
+ down: ->(line, _, _) { line == next_down }
176
+ )
177
+ @scanner.commit_if_changed
178
+ end
282
179
  end
180
+ # Rollback any uncommitted changes
181
+ @scanner.stash_changes
182
+
283
183
  self
284
184
  end
285
185
 
286
186
  # Finds code lines at the same or greater indentation and adds them
287
187
  # to the block
288
188
  def scan_neighbors_not_empty
289
- scan_while { |line| line.not_empty? && line.indent >= @orig_indent }
290
- end
291
-
292
- # Returns the next line to be scanned above the current block.
293
- # Returns `nil` if at the top of the document already
294
- def next_up
295
- @code_lines[before_index.pred]
296
- end
297
-
298
- # Returns the next line to be scanned below the current block.
299
- # Returns `nil` if at the bottom of the document already
300
- def next_down
301
- @code_lines[after_index.next]
189
+ @target_indent = @orig_indent
190
+ scan_while { |line| line.not_empty? && line.indent >= @target_indent }
302
191
  end
303
192
 
304
193
  # Scan blocks based on indentation of next line above/below block
@@ -310,30 +199,13 @@ module SyntaxSuggest
310
199
  # the `def/end` lines surrounding a method.
311
200
  def scan_adjacent_indent
312
201
  before_after_indent = []
313
- before_after_indent << (next_up&.indent || 0)
314
- before_after_indent << (next_down&.indent || 0)
315
202
 
316
- indent = before_after_indent.min
317
- scan_while { |line| line.not_empty? && line.indent >= indent }
203
+ before_after_indent << (@scanner.next_up&.indent || 0)
204
+ before_after_indent << (@scanner.next_down&.indent || 0)
318
205
 
319
- self
320
- end
206
+ @target_indent = before_after_indent.min
207
+ scan_while { |line| line.not_empty? && line.indent >= @target_indent }
321
208
 
322
- # TODO: Doc or delete
323
- #
324
- # I don't remember why this is needed, but it's called in code_context.
325
- # It's related to the implementation of `capture_neighbor_context` somehow
326
- # and that display improvement is only triggered when there's one visible line
327
- #
328
- # I think the primary purpose is to not include the current line in the
329
- # logic evaluation of `capture_neighbor_context`. If that's true, then
330
- # we should fix that method to handle this logic instead of only using
331
- # it in one place and together.
332
- def start_at_next_line
333
- before_index
334
- after_index
335
- @before_index -= 1
336
- @after_index += 1
337
209
  self
338
210
  end
339
211
 
@@ -349,29 +221,12 @@ module SyntaxSuggest
349
221
  # Returns the lines matched by the current scan as an
350
222
  # array of CodeLines
351
223
  def lines
352
- @code_lines[before_index..after_index]
353
- end
354
-
355
- # Gives the index of the first line currently scanned
356
- def before_index
357
- @before_index ||= @orig_before_index
358
- end
359
-
360
- # Gives the index of the last line currently scanned
361
- def after_index
362
- @after_index ||= @orig_after_index
363
- end
364
-
365
- # Returns an array of all the CodeLines that exist before
366
- # the currently scanned block
367
- private def before_lines
368
- @code_lines[0...before_index] || []
224
+ @scanner.lines
369
225
  end
370
226
 
371
- # Returns an array of all the CodeLines that exist after
372
- # the currently scanned block
373
- private def after_lines
374
- @code_lines[after_index.next..-1] || []
227
+ # Managable rspec errors
228
+ def inspect
229
+ "#<#{self.class}:0x0000123843lol >"
375
230
  end
376
231
  end
377
232
  end
@@ -61,11 +61,14 @@ module SyntaxSuggest
61
61
  # they can expand to capture more code up and down). It does this conservatively
62
62
  # as there's no undo (currently).
63
63
  def expand_indent(block)
64
- AroundBlockScan.new(code_lines: @code_lines, block: block)
64
+ now = AroundBlockScan.new(code_lines: @code_lines, block: block)
65
65
  .force_add_hidden
66
66
  .stop_after_kw
67
67
  .scan_adjacent_indent
68
- .code_block
68
+
69
+ now.lookahead_balance_one_line
70
+
71
+ now.code_block
69
72
  end
70
73
 
71
74
  # A neighbor is code that is at or above the current indent line.
@@ -125,17 +128,20 @@ module SyntaxSuggest
125
128
  #
126
129
  # We try to resolve this edge case with `lookahead_balance_one_line` below.
127
130
  def expand_neighbors(block)
128
- neighbors = AroundBlockScan.new(code_lines: @code_lines, block: block)
131
+ now = AroundBlockScan.new(code_lines: @code_lines, block: block)
132
+
133
+ # Initial scan
134
+ now
129
135
  .force_add_hidden
130
136
  .stop_after_kw
131
137
  .scan_neighbors_not_empty
132
138
 
133
139
  # Slurp up empties
134
- with_empties = neighbors
140
+ now
135
141
  .scan_while { |line| line.empty? }
136
142
 
137
143
  # If next line is kw and it will balance us, take it
138
- expanded_lines = with_empties
144
+ expanded_lines = now
139
145
  .lookahead_balance_one_line
140
146
  .lines
141
147
 
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxSuggest
4
+ module Capture
5
+ # Shows surrounding kw/end pairs
6
+ #
7
+ # The purpose of showing these extra pairs is due to cases
8
+ # of ambiguity when only one visible line is matched.
9
+ #
10
+ # For example:
11
+ #
12
+ # 1 class Dog
13
+ # 2 def bark
14
+ # 4 def eat
15
+ # 5 end
16
+ # 6 end
17
+ #
18
+ # In this case either line 2 could be missing an `end` or
19
+ # line 4 was an extra line added by mistake (it happens).
20
+ #
21
+ # When we detect the above problem it shows the issue
22
+ # as only being on line 2
23
+ #
24
+ # 2 def bark
25
+ #
26
+ # Showing "neighbor" keyword pairs gives extra context:
27
+ #
28
+ # 2 def bark
29
+ # 4 def eat
30
+ # 5 end
31
+ #
32
+ #
33
+ # Example:
34
+ #
35
+ # lines = BeforeAfterKeywordEnds.new(
36
+ # block: block,
37
+ # code_lines: code_lines
38
+ # ).call()
39
+ #
40
+ class BeforeAfterKeywordEnds
41
+ def initialize(code_lines:, block:)
42
+ @scanner = ScanHistory.new(code_lines: code_lines, block: block)
43
+ @original_indent = block.current_indent
44
+ end
45
+
46
+ def call
47
+ lines = []
48
+
49
+ @scanner.scan(
50
+ up: ->(line, kw_count, end_count) {
51
+ next true if line.empty?
52
+ break if line.indent < @original_indent
53
+ next true if line.indent != @original_indent
54
+
55
+ # If we're going up and have one complete kw/end pair, stop
56
+ if kw_count != 0 && kw_count == end_count
57
+ lines << line
58
+ break
59
+ end
60
+
61
+ lines << line if line.is_kw? || line.is_end?
62
+ true
63
+ },
64
+ down: ->(line, kw_count, end_count) {
65
+ next true if line.empty?
66
+ break if line.indent < @original_indent
67
+ next true if line.indent != @original_indent
68
+
69
+ # if we're going down and have one complete kw/end pair,stop
70
+ if kw_count != 0 && kw_count == end_count
71
+ lines << line
72
+ break
73
+ end
74
+
75
+ lines << line if line.is_kw? || line.is_end?
76
+ true
77
+ }
78
+ )
79
+ @scanner.stash_changes
80
+
81
+ lines
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxSuggest
4
+ module Capture
5
+ # Shows the context around code provided by "falling" indentation
6
+ #
7
+ # If this is the original code lines:
8
+ #
9
+ # class OH
10
+ # def hello
11
+ # it "foo" do
12
+ # end
13
+ # end
14
+ #
15
+ # And this is the line that is captured
16
+ #
17
+ # it "foo" do
18
+ #
19
+ # It will yield its surrounding context:
20
+ #
21
+ # class OH
22
+ # def hello
23
+ # end
24
+ # end
25
+ #
26
+ # Example:
27
+ #
28
+ # FallingIndentLines.new(
29
+ # block: block,
30
+ # code_lines: @code_lines
31
+ # ).call do |line|
32
+ # @lines_to_output << line
33
+ # end
34
+ #
35
+ class FallingIndentLines
36
+ def initialize(code_lines:, block:)
37
+ @lines = nil
38
+ @scanner = ScanHistory.new(code_lines: code_lines, block: block)
39
+ @original_indent = block.current_indent
40
+ end
41
+
42
+ def call(&yieldable)
43
+ last_indent_up = @original_indent
44
+ last_indent_down = @original_indent
45
+
46
+ @scanner.commit_if_changed
47
+ @scanner.scan(
48
+ up: ->(line, _, _) {
49
+ next true if line.empty?
50
+
51
+ if line.indent < last_indent_up
52
+ yieldable.call(line)
53
+ last_indent_up = line.indent
54
+ end
55
+ true
56
+ },
57
+ down: ->(line, _, _) {
58
+ next true if line.empty?
59
+
60
+ if line.indent < last_indent_down
61
+ yieldable.call(line)
62
+ last_indent_down = line.indent
63
+ end
64
+ true
65
+ }
66
+ )
67
+ @scanner.stash_changes
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module SyntaxSuggest
4
+ module Capture
5
+ end
6
+ end
7
+
8
+ require_relative "capture/falling_indent_lines"
9
+ require_relative "capture/before_after_keyword_ends"
10
+
3
11
  module SyntaxSuggest
4
12
  # Turns a "invalid block(s)" into useful context
5
13
  #
@@ -55,6 +63,10 @@ module SyntaxSuggest
55
63
  capture_falling_indent(block)
56
64
  end
57
65
 
66
+ sorted_lines
67
+ end
68
+
69
+ def sorted_lines
58
70
  @lines_to_output.select!(&:not_empty?)
59
71
  @lines_to_output.uniq!
60
72
  @lines_to_output.sort!
@@ -77,10 +89,10 @@ module SyntaxSuggest
77
89
  # end
78
90
  #
79
91
  def capture_falling_indent(block)
80
- AroundBlockScan.new(
92
+ Capture::FallingIndentLines.new(
81
93
  block: block,
82
94
  code_lines: @code_lines
83
- ).on_falling_indent do |line|
95
+ ).call do |line|
84
96
  @lines_to_output << line
85
97
  end
86
98
  end
@@ -115,9 +127,10 @@ module SyntaxSuggest
115
127
  def capture_before_after_kws(block)
116
128
  return unless block.visible_lines.count == 1
117
129
 
118
- around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
119
- .start_at_next_line
120
- .capture_neighbor_context
130
+ around_lines = Capture::BeforeAfterKeywordEnds.new(
131
+ code_lines: @code_lines,
132
+ block: block
133
+ ).call
121
134
 
122
135
  around_lines -= block.lines
123
136
 
@@ -45,6 +45,8 @@ if SyntaxError.method_defined?(:detailed_message)
45
45
  )
46
46
  annotation = io.string
47
47
 
48
+ annotation += "\n" unless annotation.end_with?("\n")
49
+
48
50
  annotation + message
49
51
  else
50
52
  message
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxSuggest
4
+ # Scans up/down from the given block
5
+ #
6
+ # You can try out a change, stash it, or commit it to save for later
7
+ #
8
+ # Example:
9
+ #
10
+ # scanner = ScanHistory.new(code_lines: code_lines, block: block)
11
+ # scanner.scan(
12
+ # up: ->(_, _, _) { true },
13
+ # down: ->(_, _, _) { true }
14
+ # )
15
+ # scanner.changed? # => true
16
+ # expect(scanner.lines).to eq(code_lines)
17
+ #
18
+ # scanner.stash_changes
19
+ #
20
+ # expect(scanner.lines).to_not eq(code_lines)
21
+ class ScanHistory
22
+ attr_reader :before_index, :after_index
23
+
24
+ def initialize(code_lines:, block:)
25
+ @code_lines = code_lines
26
+ @history = [block]
27
+ refresh_index
28
+ end
29
+
30
+ def commit_if_changed
31
+ if changed?
32
+ @history << CodeBlock.new(lines: @code_lines[before_index..after_index])
33
+ end
34
+
35
+ self
36
+ end
37
+
38
+ # Discards any changes that have not been committed
39
+ def stash_changes
40
+ refresh_index
41
+ self
42
+ end
43
+
44
+ # Discard changes that have not been committed and revert the last commit
45
+ #
46
+ # Cannot revert the first commit
47
+ def revert_last_commit
48
+ if @history.length > 1
49
+ @history.pop
50
+ refresh_index
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ def changed?
57
+ @before_index != current.lines.first.index ||
58
+ @after_index != current.lines.last.index
59
+ end
60
+
61
+ # Iterates up and down
62
+ #
63
+ # Returns line, kw_count, end_count for each iteration
64
+ def scan(up:, down:)
65
+ kw_count = 0
66
+ end_count = 0
67
+
68
+ up_index = before_lines.reverse_each.take_while do |line|
69
+ kw_count += 1 if line.is_kw?
70
+ end_count += 1 if line.is_end?
71
+ up.call(line, kw_count, end_count)
72
+ end.last&.index
73
+
74
+ kw_count = 0
75
+ end_count = 0
76
+
77
+ down_index = after_lines.each.take_while do |line|
78
+ kw_count += 1 if line.is_kw?
79
+ end_count += 1 if line.is_end?
80
+ down.call(line, kw_count, end_count)
81
+ end.last&.index
82
+
83
+ @before_index = if up_index && up_index < @before_index
84
+ up_index
85
+ else
86
+ @before_index
87
+ end
88
+
89
+ @after_index = if down_index && down_index > @after_index
90
+ down_index
91
+ else
92
+ @after_index
93
+ end
94
+
95
+ self
96
+ end
97
+
98
+ def next_up
99
+ return nil if @before_index <= 0
100
+
101
+ @code_lines[@before_index - 1]
102
+ end
103
+
104
+ def next_down
105
+ return nil if @after_index >= @code_lines.length
106
+
107
+ @code_lines[@after_index + 1]
108
+ end
109
+
110
+ def lines
111
+ @code_lines[@before_index..@after_index]
112
+ end
113
+
114
+ private def before_lines
115
+ @code_lines[0...@before_index] || []
116
+ end
117
+
118
+ # Returns an array of all the CodeLines that exist after
119
+ # the currently scanned block
120
+ private def after_lines
121
+ @code_lines[@after_index.next..-1] || []
122
+ end
123
+
124
+ private def current
125
+ @history.last
126
+ end
127
+
128
+ private def refresh_index
129
+ @before_index = current.lines.first.index
130
+ @after_index = current.lines.last.index
131
+ self
132
+ end
133
+ end
134
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxSuggest
4
- VERSION = "1.0.3"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_suggest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-17 00:00:00.000000000 Z
11
+ date: 2023-05-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: When you get an "unexpected end" in your syntax this gem helps you find
14
14
  it
@@ -32,6 +32,7 @@ files:
32
32
  - LICENSE.txt
33
33
  - README.md
34
34
  - Rakefile
35
+ - bin/bundle
35
36
  - bin/console
36
37
  - bin/rake
37
38
  - bin/rspec
@@ -41,6 +42,8 @@ files:
41
42
  - lib/syntax_suggest/api.rb
42
43
  - lib/syntax_suggest/around_block_scan.rb
43
44
  - lib/syntax_suggest/block_expand.rb
45
+ - lib/syntax_suggest/capture/before_after_keyword_ends.rb
46
+ - lib/syntax_suggest/capture/falling_indent_lines.rb
44
47
  - lib/syntax_suggest/capture_code_context.rb
45
48
  - lib/syntax_suggest/clean_document.rb
46
49
  - lib/syntax_suggest/cli.rb
@@ -60,6 +63,7 @@ files:
60
63
  - lib/syntax_suggest/priority_engulf_queue.rb
61
64
  - lib/syntax_suggest/priority_queue.rb
62
65
  - lib/syntax_suggest/ripper_errors.rb
66
+ - lib/syntax_suggest/scan_history.rb
63
67
  - lib/syntax_suggest/unvisited_lines.rb
64
68
  - lib/syntax_suggest/version.rb
65
69
  - syntax_suggest.gemspec
@@ -84,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
88
  - !ruby/object:Gem::Version
85
89
  version: '0'
86
90
  requirements: []
87
- rubygems_version: 3.4.6
91
+ rubygems_version: 3.3.26
88
92
  signing_key:
89
93
  specification_version: 4
90
94
  summary: Find syntax errors in your source in a snap