syntax_suggest 1.0.4 → 2.0.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 +31 -11
- data/.standard.yml +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +53 -35
- data/README.md +2 -2
- data/lib/syntax_suggest/api.rb +42 -7
- 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/clean_document.rb +6 -6
- data/lib/syntax_suggest/code_block.rb +1 -1
- data/lib/syntax_suggest/code_frontier.rb +1 -1
- data/lib/syntax_suggest/code_line.rb +12 -5
- data/lib/syntax_suggest/code_search.rb +2 -2
- data/lib/syntax_suggest/core_ext.rb +1 -1
- data/lib/syntax_suggest/display_invalid_blocks.rb +1 -1
- data/lib/syntax_suggest/explain_syntax.rb +18 -4
- data/lib/syntax_suggest/lex_all.rb +29 -10
- data/lib/syntax_suggest/pathname_from_message.rb +1 -1
- data/lib/syntax_suggest/ripper_errors.rb +4 -1
- data/lib/syntax_suggest/scan_history.rb +134 -0
- data/lib/syntax_suggest/version.rb +1 -1
- data/syntax_suggest.gemspec +2 -2
- metadata +7 -4
@@ -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
|
|
@@ -47,9 +47,9 @@ module SyntaxSuggest
|
|
47
47
|
# ## Heredocs
|
48
48
|
#
|
49
49
|
# A heredoc is an way of defining a multi-line string. They can cause many
|
50
|
-
# problems. If left as a single line,
|
50
|
+
# problems. If left as a single line, the parser would try to parse the contents
|
51
51
|
# as ruby code rather than as a string. Even without this problem, we still
|
52
|
-
# hit an issue with indentation
|
52
|
+
# hit an issue with indentation:
|
53
53
|
#
|
54
54
|
# 1 foo = <<~HEREDOC
|
55
55
|
# 2 "Be yourself; everyone else is already taken.""
|
@@ -224,7 +224,7 @@ module SyntaxSuggest
|
|
224
224
|
#
|
225
225
|
def join_consecutive!
|
226
226
|
consecutive_groups = @document.select(&:ignore_newline_not_beg?).map do |code_line|
|
227
|
-
take_while_including(code_line.index
|
227
|
+
take_while_including(code_line.index..) do |line|
|
228
228
|
line.ignore_newline_not_beg?
|
229
229
|
end
|
230
230
|
end
|
@@ -245,7 +245,7 @@ module SyntaxSuggest
|
|
245
245
|
# expect(lines[1].to_s).to eq("")
|
246
246
|
def join_trailing_slash!
|
247
247
|
trailing_groups = @document.select(&:trailing_slash?).map do |code_line|
|
248
|
-
take_while_including(code_line.index
|
248
|
+
take_while_including(code_line.index..) { |x| x.trailing_slash? }
|
249
249
|
end
|
250
250
|
join_groups(trailing_groups)
|
251
251
|
self
|
@@ -279,7 +279,7 @@ module SyntaxSuggest
|
|
279
279
|
)
|
280
280
|
|
281
281
|
# Hide the rest of the lines
|
282
|
-
lines[1
|
282
|
+
lines[1..].each do |line|
|
283
283
|
# The above lines already have newlines in them, if add more
|
284
284
|
# then there will be double newline, use an empty line instead
|
285
285
|
@document[line.index] = CodeLine.new(line: "", index: line.index, lex: [])
|
@@ -293,7 +293,7 @@ module SyntaxSuggest
|
|
293
293
|
# Like `take_while` except when it stops
|
294
294
|
# iterating, it also returns the line
|
295
295
|
# that caused it to stop
|
296
|
-
def take_while_including(range = 0
|
296
|
+
def take_while_including(range = 0..)
|
297
297
|
take_next_and_stop = false
|
298
298
|
@document[range].take_while do |line|
|
299
299
|
next if take_next_and_stop
|
@@ -81,7 +81,7 @@ module SyntaxSuggest
|
|
81
81
|
# lines then the result cannot be invalid
|
82
82
|
#
|
83
83
|
# That means there's no reason to re-check all
|
84
|
-
# lines with
|
84
|
+
# lines with the parser (which is expensive).
|
85
85
|
# Benchmark in commit message
|
86
86
|
@valid = if lines.all? { |l| l.hidden? || l.empty? }
|
87
87
|
true
|
@@ -180,12 +180,19 @@ module SyntaxSuggest
|
|
180
180
|
# EOM
|
181
181
|
# expect(lines.first.trailing_slash?).to eq(true)
|
182
182
|
#
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
183
|
+
if SyntaxSuggest.use_prism_parser?
|
184
|
+
def trailing_slash?
|
185
|
+
last = @lex.last
|
186
|
+
last&.type == :on_tstring_end
|
187
|
+
end
|
188
|
+
else
|
189
|
+
def trailing_slash?
|
190
|
+
last = @lex.last
|
191
|
+
return false unless last
|
192
|
+
return false unless last.type == :on_sp
|
187
193
|
|
188
|
-
|
194
|
+
last.token == TRAILING_SLASH
|
195
|
+
end
|
189
196
|
end
|
190
197
|
|
191
198
|
# Endless method detection
|
@@ -43,7 +43,7 @@ module SyntaxSuggest
|
|
43
43
|
|
44
44
|
def initialize(source, record_dir: DEFAULT_VALUE)
|
45
45
|
record_dir = if record_dir == DEFAULT_VALUE
|
46
|
-
ENV["SYNTAX_SUGGEST_RECORD_DIR"] || ENV["SYNTAX_SUGGEST_DEBUG"] ? "tmp" : nil
|
46
|
+
(ENV["SYNTAX_SUGGEST_RECORD_DIR"] || ENV["SYNTAX_SUGGEST_DEBUG"]) ? "tmp" : nil
|
47
47
|
else
|
48
48
|
record_dir
|
49
49
|
end
|
@@ -73,7 +73,7 @@ module SyntaxSuggest
|
|
73
73
|
if ENV["SYNTAX_SUGGEST_DEBUG"]
|
74
74
|
puts "\n\n==== #{filename} ===="
|
75
75
|
puts "\n```#{block.starts_at}..#{block.ends_at}"
|
76
|
-
puts block
|
76
|
+
puts block
|
77
77
|
puts "```"
|
78
78
|
puts " block indent: #{block.current_indent}"
|
79
79
|
end
|
@@ -21,7 +21,7 @@ if SyntaxError.method_defined?(:detailed_message)
|
|
21
21
|
attr_reader :string
|
22
22
|
end
|
23
23
|
|
24
|
-
# SyntaxSuggest.
|
24
|
+
# SyntaxSuggest.module_for_detailed_message [Private]
|
25
25
|
#
|
26
26
|
# Used to monkeypatch SyntaxError via Module.prepend
|
27
27
|
def self.module_for_detailed_message
|
@@ -2,7 +2,21 @@
|
|
2
2
|
|
3
3
|
require_relative "left_right_lex_count"
|
4
4
|
|
5
|
+
if !SyntaxSuggest.use_prism_parser?
|
6
|
+
require_relative "ripper_errors"
|
7
|
+
end
|
8
|
+
|
5
9
|
module SyntaxSuggest
|
10
|
+
class GetParseErrors
|
11
|
+
def self.errors(source)
|
12
|
+
if SyntaxSuggest.use_prism_parser?
|
13
|
+
Prism.parse(source).errors.map(&:message)
|
14
|
+
else
|
15
|
+
RipperErrors.new(source).call.errors
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
6
20
|
# Explains syntax errors based on their source
|
7
21
|
#
|
8
22
|
# example:
|
@@ -15,8 +29,8 @@ module SyntaxSuggest
|
|
15
29
|
# # => "Unmatched keyword, missing `end' ?"
|
16
30
|
#
|
17
31
|
# When the error cannot be determined by lexical counting
|
18
|
-
# then
|
19
|
-
# errors returned.
|
32
|
+
# then the parser is run against the input and the raw
|
33
|
+
# errors are returned.
|
20
34
|
#
|
21
35
|
# Example:
|
22
36
|
#
|
@@ -91,10 +105,10 @@ module SyntaxSuggest
|
|
91
105
|
# Returns an array of syntax error messages
|
92
106
|
#
|
93
107
|
# If no missing pairs are found it falls back
|
94
|
-
# on the original
|
108
|
+
# on the original error messages
|
95
109
|
def errors
|
96
110
|
if missing.empty?
|
97
|
-
return
|
111
|
+
return GetParseErrors.errors(@code_lines.map(&:original).join).uniq
|
98
112
|
end
|
99
113
|
|
100
114
|
missing.map { |miss| why(miss) }
|
@@ -3,34 +3,53 @@
|
|
3
3
|
module SyntaxSuggest
|
4
4
|
# Ripper.lex is not guaranteed to lex the entire source document
|
5
5
|
#
|
6
|
-
# lex
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# This class guarantees the whole document is lex-ed by iteratively
|
7
|
+
# lexing the document where ripper stopped.
|
8
|
+
#
|
9
|
+
# Prism likely doesn't have the same problem. Once ripper support is removed
|
10
|
+
# we can likely reduce the complexity here if not remove the whole concept.
|
11
|
+
#
|
12
|
+
# Example usage:
|
13
|
+
#
|
14
|
+
# lex = LexAll.new(source: source)
|
15
|
+
# lex.each do |value|
|
16
|
+
# puts value.line
|
17
|
+
# end
|
10
18
|
class LexAll
|
11
19
|
include Enumerable
|
12
20
|
|
13
21
|
def initialize(source:, source_lines: nil)
|
14
|
-
@lex =
|
15
|
-
lineno = @lex.last
|
22
|
+
@lex = self.class.lex(source, 1)
|
23
|
+
lineno = @lex.last[0][0] + 1
|
16
24
|
source_lines ||= source.lines
|
17
25
|
last_lineno = source_lines.length
|
18
26
|
|
19
27
|
until lineno >= last_lineno
|
20
|
-
lines = source_lines[lineno
|
28
|
+
lines = source_lines[lineno..]
|
21
29
|
|
22
30
|
@lex.concat(
|
23
|
-
|
31
|
+
self.class.lex(lines.join, lineno + 1)
|
24
32
|
)
|
25
|
-
|
33
|
+
|
34
|
+
lineno = @lex.last[0].first + 1
|
26
35
|
end
|
27
36
|
|
28
37
|
last_lex = nil
|
29
38
|
@lex.map! { |elem|
|
30
|
-
last_lex = LexValue.new(elem.
|
39
|
+
last_lex = LexValue.new(elem[0].first, elem[1], elem[2], elem[3], last_lex)
|
31
40
|
}
|
32
41
|
end
|
33
42
|
|
43
|
+
if SyntaxSuggest.use_prism_parser?
|
44
|
+
def self.lex(source, line_number)
|
45
|
+
Prism.lex_compat(source, line: line_number).value.sort_by { |values| values[0] }
|
46
|
+
end
|
47
|
+
else
|
48
|
+
def self.lex(source, line_number)
|
49
|
+
Ripper::Lexer.new(source, "-", line_number).parse.sort_by(&:pos)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
34
53
|
def to_a
|
35
54
|
@lex
|
36
55
|
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SyntaxSuggest
|
4
|
-
# Capture parse errors from
|
4
|
+
# Capture parse errors from Ripper
|
5
|
+
#
|
6
|
+
# Prism returns the errors with their messages, but Ripper
|
7
|
+
# does not. To get them we must make a custom subclass.
|
5
8
|
#
|
6
9
|
# Example:
|
7
10
|
#
|
@@ -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..] || []
|
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
|
data/syntax_suggest.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = 'When you get an "unexpected end" in your syntax this gem helps you find it'
|
17
17
|
spec.homepage = "https://github.com/ruby/syntax_suggest.git"
|
18
18
|
spec.license = "MIT"
|
19
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
19
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
20
20
|
|
21
21
|
spec.metadata["homepage_uri"] = spec.homepage
|
22
22
|
spec.metadata["source_code_uri"] = "https://github.com/ruby/syntax_suggest.git"
|
@@ -27,6 +27,6 @@ Gem::Specification.new do |spec|
|
|
27
27
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
|
28
28
|
end
|
29
29
|
spec.bindir = "exe"
|
30
|
-
spec.executables =
|
30
|
+
spec.executables = ["syntax_suggest"]
|
31
31
|
spec.require_paths = ["lib"]
|
32
32
|
end
|