mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-12-26 17:33:44 +08:00
Use inspect to highlight cell result (#18)
* Use inspect to highlight cell result * Use invalid UTF-8 byte for color separation * Match editor typography in cell output
This commit is contained in:
parent
77b60c8110
commit
a8b5227dd6
5 changed files with 138 additions and 5 deletions
|
@ -116,12 +116,59 @@ iframe[hidden] {
|
|||
@apply mb-0;
|
||||
}
|
||||
|
||||
/* Elixir HTML-ized inspect result */
|
||||
|
||||
.elixir-inspect .atom {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.elixir-inspect .binary {
|
||||
color: #5c6370;
|
||||
}
|
||||
|
||||
.elixir-inspect .boolean {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.elixir-inspect .list {
|
||||
color: #5c6370;
|
||||
}
|
||||
|
||||
.elixir-inspect .map {
|
||||
color: #5c6370;
|
||||
}
|
||||
|
||||
.elixir-inspect .nil {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.elixir-inspect .number {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.elixir-inspect .regex {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.elixir-inspect .string {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.elixir-inspect .tuple {
|
||||
color: #5c6370;
|
||||
}
|
||||
|
||||
/* Other */
|
||||
|
||||
.bg-editor {
|
||||
background-color: #282c34;
|
||||
}
|
||||
|
||||
.font-editor {
|
||||
font-family: "Droid Sans Mono", monospace, monospace, "Droid Sans Fallback";
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tiny-scrollbar::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule LiveBookWeb.Cell do
|
||||
use LiveBookWeb, :live_component
|
||||
|
||||
alias LiveBookWeb.Utils
|
||||
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<div id="cell-<%= @cell.id %>"
|
||||
|
@ -131,7 +133,7 @@ defmodule LiveBookWeb.Cell do
|
|||
assigns = %{outputs: outputs}
|
||||
|
||||
~L"""
|
||||
<div class="flex flex-col rounded-md border border-gray-200 divide-y divide-gray-200 text-sm">
|
||||
<div class="flex flex-col rounded-md border border-gray-200 divide-y divide-gray-200 font-editor">
|
||||
<%= for output <- Enum.reverse(@outputs) do %>
|
||||
<div class="p-4">
|
||||
<div class="max-h-80 overflow-auto tiny-scrollbar">
|
||||
|
@ -152,11 +154,12 @@ defmodule LiveBookWeb.Cell do
|
|||
end
|
||||
|
||||
defp render_output({:ok, value}) do
|
||||
inspected = inspect(value, pretty: true, width: 140)
|
||||
inspected = Utils.inspect_as_html(value, pretty: true, width: 140)
|
||||
|
||||
assigns = %{inspected: inspected}
|
||||
|
||||
~L"""
|
||||
<div class="whitespace-pre text-gray-500"><%= @inspected %></div>
|
||||
<div class="whitespace-pre text-gray-500 elixir-inspect"><%= @inspected %></div>
|
||||
"""
|
||||
end
|
||||
|
||||
|
|
63
lib/live_book_web/utils.ex
Normal file
63
lib/live_book_web/utils.ex
Normal file
|
@ -0,0 +1,63 @@
|
|||
defmodule LiveBookWeb.Utils do
|
||||
@doc """
|
||||
Wraps `inspect/2` to include HTML tags in the final string for syntax highlighting.
|
||||
|
||||
Any options given as the second argument are passed directly to `inspect/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex(2)> LiveBookWeb.Utils.inspect_as_html(:test, [])
|
||||
{:safe, "<span class=\\"atom\\">:test</span>"}
|
||||
"""
|
||||
@spec inspect_as_html(Inspect.t(), keyword()) :: Phoenix.HTML.safe()
|
||||
def inspect_as_html(term, opts \\ []) do
|
||||
# Inspect coloring primary tragets terminals,
|
||||
# so the colors are usually ANSI escape codes
|
||||
# and their effect is reverted by a special reset escape code.
|
||||
#
|
||||
# In our case we need HTML tags for syntax highlighting,
|
||||
# so as the colors we use sequences like \xfeatom\xfe
|
||||
# then we HTML-escape the string and finally replace
|
||||
# these special sequences with actual <span> tags.
|
||||
#
|
||||
# Note that the surrounding \xfe byte is invalid in a UTF-8 sequence,
|
||||
# so we can be certain it won't appear in the normal `inspect` result.
|
||||
|
||||
term
|
||||
|> inspect(Keyword.merge(opts, syntax_colors: inspect_html_colors()))
|
||||
|> Phoenix.HTML.html_escape()
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|> replace_colors_with_tags()
|
||||
|> Phoenix.HTML.raw()
|
||||
end
|
||||
|
||||
defp inspect_html_colors() do
|
||||
delim = "\xfe"
|
||||
|
||||
[
|
||||
atom: delim <> "atom" <> delim,
|
||||
binary: delim <> "binary" <> delim,
|
||||
boolean: delim <> "boolean" <> delim,
|
||||
list: delim <> "list" <> delim,
|
||||
map: delim <> "map" <> delim,
|
||||
number: delim <> "number" <> delim,
|
||||
nil: delim <> "nil" <> delim,
|
||||
regex: delim <> "regex" <> delim,
|
||||
string: delim <> "string" <> delim,
|
||||
tuple: delim <> "tuple" <> delim,
|
||||
reset: delim <> "reset" <> delim
|
||||
]
|
||||
end
|
||||
|
||||
defp replace_colors_with_tags(string) do
|
||||
colors = inspect_html_colors()
|
||||
|
||||
Enum.reduce(colors, string, fn
|
||||
{:reset, color}, string ->
|
||||
String.replace(string, color, "</span>")
|
||||
|
||||
{key, color}, string ->
|
||||
String.replace(string, color, "<span class=\"#{Atom.to_string(key)}\">")
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -92,7 +92,8 @@ defmodule LiveBookWeb.SessionLiveTest do
|
|||
Session.get_data(session_id)
|
||||
end
|
||||
|
||||
test "queueing cell evaluation updates the shared state", %{conn: conn, session_id: session_id} do
|
||||
test "queueing cell evaluation updates the shared state",
|
||||
%{conn: conn, session_id: session_id} do
|
||||
section_id = insert_section(session_id)
|
||||
cell_id = insert_cell(session_id, section_id, :elixir, "Process.sleep(5)")
|
||||
|
||||
|
@ -108,7 +109,6 @@ defmodule LiveBookWeb.SessionLiveTest do
|
|||
|
||||
test "queueing cell evaluation defaults to the focused cell if no cell id is given",
|
||||
%{conn: conn, session_id: session_id} do
|
||||
|
||||
section_id = insert_section(session_id)
|
||||
cell_id = insert_cell(session_id, section_id, :elixir, "Process.sleep(5)")
|
||||
|
||||
|
|
20
test/live_book_web/utils_test.exs
Normal file
20
test/live_book_web/utils_test.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule LiveBookWeb.UtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias LiveBookWeb.Utils
|
||||
|
||||
doctest Utils
|
||||
|
||||
describe "inspect_as_html/2" do
|
||||
test "uses span tags for term highlighting" do
|
||||
assert {:safe,
|
||||
~s{<span class="list">[</span><span class="number">1</span><span class="list">,</span> <span class="number">2</span><span class="list">]</span>}} ==
|
||||
Utils.inspect_as_html([1, 2])
|
||||
end
|
||||
|
||||
test "escapes HTML in the inspect result" do
|
||||
assert {:safe, ~s{<span class="string">"1 < 2"</span>}} ==
|
||||
Utils.inspect_as_html("1 < 2")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue