syntax_suggest 1.0.3 → 1.1.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 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