Improve errors formatting (#936)

* Use monospaced font and wrapping for errors

* Improve function clause error format
This commit is contained in:
Jonatan Kłosko 2022-01-25 21:55:24 +01:00 committed by GitHub
parent 6b19f1d71b
commit 00c2cfb31a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 23 deletions

View file

@ -349,24 +349,18 @@ defmodule Livebook.Evaluator do
defp eval(code, binding, env) do
try do
quoted = Code.string_to_quoted!(code)
quoted = Code.string_to_quoted!(code, file: env.file)
# TODO: Use Code.eval_quoted_with_env/3 on Elixir v1.14
{result, binding, env} = :elixir.eval_quoted(quoted, binding, env)
{:ok, result, binding, env}
catch
kind, error ->
{kind, error, stacktrace} = prepare_error(kind, error, __STACKTRACE__)
stacktrace = prune_stacktrace(__STACKTRACE__)
{:error, kind, error, stacktrace}
end
end
defp prepare_error(kind, error, stacktrace) do
{error, stacktrace} = Exception.blame(kind, error, stacktrace)
stacktrace = prune_stacktrace(stacktrace)
{kind, error, stacktrace}
end
# Adapted from https://github.com/elixir-lang/elixir/blob/1c1654c88adfdbef38ff07fc30f6fbd34a542c07/lib/iex/lib/iex/evaluator.ex#L355-L372
@elixir_internals [:elixir, :elixir_expand, :elixir_compiler, :elixir_module] ++

View file

@ -26,7 +26,7 @@ defmodule Livebook.Evaluator.DefaultFormatter do
end
def format_response({:error, kind, error, stacktrace}) do
formatted = Exception.format(kind, error, stacktrace)
formatted = format_error(kind, error, stacktrace)
{:error, formatted, error_type(error)}
end
@ -61,12 +61,7 @@ defmodule Livebook.Evaluator.DefaultFormatter do
end
end
defp format_error(kind, error, stacktrace) do
{error, stacktrace} = Exception.blame(kind, error, stacktrace)
Exception.format(kind, error, stacktrace)
end
defp inspect_opts(opts) do
defp inspect_opts(opts \\ []) do
default_opts = [pretty: true, width: 100, syntax_colors: syntax_colors()]
Keyword.merge(default_opts, opts)
end
@ -103,4 +98,47 @@ defmodule Livebook.Evaluator.DefaultFormatter do
Exception.message(exception) =~
"Mix.install/2 can only be called with the same dependencies"
end
defp format_error(kind, error, stacktrace) do
{blamed, stacktrace} = Exception.blame(kind, error, stacktrace)
banner =
case blamed do
%FunctionClauseError{} ->
banner = Exception.format_banner(kind, error, stacktrace)
blame = FunctionClauseError.blame(blamed, &inspect(&1, inspect_opts()), &blame_match/1)
[error_color(banner), pad(blame)]
_ ->
banner = Exception.format_banner(kind, blamed, stacktrace)
error_color(banner)
end
message =
if stacktrace == [] do
banner
else
stacktrace = Exception.format_stacktrace(stacktrace)
[banner, "\n", error_color(stacktrace)]
end
IO.iodata_to_binary(message)
end
defp blame_match(%{match?: true, node: node}), do: Macro.to_string(node)
defp blame_match(%{match?: false, node: node}) do
node
|> Macro.to_string()
|> error_color()
|> IO.iodata_to_binary()
end
defp pad(string) do
" " <> String.replace(string, "\n", "\n ")
end
defp error_color(string) do
IO.ANSI.format([:red, string], true)
end
end

View file

@ -1,6 +1,8 @@
defmodule LivebookWeb.Output do
use Phoenix.Component
import LivebookWeb.Helpers
alias LivebookWeb.Output
@doc """
@ -93,7 +95,7 @@ defmodule LivebookWeb.Output do
~H"""
<div class="flex flex-col space-y-4">
<%= render_error_message_output(@formatted) %>
<%= render_error(@formatted) %>
<%= if @is_standalone do %>
<div>
<button class="button-base button-gray" phx-click="restart_runtime">
@ -112,7 +114,7 @@ defmodule LivebookWeb.Output do
end
defp render_output({:error, formatted, _type}, %{}) do
render_error_message_output(formatted)
render_error(formatted)
end
# TODO: remove on Livebook v0.7
@ -123,24 +125,32 @@ defmodule LivebookWeb.Output do
:table_dynamic,
:frame_dynamic
] do
render_error_message_output("""
render_error_message("""
Legacy output format: #{inspect(output)}. Please update Kino to
the latest version.
""")
end
defp render_output(output, %{}) do
render_error_message_output("""
render_error_message("""
Unknown output format: #{inspect(output)}. If you're using Kino,
you may want to update Kino and Livebook to the latest version.
""")
end
defp render_error_message_output(message) do
defp render_error(message) do
assigns = %{message: message}
~H"""
<div class="overflow-auto whitespace-pre text-red-600 tiny-scrollbar"><%= @message %></div>
<div class="whitespace-pre-wrap font-editor text-gray-500"><%= ansi_string_to_html(@message) %></div>
"""
end
defp render_error_message(message) do
assigns = %{message: message}
~H"""
<div class="whitespace-pre-wrap font-editor text-red-600"><%= @message %></div>
"""
end
end

View file

@ -94,8 +94,8 @@ defmodule Livebook.EvaluatorTest do
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
assert_receive {:evaluation_response, :code_1,
{:error, :error, %FunctionClauseError{},
[{List, :first, _arity, _location}]}, metadata()}
{:error, :error, :function_clause, [{List, :first, _arity, _location}]},
metadata()}
end
test "in case of an error returns only the relevant part of stacktrace",