summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/error_highlight/formatter.rb47
-rw-r--r--test/error_highlight/test_error_highlight.rb83
2 files changed, 108 insertions, 22 deletions
diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb
index 4d5c82f460..0656a88ef2 100644
--- a/lib/error_highlight/formatter.rb
+++ b/lib/error_highlight/formatter.rb
@@ -1,5 +1,7 @@
module ErrorHighlight
class DefaultFormatter
+ MIN_SNIPPET_WIDTH = 20
+
def self.message_for(spot)
# currently only a one-line code snippet is supported
return "" unless spot[:first_lineno] == spot[:last_lineno]
@@ -7,22 +9,24 @@ module ErrorHighlight
snippet = spot[:snippet]
first_column = spot[:first_column]
last_column = spot[:last_column]
+ ellipsis = "..."
# truncate snippet to fit in the viewport
- if snippet.size > viewport_size
- visible_start = [first_column - viewport_size / 2, 0].max
- visible_end = visible_start + viewport_size
+ if snippet_max_width && snippet.size > snippet_max_width
+ available_width = snippet_max_width - ellipsis.size
+ center = first_column - snippet_max_width / 2
- # avoid centering the snippet when the error is at the end of the line
- visible_start = snippet.size - viewport_size if visible_end > snippet.size
+ visible_start = last_column < available_width ? 0 : [center, 0].max
+ visible_end = visible_start + snippet_max_width
+ visible_start = snippet.size - snippet_max_width if visible_end > snippet.size
- prefix = visible_start.positive? ? "..." : ""
- suffix = visible_end < snippet.size ? "..." : ""
+ prefix = visible_start.positive? ? ellipsis : ""
+ suffix = visible_end < snippet.size ? ellipsis : ""
snippet = prefix + snippet[(visible_start + prefix.size)...(visible_end - suffix.size)] + suffix
snippet << "\n" unless snippet.end_with?("\n")
- first_column = first_column - visible_start
+ first_column -= visible_start
last_column = [last_column - visible_start, snippet.size - 1].min
end
@@ -32,18 +36,31 @@ module ErrorHighlight
"\n\n#{ snippet }#{ marker }"
end
- def self.viewport_size
- Ractor.current[:__error_highlight_viewport_size__] ||= terminal_columns
+ def self.snippet_max_width
+ return if Ractor.current[:__error_highlight_max_snippet_width__] == :disabled
+
+ Ractor.current[:__error_highlight_max_snippet_width__] ||= terminal_width
end
- def self.viewport_size=(viewport_size)
- Ractor.current[:__error_highlight_viewport_size__] = viewport_size
+ def self.snippet_max_width=(width)
+ return Ractor.current[:__error_highlight_max_snippet_width__] = :disabled if width.nil?
+
+ width = width.to_i
+
+ if width < MIN_SNIPPET_WIDTH
+ warn "'snippet_max_width' adjusted to minimum value of #{MIN_SNIPPET_WIDTH}."
+ width = MIN_SNIPPET_WIDTH
+ end
+
+ Ractor.current[:__error_highlight_max_snippet_width__] = width
end
- def self.terminal_columns
- # lazy load io/console, so it's not loaded when viewport_size is set
+ def self.terminal_width
+ # lazy load io/console, so it's not loaded when snippet_max_width is set
require "io/console"
- IO.console.winsize[1]
+ STDERR.winsize[1] if STDERR.tty?
+ rescue LoadError, NoMethodError, SystemCallError
+ # do not truncate when window size is not available
end
end
diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb
index d00c04995d..be36fca260 100644
--- a/test/error_highlight/test_error_highlight.rb
+++ b/test/error_highlight/test_error_highlight.rb
@@ -5,7 +5,7 @@ require "did_you_mean"
require "tempfile"
class ErrorHighlightTest < Test::Unit::TestCase
- ErrorHighlight::DefaultFormatter.viewport_size = 80
+ ErrorHighlight::DefaultFormatter.snippet_max_width = 80
class DummyFormatter
def self.message_for(corrections)
@@ -1287,7 +1287,7 @@ undefined method `time' for #{ ONE_RECV_MESSAGE }
end
end
- def test_errors_on_small_viewports_at_the_end
+ def test_errors_on_small_terminal_window_at_the_end
assert_error_message(NoMethodError, <<~END) do
undefined method `time' for #{ ONE_RECV_MESSAGE }
@@ -1299,7 +1299,7 @@ undefined method `time' for #{ ONE_RECV_MESSAGE }
end
end
- def test_errors_on_small_viewports_at_the_beginning
+ def test_errors_on_small_terminal_window_at_the_beginning
assert_error_message(NoMethodError, <<~END) do
undefined method `time' for #{ ONE_RECV_MESSAGE }
@@ -1312,7 +1312,19 @@ undefined method `time' for #{ ONE_RECV_MESSAGE }
end
end
- def test_errors_on_small_viewports_at_the_middle
+ def test_errors_on_small_terminal_window_at_the_middle_near_beginning
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `time' for #{ ONE_RECV_MESSAGE }
+
+ 100000000000000000000000000000000000000 + 1.time { 1000000000000000000000...
+ ^^^^^
+ END
+
+ 100000000000000000000000000000000000000 + 1.time { 100000000000000000000000000000000000000 }
+ end
+ end
+
+ def test_errors_on_small_terminal_window_at_the_middle
assert_error_message(NoMethodError, <<~END) do
undefined method `time' for #{ ONE_RECV_MESSAGE }
@@ -1320,11 +1332,68 @@ undefined method `time' for #{ ONE_RECV_MESSAGE }
^^^^^
END
- 100000000000000000000000000000000000000 + 1.time { 100000000000000000000000000000000000000 }
+ 10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
+ end
+ end
+
+ def test_errors_on_extremely_small_terminal_window
+ custom_max_width = 30
+ original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
+
+ ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `time' for #{ ONE_RECV_MESSAGE }
+
+...00000000 + 1.time { 1000...
+ ^^^^^
+ END
+
+ 100000000000000 + 1.time { 100000000000000 }
+ end
+ ensure
+ ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
+ end
+
+ def test_errors_on_terminal_window_smaller_than_min_width
+ custom_max_width = 5
+ original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
+
+ ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `time' for #{ ONE_RECV_MESSAGE }
+
+...000 + 1.time {...
+ ^^^^^
+ END
+
+ 100000000000000 + 1.time { 100000000000000 }
+ end
+ ensure
+ ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
+ end
+
+ def test_errors_on_terminal_window_when_truncation_is_disabled
+ custom_max_width = nil
+ original_max_width = ErrorHighlight::DefaultFormatter.snippet_max_width
+
+ ErrorHighlight::DefaultFormatter.snippet_max_width = custom_max_width
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `time' for #{ ONE_RECV_MESSAGE }
+
+ 10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
+ ^^^^^
+ END
+
+ 10000000000000000000000000000000000000000000000000000000000000000000000 + 1.time { 1000000000000000000000000000000 }
end
+ ensure
+ ErrorHighlight::DefaultFormatter.snippet_max_width = original_max_width
end
- def test_errors_on_small_viewports_when_larger_than_viewport
+ def test_errors_on_small_terminal_window_when_larger_than_viewport
assert_error_message(NoMethodError, <<~END) do
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }
@@ -1336,7 +1405,7 @@ undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssss
end
end
- def test_errors_on_small_viewports_when_exact_size_of_viewport
+ def test_errors_on_small_terminal_window_when_exact_size_of_viewport
assert_error_message(NoMethodError, <<~END) do
undefined method `timessssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss!' for #{ ONE_RECV_MESSAGE }