Skip to content

Commit c565340

Browse files
karreiromame
authored andcommitted
Adjust truncation, add opt-out mechanism, rename methods, and prepare error highlighting to render on extremely small screens
1 parent 0657bc1 commit c565340

File tree

2 files changed

+108
-22
lines changed

2 files changed

+108
-22
lines changed

lib/error_highlight/formatter.rb

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
module ErrorHighlight
22
class DefaultFormatter
3+
MIN_SNIPPET_WIDTH = 20
4+
35
def self.message_for(spot)
46
# currently only a one-line code snippet is supported
57
return "" unless spot[:first_lineno] == spot[:last_lineno]
68

79
snippet = spot[:snippet]
810
first_column = spot[:first_column]
911
last_column = spot[:last_column]
12+
ellipsis = "..."
1013

1114
# 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+
if snippet_max_width && snippet.size > snippet_max_width
16+
available_width = snippet_max_width - ellipsis.size
17+
center = first_column - snippet_max_width / 2
1518

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
19+
visible_start = last_column < available_width ? 0 : [center, 0].max
20+
visible_end = visible_start + snippet_max_width
21+
visible_start = snippet.size - snippet_max_width if visible_end > snippet.size
1822

19-
prefix = visible_start.positive? ? "..." : ""
20-
suffix = visible_end < snippet.size ? "..." : ""
23+
prefix = visible_start.positive? ? ellipsis : ""
24+
suffix = visible_end < snippet.size ? ellipsis : ""
2125

2226
snippet = prefix + snippet[(visible_start + prefix.size)...(visible_end - suffix.size)] + suffix
2327
snippet << "\n" unless snippet.end_with?("\n")
2428

25-
first_column = first_column - visible_start
29+
first_column -= visible_start
2630
last_column = [last_column - visible_start, snippet.size - 1].min
2731
end
2832

@@ -32,18 +36,31 @@ def self.message_for(spot)
3236
"\n\n#{ snippet }#{ marker }"
3337
end
3438

35-
def self.viewport_size
36-
Ractor.current[:__error_highlight_viewport_size__] ||= terminal_columns
39+
def self.snippet_max_width
40+
return if Ractor.current[:__error_highlight_max_snippet_width__] == :disabled
41+
42+
Ractor.current[:__error_highlight_max_snippet_width__] ||= terminal_width
3743
end
3844

39-
def self.viewport_size=(viewport_size)
40-
Ractor.current[:__error_highlight_viewport_size__] = viewport_size
45+
def self.snippet_max_width=(width)
46+
return Ractor.current[:__error_highlight_max_snippet_width__] = :disabled if width.nil?
47+
48+
width = width.to_i
49+
50+
if width < MIN_SNIPPET_WIDTH
51+
warn "'snippet_max_width' adjusted to minimum value of #{MIN_SNIPPET_WIDTH}."
52+
width = MIN_SNIPPET_WIDTH
53+
end
54+
55+
Ractor.current[:__error_highlight_max_snippet_width__] = width
4156
end
4257

43-
def self.terminal_columns
44-
# lazy load io/console, so it's not loaded when viewport_size is set
58+
def self.terminal_width
59+
# lazy load io/console, so it's not loaded when snippet_max_width is set
4560
require "io/console"
46-
IO.console.winsize[1]
61+
STDERR.winsize[1] if STDERR.tty?
62+
rescue LoadError, NoMethodError, SystemCallError
63+
# do not truncate when window size is not available
4764
end
4865
end
4966

test/test_error_highlight.rb

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

77
class ErrorHighlightTest < Test::Unit::TestCase
8-
ErrorHighlight::DefaultFormatter.viewport_size = 80
8+
ErrorHighlight::DefaultFormatter.snippet_max_width = 80
99

1010
class DummyFormatter
1111
def self.message_for(corrections)
@@ -1287,7 +1287,7 @@ def test_no_final_newline
12871287
end
12881288
end
12891289

