Class: SyntaxSuggest::AroundBlockScan
- Inherits:
-
Object
- Object
- SyntaxSuggest::AroundBlockScan
- Defined in:
- lib/syntax_suggest/around_block_scan.rb
Overview
This class is useful for exploring contents before and after a block
It searches above and below the passed in block to match for whatever criteria you give it:
Example:
def dog # 1
puts "bark" # 2
puts "bark" # 3
end # 4
scan = AroundBlockScan.new(
code_lines: code_lines
block: CodeBlock.new(lines: code_lines[1])
)
scan.scan_while { true }
puts scan.before_index # => 0
puts scan.after_index # => 3
Contents can also be filtered using AroundBlockScan#skip
To grab the next surrounding indentation use AroundBlockScan#scan_adjacent_indent
Instance Method Summary collapse
-
#after_index ⇒ Object
Gives the index of the last line currently scanned.
-
#before_index ⇒ Object
Gives the index of the first line currently scanned.
-
#capture_neighbor_context ⇒ Object
Shows surrounding kw/end pairs.
-
#code_block ⇒ Object
Return the currently matched lines as a
CodeBlock. -
#force_add_empty ⇒ Object
When using this flag,
scan_whilewill bypass the block it’s given and always add a line that responds truthy to ‘CodeLine#empty?`. -
#force_add_hidden ⇒ Object
When using this flag,
scan_whilewill bypass the block it’s given and always add a line that responds truthy to ‘CodeLine#hidden?`. -
#initialize(code_lines:, block:) ⇒ AroundBlockScan
constructor
A new instance of AroundBlockScan.
-
#lines ⇒ Object
Returns the lines matched by the current scan as an array of CodeLines.
-
#lookahead_balance_one_line ⇒ Object
Scanning is intentionally conservative because we have no way of rolling back an agressive block (at this time).
-
#next_down ⇒ Object
Returns the next line to be scanned below the current block.
-
#next_up ⇒ Object
Returns the next line to be scanned above the current block.
-
#on_falling_indent ⇒ Object
Shows the context around code provided by “falling” indentation.
-
#scan_adjacent_indent ⇒ Object
Scan blocks based on indentation of next line above/below block.
-
#scan_neighbors_not_empty ⇒ Object
Finds code lines at the same or greater indentation and adds them to the block.
-
#scan_while ⇒ Object
Main work method.
-
#start_at_next_line ⇒ Object
TODO: Doc or delete.
-
#stop_after_kw ⇒ Object
Tells
scan_whileto look for mismatched keyword/end-s.
Constructor Details
#initialize(code_lines:, block:) ⇒ AroundBlockScan
Returns a new instance of AroundBlockScan.
31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 31 def initialize(code_lines:, block:) @code_lines = code_lines @orig_before_index = block.lines.first.index @orig_after_index = block.lines.last.index @orig_indent = block.current_indent @skip_array = [] @after_array = [] @before_array = [] @stop_after_kw = false @force_add_hidden = false @force_add_empty = false end |
Instance Method Details
#after_index ⇒ Object
Gives the index of the last line currently scanned
361 362 363 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 361 def after_index @after_index ||= @orig_after_index end |
#before_index ⇒ Object
Gives the index of the first line currently scanned
356 357 358 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 356 def before_index @before_index ||= @orig_before_index end |
#capture_neighbor_context ⇒ Object
Shows surrounding kw/end pairs
The purpose of showing these extra pairs is due to cases of ambiguity when only one visible line is matched.
For example:
1 class Dog
2 def
4 def eat
5 end
6 end
In this case either line 2 could be missing an end or line 4 was an extra line added by mistake (it happens).
When we detect the above problem it shows the issue as only being on line 2
2 def
Showing “neighbor” keyword pairs gives extra context:
2 def
4 def eat
5 end
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 163 def capture_neighbor_context lines = [] kw_count = 0 end_count = 0 before_lines.reverse_each do |line| next if line.empty? break if line.indent < @orig_indent next if line.indent != @orig_indent kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? if kw_count != 0 && kw_count == end_count lines << line break end lines << line end lines.reverse! kw_count = 0 end_count = 0 after_lines.each do |line| next if line.empty? break if line.indent < @orig_indent next if line.indent != @orig_indent kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? if kw_count != 0 && kw_count == end_count lines << line break end lines << line end lines end |
#code_block ⇒ Object
Return the currently matched lines as a CodeBlock
When a CodeBlock is created it will gather metadata about itself, so this is not a free conversion. Avoid allocating more CodeBlock’s than needed
345 346 347 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 345 def code_block CodeBlock.new(lines: lines) end |
#force_add_empty ⇒ Object
When using this flag, scan_while will bypass the block it’s given and always add a line that responds truthy to ‘CodeLine#empty?`
Empty lines contain no code, only whitespace such as leading spaces a newline.
63 64 65 66 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 63 def force_add_empty @force_add_empty = true self end |
#force_add_hidden ⇒ Object
When using this flag, scan_while will bypass the block it’s given and always add a line that responds truthy to ‘CodeLine#hidden?`
Lines are hidden when they’ve been evaluated by the parser as part of a block and found to contain valid code.
52 53 54 55 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 52 def force_add_hidden @force_add_hidden = true self end |
#lines ⇒ Object
Returns the lines matched by the current scan as an array of CodeLines
351 352 353 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 351 def lines @code_lines[before_index..after_index] end |
#lookahead_balance_one_line ⇒ Object
Scanning is intentionally conservative because we have no way of rolling back an agressive block (at this time)
If a block was stopped for some trivial reason, (like an empty line) but the next line would have caused it to be balanced then we can check that condition and grab just one more line either up or down.
For example, below if we’re scanning up, line 2 might cause the scanning to stop. This is because empty lines might denote logical breaks where the user intended to chunk code which is a good place to stop and check validity. Unfortunately it also means we might have a “dangling” keyword or end.
1 def
2
3 end
If lines 2 and 3 are in the block, then when this method is run it would see it is unbalanced, but that acquiring line 1 would make it balanced, so that’s what it does.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 259 def lookahead_balance_one_line kw_count = 0 end_count = 0 lines.each do |line| kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? end return self if kw_count == end_count # nothing to balance # More ends than keywords, check if we can balance expanding up if (end_count - kw_count) == 1 && next_up return self unless next_up.is_kw? return self unless next_up.indent >= @orig_indent @before_index = next_up.index # More keywords than ends, check if we can balance by expanding down elsif (kw_count - end_count) == 1 && next_down return self unless next_down.is_end? return self unless next_down.indent >= @orig_indent @after_index = next_down.index end self end |
#next_down ⇒ Object
Returns the next line to be scanned below the current block. Returns nil if at the bottom of the document already
300 301 302 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 300 def next_down @code_lines[after_index.next] end |
#next_up ⇒ Object
Returns the next line to be scanned above the current block. Returns nil if at the top of the document already
294 295 296 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 294 def next_up @code_lines[before_index.pred] end |
#on_falling_indent ⇒ Object
Shows the context around code provided by “falling” indentation
Converts:
it "foo" do
into:
class OH
def hello
it "foo" do
end
end
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 218 def on_falling_indent last_indent = @orig_indent before_lines.reverse_each do |line| next if line.empty? if line.indent < last_indent yield line last_indent = line.indent end end last_indent = @orig_indent after_lines.each do |line| next if line.empty? if line.indent < last_indent yield line last_indent = line.indent end end end |
#scan_adjacent_indent ⇒ Object
Scan blocks based on indentation of next line above/below block
Determines indentaion of the next line above/below the current block.
Normally this is called when a block has expanded to capture all “neighbors” at the same (or greater) indentation and needs to expand out. For example the def/end lines surrounding a method.
311 312 313 314 315 316 317 318 319 320 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 311 def scan_adjacent_indent before_after_indent = [] before_after_indent << (next_up&.indent || 0) before_after_indent << (next_down&.indent || 0) indent = before_after_indent.min scan_while { |line| line.not_empty? && line.indent >= indent } self end |
#scan_neighbors_not_empty ⇒ Object
Finds code lines at the same or greater indentation and adds them to the block
288 289 290 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 288 def scan_neighbors_not_empty scan_while { |line| line.not_empty? && line.indent >= @orig_indent } end |
#scan_while ⇒ Object
Main work method
The scan_while method takes a block that yields lines above and below the block. If the yield returns true, the @before_index or @after_index are modified to include the matched line.
In addition to yielding individual lines, the internals of this object give a mini DSL to handle common situations such as stopping if we’ve found a keyword/end mis-match in one direction or the other.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 91 def scan_while stop_next = false kw_count = 0 end_count = 0 index = before_lines.reverse_each.take_while do |line| next false if stop_next next true if @force_add_hidden && line.hidden? next true if @force_add_empty && line.empty? kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? if @stop_after_kw && kw_count > end_count stop_next = true end yield line end.last&.index if index && index < before_index @before_index = index end stop_next = false kw_count = 0 end_count = 0 index = after_lines.take_while do |line| next false if stop_next next true if @force_add_hidden && line.hidden? next true if @force_add_empty && line.empty? kw_count += 1 if line.is_kw? end_count += 1 if line.is_end? if @stop_after_kw && end_count > kw_count stop_next = true end yield line end.last&.index if index && index > after_index @after_index = index end self end |
#start_at_next_line ⇒ Object
TODO: Doc or delete
I don’t remember why this is needed, but it’s called in code_context. It’s related to the implementation of capture_neighbor_context somehow and that display improvement is only triggered when there’s one visible line
I think the primary purpose is to not include the current line in the logic evaluation of capture_neighbor_context. If that’s true, then we should fix that method to handle this logic instead of only using it in one place and together.
332 333 334 335 336 337 338 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 332 def start_at_next_line before_index after_index @before_index -= 1 @after_index += 1 self end |
#stop_after_kw ⇒ Object
Tells scan_while to look for mismatched keyword/end-s
When scanning up, if we see more keywords then end-s it will stop. This might happen when scanning outside of a method body. the first scan line up would be a keyword and this setting would trigger a stop.
When scanning down, stop if there are more end-s than keywords.
76 77 78 79 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 76 def stop_after_kw @stop_after_kw = true self end |