diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/syntax_suggest/around_block_scan.rb | 282 | ||||
-rw-r--r-- | lib/syntax_suggest/block_expand.rb | 9 | ||||
-rw-r--r-- | lib/syntax_suggest/capture_code_context.rb | 6 | ||||
-rw-r--r-- | lib/syntax_suggest/scan_history.rb | 113 |
4 files changed, 259 insertions, 151 deletions
diff --git a/lib/syntax_suggest/around_block_scan.rb b/lib/syntax_suggest/around_block_scan.rb index fe63470dee..21f309bf11 100644 --- a/lib/syntax_suggest/around_block_scan.rb +++ b/lib/syntax_suggest/around_block_scan.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "scan_history" + module SyntaxSuggest # This class is useful for exploring contents before and after # a block @@ -24,22 +26,17 @@ module SyntaxSuggest # 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 class AroundBlockScan 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 + @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 # When using this flag, `scan_while` will @@ -89,47 +86,34 @@ module SyntaxSuggest # stopping if we've found a keyword/end mis-match in one direction # or the other. 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? + stop_next_up = false + stop_next_down = false - 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 + @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? - yield line - end.last&.index + if @stop_after_kw && kw_count > end_count + stop_next_up = true + end - 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? + 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? - 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 + if @stop_after_kw && end_count > kw_count + stop_next_down = true + end - yield line - end.last&.index + yield line + } + ) - if index && index > after_index - @after_index = index - end self end @@ -160,79 +144,104 @@ module SyntaxSuggest # 4 def eat # 5 end # - def capture_neighbor_context + def capture_before_after_kws 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 if line.is_kw? || line.is_end? - 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 if line.is_kw? || line.is_end? - end + up_stop_next = false + down_stop_next = false + @scanner.commit_if_changed + lines = [] + @scanner.scan( + up: ->(line, kw_count, end_count) { + break if up_stop_next + next true if line.empty? + break if line.indent < @orig_indent + next true if line.indent != @orig_indent + + # If we're going up and have one complete kw/end pair, stop + if kw_count != 0 && kw_count == end_count + lines << line + break + end + + lines << line if line.is_kw? || line.is_end? + }, + down: ->(line, kw_count, end_count) { + break if down_stop_next + next true if line.empty? + break if line.indent < @orig_indent + next true if line.indent != @orig_indent + + # if we're going down and have one complete kw/end pair,stop + if kw_count != 0 && kw_count == end_count + lines << line + break + end + + lines << line if line.is_kw? || line.is_end? + } + ) + @scanner.try_rollback lines end # Shows the context around code provided by "falling" indentation # - # Converts: # + # If this is the original code lines: + # + # class OH + # def hello # it "foo" do + # end + # end # - # into: + # And this is the line that is captured + # + # it "foo" do + # + # It will yield its surrounding context: # # class OH # def hello - # it "foo" do # end # end # + # Example: + # + # AroundBlockScan.new( + # block: block, + # code_lines: @code_lines + # ).on_falling_indent do |line| + # @lines_to_output << line + # end + # 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 + last_indent_up = @orig_indent + last_indent_down = @orig_indent + + @scanner.commit_if_changed + @scanner.scan( + up: ->(line, _, _) { + next true if line.empty? + + if line.indent < last_indent_up + yield line + last_indent_up = line.indent + end + true + }, + down: ->(line, _, _) { + next true if line.empty? + if line.indent < last_indent_down + yield line + last_indent_down = line.indent + end + true + } + ) + @scanner.try_rollback + self end # Scanning is intentionally conservative because @@ -267,18 +276,24 @@ module SyntaxSuggest return self if kw_count == end_count # nothing to balance # More ends than keywords, check if we can balance expanding up + next_up = @scanner.next_up + next_down = @scanner.next_down 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 + if next_up.is_kw? && next_up.indent >= @orig_indent + @scanner.scan( + up: ->(line, _, _) { line == next_up }, + down: ->(line, _, _) { false } + ) + end # 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 + if next_down.is_end? && next_down.indent >= @orig_indent + @scanner.scan( + up: ->(line, _, _) { false }, + down: ->(line) { line == next_down } + ) + end end self end @@ -286,19 +301,8 @@ module SyntaxSuggest # Finds code lines at the same or greater indentation and adds them # to the block def scan_neighbors_not_empty - scan_while { |line| line.not_empty? && line.indent >= @orig_indent } - end - - # Returns the next line to be scanned above the current block. - # Returns `nil` if at the top of the document already - def next_up - @code_lines[before_index.pred] - end - - # Returns the next line to be scanned below the current block. - # Returns `nil` if at the bottom of the document already - def next_down - @code_lines[after_index.next] + @target_indent = @orig_indent + scan_while { |line| line.not_empty? && line.indent >= @target_indent } end # Scan blocks based on indentation of next line above/below block @@ -310,11 +314,12 @@ module SyntaxSuggest # the `def/end` lines surrounding a method. 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 } + 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 @@ -331,29 +336,12 @@ module SyntaxSuggest # Returns the lines matched by the current scan as an # array of CodeLines def lines - @code_lines[before_index..after_index] - end - - # Gives the index of the first line currently scanned - def before_index - @before_index ||= @orig_before_index - end - - # Gives the index of the last line currently scanned - def after_index - @after_index ||= @orig_after_index - end - - # Returns an array of all the CodeLines that exist before - # the currently scanned block - private def before_lines - @code_lines[0...before_index] || [] + @scanner.lines end - # Returns an array of all the CodeLines that exist after - # the currently scanned block - private def after_lines - @code_lines[after_index.next..-1] || [] + # Managable rspec errors + def inspect + "#<#{self.class}:0x0000123843lol >" end end end diff --git a/lib/syntax_suggest/block_expand.rb b/lib/syntax_suggest/block_expand.rb index 8431d15edd..e85d286768 100644 --- a/lib/syntax_suggest/block_expand.rb +++ b/lib/syntax_suggest/block_expand.rb @@ -125,17 +125,20 @@ module SyntaxSuggest # # We try to resolve this edge case with `lookahead_balance_one_line` below. def expand_neighbors(block) - neighbors = AroundBlockScan.new(code_lines: @code_lines, block: block) + now = AroundBlockScan.new(code_lines: @code_lines, block: block) + + # Initial scan + now .force_add_hidden .stop_after_kw .scan_neighbors_not_empty # Slurp up empties - with_empties = neighbors + now .scan_while { |line| line.empty? } # If next line is kw and it will balance us, take it - expanded_lines = with_empties + expanded_lines = now .lookahead_balance_one_line .lines diff --git a/lib/syntax_suggest/capture_code_context.rb b/lib/syntax_suggest/capture_code_context.rb index a618b2ec68..71f5b271b5 100644 --- a/lib/syntax_suggest/capture_code_context.rb +++ b/lib/syntax_suggest/capture_code_context.rb @@ -55,6 +55,10 @@ module SyntaxSuggest capture_falling_indent(block) end + sorted_lines + end + + def sorted_lines @lines_to_output.select!(&:not_empty?) @lines_to_output.uniq! @lines_to_output.sort! @@ -116,7 +120,7 @@ module SyntaxSuggest return unless block.visible_lines.count == 1 around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block) - .capture_neighbor_context + .capture_before_after_kws around_lines -= block.lines diff --git a/lib/syntax_suggest/scan_history.rb b/lib/syntax_suggest/scan_history.rb new file mode 100644 index 0000000000..c5664d0f65 --- /dev/null +++ b/lib/syntax_suggest/scan_history.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module SyntaxSuggest + # Scans up/down from the given block + # + # You can snapshot a change by committing it and rolling back. + # + class ScanHistory + attr_reader :before_index, :after_index + + def initialize(code_lines:, block:) + @code_lines = code_lines + @history = [block] + refresh_index + end + + def commit_if_changed + if changed? + @history << CodeBlock.new(lines: @code_lines[before_index..after_index]) + refresh_index + end + + self + end + + def try_rollback + if @history.length > 1 + @history.pop + refresh_index + end + + self + end + + def changed? + @before_index != current.lines.first.index || + @after_index != current.lines.last.index + end + + # Iterates up and down + # + # Returns line, kw_count, end_count for each iteration + def scan(up:, down:) + kw_count = 0 + end_count = 0 + + up_index = before_lines.reverse_each.take_while do |line| + kw_count += 1 if line.is_kw? + end_count += 1 if line.is_end? + up.call(line, kw_count, end_count) + end.last&.index + + kw_count = 0 + end_count = 0 + + down_index = after_lines.each.take_while do |line| + kw_count += 1 if line.is_kw? + end_count += 1 if line.is_end? + down.call(line, kw_count, end_count) + end.last&.index + + @before_index = if up_index && up_index < @before_index + up_index + else + @before_index + end + + @after_index = if down_index && down_index > @after_index + down_index + else + @after_index + end + + self + end + + def next_up + return nil if @before_index <= 0 + + @code_lines[@before_index - 1] + end + + def next_down + return nil if @after_index >= @code_lines.length + + @code_lines[@after_index + 1] + end + + def lines + @code_lines[@before_index..@after_index] + end + + private def before_lines + @code_lines[0...@before_index] || [] + end + + # Returns an array of all the CodeLines that exist after + # the currently scanned block + private def after_lines + @code_lines[@after_index.next..-1] || [] + end + + private def current + @history.last + end + + private def refresh_index + @before_index = current.lines.first.index + @after_index = current.lines.last.index + self + end + end +end |