1290-
def test_errors_on_small_viewports_at_the_end
1290+
def test_errors_on_small_terminal_window_at_the_end
12911291
assert_error_message(NoMethodError, <<~END) do
12921292
undefined method `time' for #{ ONE_RECV_MESSAGE }
12931293
@@ -1299,7 +1299,7 @@ def test_errors_on_small_viewports_at_the_end
12991299
end
13001300
end
13011301

1302-
def test_errors_on_small_viewports_at_the_beginning
1302+
def test_errors_on_small_terminal_window_at_the_beginning
13031303
assert_error_message(NoMethodError, <<~END) do
13041304
undefined method `time' for #{ ONE_RECV_MESSAGE }
13051305
@@ -1312,19 +1312,88 @@ def test_errors_on_small_viewports_at_the_beginning
13121312
end
13131313
end
13141314

1315-
def test_errors_on_small_viewports_at_the_middle
1315+
def test_errors_on_small_terminal_window_at_the_middle_near_beginning
1316+
assert_error_message(NoMethodError, <<~END) do
1317+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1318+
1319+
100000000000000000000000000000000000000 + 1.time { 1000000000000000000000...
1320+
^^^^^
1321+
END
1322+
1323+
100000000000000000000000000000000000000 + 1.time { 100000000000000000000000000000000000000 }
1324+
end
1325+
end
1326+
1327+
def test_errors_on_small_terminal_window_at_the_middle
13161328
assert_error_message(NoMethodError, <<~END) do
13171329
undefined method `time' for #{ ONE_RECV_MESSAGE }
13181330
13191331
...000000000000000000000000000000000 + 1.time { 10000000000000000000000000000...
13201332
^^^^^
13211333
END
13221334

1323-
100000000000000000000000000000000000000 + 1.time { 100000000000000000000000000000000000000 }
1335+
10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
1336+
end
1337+
end
1338+
1339+
def test_errors_on_extremely_small_terminal_window
1340+
custom_max_width = 30
1341+
original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
1342+
1343+
ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
1344+
1345+
assert_error_message(NoMethodError, <<~END) do
1346+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1347+
1348+
...00000000 + 1.time { 1000...
1349+
^^^^^
1350+
END
1351+
1352+
100000000000000 + 1.time { 100000000000000 }
1353+
end
1354+
ensure
1355+
ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
1356+
end
1357+
1358+
def test_errors_on_terminal_window_smaller_than_min_width
1359+
custom_max_width = 5
1360+
original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
1361+
1362+
ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
1363+
1364+
assert_error_message(NoMethodError, <<~END) do
1365+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1366+
1367+
...000 + 1.time {...
1368+
^^^^^
1369+
END
1370+
1371+
100000000000000 + 1.time { 100000000000000 }
1372+
end
1373+
ensure
1374+
ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
1375+
end
1376+
1377+
def test_errors_on_terminal_window_when_truncation_is_disabled
1378+
custom_max_width = nil
1379+
original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
1380+
1381+
ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
1382+
1383+
assert_error_message(NoMethodError, <<~END) do
1384+
undefined method `time' for #{ ONE_RECV_MESSAGE }
1385+
1386+
10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
1387+
^^^^^
1388+
END
1389+
1390+
10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
13241391
end
1392+
ensure
1393+
ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
13251394
end
13261395

1327-
def test_errors_on_small_viewports_when_larger_than_viewport
1396+
def test_errors_on_small_terminal_window_when_larger_than_viewport
13281397
assert_error_message(NoMethodError, <<~END) do
13291398
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }
13301399
@@ -1336,7 +1405,7 @@ def test_errors_on_small_viewports_when_larger_than_viewport
13361405
end
13371406
end
13381407

1339-
def test_errors_on_small_viewports_when_exact_size_of_viewport
1408+
def test_errors_on_small_terminal_window_when_exact_size_of_viewport
13401409
assert_error_message(NoMethodError, <<~END) do
13411410
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }
13421411

0 commit comments

Comments
 (0)