diff options
-rw-r--r-- | lib/error_highlight/formatter.rb | 47 | ||||
-rw-r--r-- | test/error_highlight/test_error_highlight.rb | 83 |
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 } |