Skip to content

Commit 10c69c4

Browse files
committed
Handle very long lines with errors in the middle of the line
1 parent 14a51e3 commit 10c69c4

2 files changed

Lines changed: 76 additions & 48 deletions

File tree

lib/error_highlight/formatter.rb

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,46 @@ module ErrorHighlight
22
class DefaultFormatter
33
def self.message_for(spot)
44
# currently only a one-line code snippet is supported
5-
if spot[:first_lineno] == spot[:last_lineno]
6-
spot = truncate(spot)
5+
return "" unless spot[:first_lineno] == spot[:last_lineno]
76

8-
indent = spot[:snippet][0...spot[:first_column]].gsub(/[^\t]/, " ")
9-
marker = indent + "^" * (spot[:last_column] - spot[:first_column])
7+
snippet = spot[:snippet]
8+
first_column = spot[:first_column]
9+
last_column = spot[:last_column]
1010

11-
"\n\n#{ spot[:snippet] }#{ marker }"
12-
else
13-
""
11+
# truncate snippet to fit in the viewport
12+
if snippet.size > viewport_size
13+
visible_start = [first_column - viewport_size / 2, 0].max
14+
visible_end = visible_start + viewport_size
15+
16+
# avoid centering the snippet when the error is at the end of the line
17+
visible_start = snippet.size - viewport_size if visible_end > snippet.size
18+
19+
prefix = visible_start.positive? ? "..." : ""
20+
suffix = visible_end < snippet.size ? "..." : ""
21+
22+
snippet = prefix + snippet[(visible_start + prefix.size)...(visible_end - suffix.size)] + suffix
23+
snippet << "\n" unless snippet.end_with?("\n")
24+
25+
first_column = first_column - visible_start
26+
last_column = [last_column - visible_start, snippet.size - 1].min
1427
end
28+
29+
indent = snippet[0...first_column].gsub(/[^\t]/, " ")
30+
marker = indent + "^" * (last_column - first_column)
31+
32+
"\n\n#{ snippet }#{ marker }"
1533
end
1634

1735
def self.viewport_size
18-
Ractor.current[:__error_highlight_viewport_size__] || terminal_columns
36+
Ractor.current[:__error_highlight_viewport_size__] ||= terminal_columns
1937
end
2038

2139
def self.viewport_size=(viewport_size)
2240
Ractor.current[:__error_highlight_viewport_size__] = viewport_size
2341
end
2442

25-
private
26-
27-
def self.truncate(spot)
28-
ellipsis = '...'
29-
snippet = spot[:snippet]
30-
diff = snippet.size - (viewport_size - ellipsis.size)
31-
32-
# snippet fits in the terminal
33-
return spot if diff.negative?
34-
35-
if spot[:first_column] < diff
36-
snippet = snippet[0...snippet.size - diff]
37-
{
38-
**spot,
39-
snippet: snippet + ellipsis + "\n",
40-
last_column: [spot[:last_column], snippet.size].min
41-
}
42-
else
43-
{
44-
**spot,
45-
snippet: ellipsis + snippet[diff..-1],
46-
first_column: spot[:first_column] - (diff - ellipsis.size),
47-
last_column: spot[:last_column] - (diff - ellipsis.size)
48-
}
49-
end
50-
end
51-
5243
def self.terminal_columns
53-
# lazy load io/console in case viewport_size is set
44+
# lazy load io/console, so it's not loaded when viewport_size is set
5445
require "io/console"
5546
IO.console.winsize[1]
5647
end

test/test_error_highlight.rb

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
require "tempfile"
66

77
class ErrorHighlightTest < Test::Unit::TestCase
8+
ErrorHighlight::DefaultFormatter.viewport_size = 80
9+
810
class DummyFormatter
911
def self.message_for(corrections)
1012
""
1113
end
1214
end
1315

1416
def setup
15-
ErrorHighlight::DefaultFormatter.viewport_size = 80
16-
1717
if defined?(DidYouMean)
1818
@did_you_mean_old_formatter = DidYouMean.formatter
1919
DidYouMean.formatter = DummyFormatter
@@ -1287,27 +1287,64 @@ def test_no_final_newline
12871287
end
12881288
end
12891289

1290-
def test_errors_on_small_viewports_when_error_lives_at_the_end
1290+
def test_errors_on_small_viewports_at_the_end
1291+
assert_error_message(NoMethodError, <<~END) do
1292+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1293+
1294+
...0000000000000000000000000000000000000000000000000000000000000000 + 1.time {}
1295+
^^^^^
1296+
END
1297+
1298+
100000000000000000000000000000000000000000000000000000000000000000000000000000 + 1.time {}
1299+
end
1300+
end
1301+
1302+
def test_errors_on_small_viewports_at_the_beginning
1303+
assert_error_message(NoMethodError, <<~END) do
1304+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1305+
1306+
1.time { 10000000000000000000000000000000000000000000000000000000000000...
1307+
^^^^^
1308+
END
1309+
1310+
1.time { 100000000000000000000000000000000000000000000000000000000000000000000000000000 }
1311+
1312+
end
1313+
end
1314+
1315+
def test_errors_on_small_viewports_at_the_middle
1316+
assert_error_message(NoMethodError, <<~END) do
1317+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1318+
1319+
...000000000000000000000000000000000 + 1.time { 10000000000000000000000000000...
1320+
^^^^^
1321+
END
1322+
1323+
100000000000000000000000000000000000000 + 1.time { 100000000000000000000000000000000000000 }
1324+
end
1325+
end
1326+
1327+
def test_errors_on_small_viewports_when_larger_than_viewport
12911328
assert_error_message(NoMethodError, <<~END) do
1292-
undefined method 'gsuub' for an instance of String
1329+
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }
12931330
1294-
...ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo".gsuub(//, "")
1295-
^^^^^^
1331+
1.timesssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss...
1332+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12961333
END
12971334

1298-
"fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo".gsuub(//, "")
1335+
1.timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!
12991336
end
13001337
end
13011338

1302-
def test_errors_on_small_viewports_when_error_lives_at_the_beginning
1339+
def test_errors_on_small_viewports_when_exact_size_of_viewport
13031340
assert_error_message(NoMethodError, <<~END) do
1304-
undefined method 'gsuub' for an instance of Integer
1341+
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }
13051342
1306-
1.gsuub(//, "fooooooooooooooooooooooooooooooooooooooooooooooooooooooooo...
1307-
^^^^^^
1343+
1.timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!...
1344+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13081345
END
13091346

1310-
1.gsuub(//, "fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")
1347+
1.timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss! * 1000
13111348
end
13121349
end
13131350

0 commit comments

Comments
 (0)