Skip to content

Commit 688afa0

Browse files
authored
Merge pull request #26 from zombocom/schneems/better-output-format
[close #20] Simplify large file output
2 parents dfc3d3f + 9036905 commit 688afa0

File tree

11 files changed

+352
-46
lines changed

11 files changed

+352
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## HEAD (unreleased)
22

3+
- Simplify large file output so minimal context around the invalid section is shown (https://github.com/zombocom/syntax_search/pull/26)
34
- Block expansion is now lexically aware of keywords (def/do/end etc.) (https://github.com/zombocom/syntax_search/pull/24)
45
- 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)
56

lib/syntax_search/around_block_scan.rb

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,70 @@ def scan_while(&block)
8585
self
8686
end
8787

88+
def capture_neighbor_context
89+
lines = []
90+
kw_count = 0
91+
end_count = 0
92+
before_lines.reverse.each do |line|
93+
next if line.empty?
94+
break if line.indent < @orig_indent
95+
next if line.indent != @orig_indent
96+
97+
kw_count += 1 if line.is_kw?
98+
end_count += 1 if line.is_end?
99+
if kw_count != 0 && kw_count == end_count
100+
lines << line
101+
break
102+
end
103+
104+
lines << line
105+
end
106+
107+
lines.reverse!
108+
109+
kw_count = 0
110+
end_count = 0
111+
after_lines.each do |line|
112+
# puts "line: #{line.number} #{line.original_line}, indent: #{line.indent}, #{line.empty?} #{line.indent == @orig_indent}"
113+
114+
next if line.empty?
115+
break if line.indent < @orig_indent
116+
next if line.indent != @orig_indent
117+
118+
kw_count += 1 if line.is_kw?
119+
end_count += 1 if line.is_end?
120+
if kw_count != 0 && kw_count == end_count
121+
lines << line
122+
break
123+
end
124+
125+
lines << line
126+
end
127+
lines.select! {|line| !line.is_comment? }
128+
129+
lines
130+
end
131+
132+
def on_falling_indent
133+
last_indent = @orig_indent
134+
before_lines.reverse.each do |line|
135+
next if line.empty?
136+
if line.indent < last_indent
137+
yield line
138+
last_indent = line.indent
139+
end
140+
end
141+
142+
last_indent = @orig_indent
143+
after_lines.each do |line|
144+
next if line.empty?
145+
if line.indent < last_indent
146+
yield line
147+
last_indent = line.indent
148+
end
149+
end
150+
end
151+
88152
def scan_neighbors
89153
self.scan_while {|line| line.not_empty? && line.indent >= @orig_indent }
90154
end
@@ -93,23 +157,29 @@ def scan_adjacent_indent
93157
before_indent = @code_lines[@orig_before_index.pred]&.indent || 0
94158
after_indent = @code_lines[@orig_after_index.next]&.indent || 0
95159

96-
97160
indent = [before_indent, after_indent].min
98161
self.scan_while {|line| line.not_empty? && line.indent >= indent }
99162

100163
self
101164
end
102165

166+
def start_at_next_line
167+
before_index; after_index
168+
@before_index -= 1
169+
@after_index += 1
170+
self
171+
end
172+
103173
def code_block
104174
CodeBlock.new(lines: @code_lines[before_index..after_index])
105175
end
106176

107177
def before_index
108-
@before_index || @orig_before_index
178+
@before_index ||= @orig_before_index
109179
end
110180

111181
def after_index
112-
@after_index || @orig_after_index
182+
@after_index ||= @orig_after_index
113183
end
114184

115185
private def before_lines
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxErrorSearch
4+
5+
# Given a block, this method will capture surrounding
6+
# code to give the user more context for the location of
7+
# the problem.
8+
#
9+
# Return is an array of CodeLines to be rendered.
10+
#
11+
# Surrounding code is captured regardless of visible state
12+
#
13+
# puts block.to_s # => "def bark"
14+
#
15+
# context = CaptureCodeContext.new(
16+
# blocks: block,
17+
# code_lines: code_lines
18+
# )
19+
#
20+
# puts context.call.join
21+
# # =>
22+
# class Dog
23+
# def bark
24+
# end
25+
#
26+
class CaptureCodeContext
27+
attr_reader :code_lines
28+
29+
def initialize(blocks: , code_lines:)
30+
@blocks = Array(blocks)
31+
@code_lines = code_lines
32+
@visible_lines = @blocks.map(&:visible_lines).flatten
33+
@lines_to_output = @visible_lines.dup
34+
end
35+
36+
def call
37+
@blocks.each do |block|
38+
around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
39+
.start_at_next_line
40+
.capture_neighbor_context
41+
42+
around_lines -= block.lines
43+
44+
@lines_to_output.concat(around_lines)
45+
46+
AroundBlockScan.new(
47+
block: block,
48+
code_lines: @code_lines,
49+
).on_falling_indent do |line|
50+
@lines_to_output << line
51+
end
52+
end
53+
54+
@lines_to_output.select!(&:not_empty?)
55+
@lines_to_output.select!(&:not_comment?)
56+
@lines_to_output.uniq!
57+
@lines_to_output.sort!
58+
59+
return @lines_to_output
60+
end
61+
end
62+
end

lib/syntax_search/code_block.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def initialize(lines: [])
2323
@lines = Array(lines)
2424
end
2525

26+
def visible_lines
27+
@lines.select(&:visible?).select(&:not_empty?)
28+
end
29+
2630
def mark_invisible
2731
@lines.map(&:mark_invisible)
2832
end
@@ -48,7 +52,11 @@ def ends_at
4852
# populate an array with multiple code blocks then call `sort!`
4953
# on it without having to specify the sorting criteria
5054
def <=>(other)
51-
self.current_indent <=> other.current_indent
55+
out = self.current_indent <=> other.current_indent
56+
return out if out != 0
57+
58+
# Stable sort
59+
self.starts_at <=> other.starts_at
5260
end
5361

5462
def current_indent

lib/syntax_search/code_line.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module SyntaxErrorSearch
2929
# Marking a line as invisible also lets the overall program know
3030
# that it should not check that area for syntax errors.
3131
class CodeLine
32-
attr_reader :line, :index, :indent
32+
attr_reader :line, :index, :indent, :original_line
3333

3434
def initialize(line: , index:)
3535
@original_line = line.freeze
@@ -60,10 +60,18 @@ def initialize(line: , index:)
6060
@is_end = (@end_count - @kw_count) > 0
6161
end
6262

63+
def <=>(b)
64+
self.index <=> b.index
65+
end
66+
6367
def is_comment?
6468
@is_comment
6569
end
6670

71+
def not_comment?
72+
!is_comment?
73+
end
74+
6775
def is_kw?
6876
@is_kw
6977
end
@@ -103,6 +111,8 @@ def line_number
103111
index + 1
104112
end
105113

114+
alias :number :line_number
115+
106116
def not_empty?
107117
!empty?
108118
end

lib/syntax_search/code_search.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ def push(block, name: )
7979
end
8080
end
8181

82+
# Removes the block without putting it back in the frontier
83+
def sweep(block:, name: )
84+
record(block: block, name: name)
85+
86+
block.lines.each(&:mark_invisible)
87+
frontier.register_indent_block(block)
88+
end
89+
8290
# Parses the most indented lines into blocks that are marked
8391
# and added to the frontier
8492
def add_invalid_blocks
@@ -118,9 +126,10 @@ def sweep_heredocs
118126
end
119127

120128
def sweep_comments
121-
@code_lines.select(&:is_comment?).each do |line|
122-
line.mark_invisible
123-
end
129+
lines = @code_lines.select(&:is_comment?)
130+
return if lines.empty?
131+
block = CodeBlock.new(lines: lines)
132+
sweep(block: block, name: "comments")
124133
end
125134

126135
# Main search loop
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxErrorSearch
4+
# Outputs code with highlighted lines
5+
#
6+
# Whatever is passed to this class will be rendered
7+
# even if it is "marked invisible" any filtering of
8+
# output should be done before calling this class.
9+
#
10+
#
11+
# DisplayCodeWithLineNumbers.new(
12+
# lines: lines,
13+
# highlight_lines: [lines[2], lines[3]]
14+
# ).call
15+
# # =>
16+
# 1
17+
# 2 def cat
18+
# ❯ 3 Dir.chdir
19+
# ❯ 4 end
20+
# 5 end
21+
# 6
22+
class DisplayCodeWithLineNumbers
23+
TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
24+
TERMINAL_END = "\e[0m"
25+
26+
def initialize(lines: , highlight_lines: [], terminal: false)
27+
@lines = lines.sort
28+
@terminal = terminal
29+
@highlight_line_hash = highlight_lines.each_with_object({}) {|line, h| h[line] = true }
30+
@digit_count = @lines.last&.line_number.to_s.length
31+
end
32+
33+
def call
34+
@lines.map do |line|
35+
string = String.new("")
36+
if @highlight_line_hash[line]
37+
string << "❯ "
38+
else
39+
string << " "
40+
end
41+
42+
number = line.line_number.to_s.rjust(@digit_count)
43+
string << number.to_s
44+
if line.empty?
45+
string << line.original_line
46+
else
47+
string << " "
48+
string << TERMINAL_HIGHLIGHT if @terminal && @highlight_line_hash[line] # Bold, italics
49+
string << line.original_line
50+
string << TERMINAL_END if @terminal
51+
end
52+
string
53+
end.join
54+
end
55+
end
56+
end

0 commit comments

Comments
 (0)