mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-09 13:07:37 +08:00
Improve errors formatting (#936)
* Use monospaced font and wrapping for errors * Improve function clause error format
This commit is contained in:
parent
6b19f1d71b
commit
00c2cfb31a
4 changed files with 65 additions and 23 deletions
|
@ -349,24 +349,18 @@ defmodule Livebook.Evaluator do
|
||||||
|
|
||||||
defp eval(code, binding, env) do
|
defp eval(code, binding, env) do
|
||||||
try 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
|
# TODO: Use Code.eval_quoted_with_env/3 on Elixir v1.14
|
||||||
{result, binding, env} = :elixir.eval_quoted(quoted, binding, env)
|
{result, binding, env} = :elixir.eval_quoted(quoted, binding, env)
|
||||||
|
|
||||||
{:ok, result, binding, env}
|
{:ok, result, binding, env}
|
||||||
catch
|
catch
|
||||||
kind, error ->
|
kind, error ->
|
||||||
{kind, error, stacktrace} = prepare_error(kind, error, __STACKTRACE__)
|
stacktrace = prune_stacktrace(__STACKTRACE__)
|
||||||
{:error, kind, error, stacktrace}
|
{:error, kind, error, stacktrace}
|
||||||
end
|
end
|
||||||
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
|
# 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] ++
|
@elixir_internals [:elixir, :elixir_expand, :elixir_compiler, :elixir_module] ++
|
||||||
|
|
|
@ -26,7 +26,7 @@ defmodule Livebook.Evaluator.DefaultFormatter do
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_response({:error, kind, error, stacktrace}) do
|
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)}
|
{:error, formatted, error_type(error)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,12 +61,7 @@ defmodule Livebook.Evaluator.DefaultFormatter do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_error(kind, error, stacktrace) do
|
defp inspect_opts(opts \\ []) do
|
||||||
{error, stacktrace} = Exception.blame(kind, error, stacktrace)
|
|
||||||
Exception.format(kind, error, stacktrace)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp inspect_opts(opts) do
|
|
||||||
default_opts = [pretty: true, width: 100, syntax_colors: syntax_colors()]
|
default_opts = [pretty: true, width: 100, syntax_colors: syntax_colors()]
|
||||||
Keyword.merge(default_opts, opts)
|
Keyword.merge(default_opts, opts)
|
||||||
end
|
end
|
||||||
|
@ -103,4 +98,47 @@ defmodule Livebook.Evaluator.DefaultFormatter do
|
||||||
Exception.message(exception) =~
|
Exception.message(exception) =~
|
||||||
"Mix.install/2 can only be called with the same dependencies"
|
"Mix.install/2 can only be called with the same dependencies"
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule LivebookWeb.Output do
|
defmodule LivebookWeb.Output do
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
|
||||||
|
import LivebookWeb.Helpers
|
||||||
|
|
||||||
alias LivebookWeb.Output
|
alias LivebookWeb.Output
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -93,7 +95,7 @@ defmodule LivebookWeb.Output do
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<%= render_error_message_output(@formatted) %>
|
<%= render_error(@formatted) %>
|
||||||
<%= if @is_standalone do %>
|
<%= if @is_standalone do %>
|
||||||
<div>
|
<div>
|
||||||
<button class="button-base button-gray" phx-click="restart_runtime">
|
<button class="button-base button-gray" phx-click="restart_runtime">
|
||||||
|
@ -112,7 +114,7 @@ defmodule LivebookWeb.Output do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_output({:error, formatted, _type}, %{}) do
|
defp render_output({:error, formatted, _type}, %{}) do
|
||||||
render_error_message_output(formatted)
|
render_error(formatted)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: remove on Livebook v0.7
|
# TODO: remove on Livebook v0.7
|
||||||
|
@ -123,24 +125,32 @@ defmodule LivebookWeb.Output do
|
||||||
:table_dynamic,
|
:table_dynamic,
|
||||||
:frame_dynamic
|
:frame_dynamic
|
||||||
] do
|
] do
|
||||||
render_error_message_output("""
|
render_error_message("""
|
||||||
Legacy output format: #{inspect(output)}. Please update Kino to
|
Legacy output format: #{inspect(output)}. Please update Kino to
|
||||||
the latest version.
|
the latest version.
|
||||||
""")
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_output(output, %{}) do
|
defp render_output(output, %{}) do
|
||||||
render_error_message_output("""
|
render_error_message("""
|
||||||
Unknown output format: #{inspect(output)}. If you're using Kino,
|
Unknown output format: #{inspect(output)}. If you're using Kino,
|
||||||
you may want to update Kino and Livebook to the latest version.
|
you may want to update Kino and Livebook to the latest version.
|
||||||
""")
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_error_message_output(message) do
|
defp render_error(message) do
|
||||||
assigns = %{message: message}
|
assigns = %{message: message}
|
||||||
|
|
||||||
~H"""
|
~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
|
||||||
end
|
end
|
||||||
|
|
|
@ -94,8 +94,8 @@ defmodule Livebook.EvaluatorTest do
|
||||||
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
|
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
|
||||||
|
|
||||||
assert_receive {:evaluation_response, :code_1,
|
assert_receive {:evaluation_response, :code_1,
|
||||||
{:error, :error, %FunctionClauseError{},
|
{:error, :error, :function_clause, [{List, :first, _arity, _location}]},
|
||||||
[{List, :first, _arity, _location}]}, metadata()}
|
metadata()}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "in case of an error returns only the relevant part of stacktrace",
|
test "in case of an error returns only the relevant part of stacktrace",
|
||||||
|
|
Loading…
Add table
Reference in a new issue