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
Instance Method Summary collapse
-
#code_block ⇒ Object
Return the currently matched lines as a ‘CodeBlock`.
-
#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?`.
-
#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?`.
-
#initialize(code_lines:, block:) ⇒ AroundBlockScan
constructor
A new instance of AroundBlockScan.
-
#inspect ⇒ Object
Manageable rspec errors.
-
#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 aggressive block (at this time).
-
#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.
-
#stop_after_kw ⇒ Object
Tells ‘scan_while` to look for mismatched keyword/end-s.
Constructor Details
#initialize(code_lines:, block:) ⇒ AroundBlockScan
Returns a new instance of AroundBlockScan.
30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 30 def initialize(code_lines:, block:) @code_lines = code_lines @orig_indent = block.current_indent @stop_after_kw = false @force_add_empty = false @force_add_hidden = false @target_indent = nil @scanner = ScanHistory.new(code_lines: code_lines, block: block) end |
Instance Method Details
#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
217 218 219 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 217 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.
60 61 62 63 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 60 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.
49 50 51 52 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 49 def force_add_hidden @force_add_hidden = true self end |
#inspect ⇒ Object
Manageable rspec errors
228 229 230 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 228 def inspect "#<#{self.class}:0x0000123843lol >" end |
#lines ⇒ Object
Returns the lines matched by the current scan as an array of CodeLines
223 224 225 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 223 def lines @scanner.lines end |
#lookahead_balance_one_line ⇒ Object
Scanning is intentionally conservative because we have no way of rolling back an aggressive 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 bark
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.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 141 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 @scanner.commit_if_changed # Rollback point if we don't find anything to optimize # Try to eat up empty lines @scanner.scan( up: ->(line, _, _) { line.hidden? || line.empty? }, down: ->(line, _, _) { line.hidden? || line.empty? } ) # More ends than keywords, check if we can balance expanding up next_up = @scanner.next_up next_down = @scanner.next_down case end_count - kw_count when 1 if next_up&.is_kw? && next_up.indent >= @target_indent @scanner.scan( up: ->(line, _, _) { line == next_up }, down: ->(line, _, _) { false } ) @scanner.commit_if_changed end when -1 if next_down&.is_end? && next_down.indent >= @target_indent @scanner.scan( up: ->(line, _, _) { false }, down: ->(line, _, _) { line == next_down } ) @scanner.commit_if_changed end end # Rollback any uncommitted changes @scanner.stash_changes self 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.
200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 200 def scan_adjacent_indent before_after_indent = [] before_after_indent << (@scanner.next_up&.indent || 0) before_after_indent << (@scanner.next_down&.indent || 0) @target_indent = before_after_indent.min scan_while { |line| line.not_empty? && line.indent >= @target_indent } self end |
#scan_neighbors_not_empty ⇒ Object
Finds code lines at the same or greater indentation and adds them to the block
188 189 190 191 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 188 def scan_neighbors_not_empty @target_indent = @orig_indent scan_while { |line| line.not_empty? && line.indent >= @target_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.
88 89 90 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 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 88 def scan_while stop_next_up = false stop_next_down = false @scanner.scan( up: ->(line, kw_count, end_count) { next false if stop_next_up next true if @force_add_hidden && line.hidden? next true if @force_add_empty && line.empty? if @stop_after_kw && kw_count > end_count stop_next_up = true end yield line }, down: ->(line, kw_count, end_count) { next false if stop_next_down next true if @force_add_hidden && line.hidden? next true if @force_add_empty && line.empty? if @stop_after_kw && end_count > kw_count stop_next_down = true end yield line } ) 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.
73 74 75 76 |
# File 'lib/syntax_suggest/around_block_scan.rb', line 73 def stop_after_kw @stop_after_kw = true self end |