Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## HEAD (unreleased)

- Simplify large file output so minimal context around the invalid section is shown (https://github.com/zombocom/syntax_search/pull/26)
- Block expansion is now lexically aware of keywords (def/do/end etc.) (https://github.com/zombocom/syntax_search/pull/24)
- Fix bug where not all of a source is lexed which is used in heredoc detection/removal (https://github.com/zombocom/syntax_search/pull/23)

Expand Down
76 changes: 73 additions & 3 deletions lib/syntax_search/around_block_scan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,70 @@ def scan_while(&block)
self
end

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|
# puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"

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.select! {|line| !line.is_comment? }

lines
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
end

def scan_neighbors
self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
end
Expand All @@ -93,23 +157,29 @@ def scan_adjacent_indent
before_indent = @code_lines[@orig_before_index.pred]&.indent || 0
after_indent = @code_lines[@orig_after_index.next]&.indent || 0


indent = [before_indent, after_indent].min
self.scan_while {|line| line.not_empty? && line.indent >= indent }

self
end

def start_at_next_line
before_index; after_index
@before_index -= 1
@after_index += 1
self
end

def code_block
CodeBlock.new(lines: @code_lines[before_index..after_index])
end

def before_index
@before_index || @orig_before_index
@before_index ||= @orig_before_index
end

def after_index
@after_index || @orig_after_index
@after_index ||= @orig_after_index
end

private def before_lines
Expand Down
1 change: 1 addition & 0 deletions lib/syntax_search/block_expand.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def call(block)

def expand_indent(block)
block = AroundBlockScan.new(code_lines: @code_lines, block: block)
.skip(:hidden?)
.stop_after_kw
.scan_adjacent_indent
.code_block
Expand Down
62 changes: 62 additions & 0 deletions lib/syntax_search/capture_code_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module SyntaxErrorSearch

# Given a block, this method will capture surrounding
# code to give the user more context for the location of
# the problem.
#
# Return is an array of CodeLines to be rendered.
#
# Surrounding code is captured regardless of visible state
#
# puts block.to_s # => "def bark"
#
# context = CaptureCodeContext.new(
# blocks: block,
# code_lines: code_lines
# )
#
# puts context.call.join
# # =>
# class Dog
# def bark
# end
#
class CaptureCodeContext
attr_reader :code_lines

def initialize(blocks: , code_lines:)
@blocks = Array(blocks)
@code_lines = code_lines
@visible_lines = @blocks.map(&:visible_lines).flatten
@lines_to_output = @visible_lines.dup
end

def call
@blocks.each do |block|
around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
.start_at_next_line
.capture_neighbor_context

around_lines -= block.lines

@lines_to_output.concat(around_lines)

AroundBlockScan.new(
block: block,
code_lines: @code_lines,
).on_falling_indent do |line|
@lines_to_output << line
end
end

@lines_to_output.select!(&:not_empty?)
@lines_to_output.select!(&:not_comment?)
@lines_to_output.uniq!
@lines_to_output.sort!

return @lines_to_output
end
end
end
10 changes: 9 additions & 1 deletion lib/syntax_search/code_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def initialize(lines: [])
@lines = Array(lines)
end

def visible_lines
@lines.select(&:visible?).select(&:not_empty?)
end

def mark_invisible
@lines.map(&:mark_invisible)
end
Expand All @@ -48,7 +52,11 @@ def ends_at
# populate an array with multiple code blocks then call `sort!`
# on it without having to specify the sorting criteria
def <=>(other)
self.current_indent <=> other.current_indent
out = self.current_indent <=> other.current_indent
return out if out != 0

# Stable sort
self.starts_at <=> other.starts_at
end

def current_indent
Expand Down
12 changes: 11 additions & 1 deletion lib/syntax_search/code_line.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module SyntaxErrorSearch
# Marking a line as invisible also lets the overall program know
# that it should not check that area for syntax errors.
class CodeLine
attr_reader :line, :index, :indent
attr_reader :line, :index, :indent, :original_line

def initialize(line: , index:)
@original_line = line.freeze
Expand Down Expand Up @@ -60,10 +60,18 @@ def initialize(line: , index:)
@is_end = (@end_count - @kw_count) > 0
end

def <=>(b)
self.index <=> b.index
end

def is_comment?
@is_comment
end

def not_comment?
!is_comment?
end

def is_kw?
@is_kw
end
Expand Down Expand Up @@ -103,6 +111,8 @@ def line_number
index + 1
end

alias :number :line_number

def not_empty?
!empty?
end
Expand Down
15 changes: 12 additions & 3 deletions lib/syntax_search/code_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ def push(block, name: )
end
end

# Removes the block without putting it back in the frontier
def sweep(block:, name: )
record(block: block, name: name)

block.lines.each(&:mark_invisible)
frontier.register_indent_block(block)
end

# Parses the most indented lines into blocks that are marked
# and added to the frontier
def add_invalid_blocks
Expand Down Expand Up @@ -118,9 +126,10 @@ def sweep_heredocs
end

def sweep_comments
@code_lines.select(&:is_comment?).each do |line|
line.mark_invisible
end
lines = @code_lines.select(&:is_comment?)
return if lines.empty?
block = CodeBlock.new(lines: lines)
sweep(block: block, name: "comments")
end

# Main search loop
Expand Down
56 changes: 56 additions & 0 deletions lib/syntax_search/display_code_with_line_numbers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module SyntaxErrorSearch
# Outputs code with highlighted lines
#
# Whatever is passed to this class will be rendered
# even if it is "marked invisible" any filtering of
# output should be done before calling this class.
#
#
# DisplayCodeWithLineNumbers.new(
# lines: lines,
# highlight_lines: [lines[2], lines[3]]
# ).call
# # =>
# 1
# 2 def cat
# ❯ 3 Dir.chdir
# ❯ 4 end
# 5 end
# 6
class DisplayCodeWithLineNumbers
TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
TERMINAL_END = "\e[0m"

def initialize(lines: , highlight_lines: [], terminal: false)
@lines = lines.sort
@terminal = terminal
@highlight_line_hash = highlight_lines.each_with_object({}) {|line, h| h[line] = true }
@digit_count = @lines.last&.line_number.to_s.length
end

def call
@lines.map do |line|
string = String.new("")
if @highlight_line_hash[line]
string << "❯ "
else
string << " "
end

number = line.line_number.to_s.rjust(@digit_count)
string << number.to_s
if line.empty?
string << line.original_line
else
string << " "
string << TERMINAL_HIGHLIGHT if @terminal && @highlight_line_hash[line] # Bold, italics
string << line.original_line
string << TERMINAL_END if @terminal
end
string
end.join
end
end
end
Loading