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 +4 -4
- data/.github/workflows/check_changelog.yml +1 -1
- data/.github/workflows/ci.yml +2 -2
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -0
- data/bin/bundle +6 -0
- data/lib/syntax_suggest/api.rb +1 -1
- data/lib/syntax_suggest/around_block_scan.rb +68 -213
- data/lib/syntax_suggest/block_expand.rb +11 -5
- data/lib/syntax_suggest/capture/before_after_keyword_ends.rb +85 -0
- data/lib/syntax_suggest/capture/falling_indent_lines.rb +71 -0
- data/lib/syntax_suggest/capture_code_context.rb +18 -5
- data/lib/syntax_suggest/core_ext.rb +2 -0
- data/lib/syntax_suggest/scan_history.rb +134 -0
- data/lib/syntax_suggest/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0aa200b221c59254250acfa3343365ead0a7e8a0e699aec254e248a880bf4084
|
4
|
+
data.tar.gz: e343f13b8cfb3091b7e6f2ee97575fbe7e8e9b24d0f36168524a098e5d596098
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 && \
|
data/.github/workflows/ci.yml
CHANGED
@@ -9,7 +9,7 @@ jobs:
|
|
9
9
|
runs-on: ubuntu-latest
|
10
10
|
steps:
|
11
11
|
- name: Checkout code
|
12
|
-
uses: actions/checkout@v3.
|
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.
|
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
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
data/lib/syntax_suggest/api.rb
CHANGED
@@ -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
|
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
|
-
@
|
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
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
202
|
-
|
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
|
-
|
225
|
-
|
226
|
-
end
|
114
|
+
}
|
115
|
+
)
|
227
116
|
|
228
|
-
|
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
|
-
#
|
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
|
-
#
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
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
|
-
|
290
|
-
|
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
|
317
|
-
|
203
|
+
before_after_indent << (@scanner.next_up&.indent || 0)
|
204
|
+
before_after_indent << (@scanner.next_down&.indent || 0)
|
318
205
|
|
319
|
-
|
320
|
-
|
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
|
-
@
|
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
|
-
#
|
372
|
-
|
373
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
92
|
+
Capture::FallingIndentLines.new(
|
81
93
|
block: block,
|
82
94
|
code_lines: @code_lines
|
83
|
-
).
|
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 =
|
119
|
-
|
120
|
-
|
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
|
|
@@ -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
|
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
|
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-
|
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.
|
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
|