Drafter.Widget.Pretty (drafter v0.3.2)

Copy Markdown View Source

Renders an Elixir term with syntax-highlighted pretty-printing.

Maps and keyword lists print an atom key as :key: value and every other key as key => value. Structs are displayed with the last segment of their module name. The :expand option forces multi-line output with one entry per line.

Collections nested inside a collection are not descended into: they print as .... Only nil, booleans, atoms, integers, floats, binaries, lists, keyword lists, maps and structs can be rendered at the top level — any other term, a tuple or a pid among them, raises FunctionClauseError from format_pretty/3.

Component tag

Tag :pretty, built by Drafter.App as {:pretty, data, opts}:

pretty(data, opts)

The positional argument becomes :data directly; it is never re-read from opts. Pass the term positionally. Writing pretty(data: term) puts a keyword list in the positional slot, which the renderer then treats as options, leaving :data as nil and rendering nothing.

Options

  • :data - the term to display. Default nil, which renders the text nil. Supplied positionally through the pretty/2 element.
  • :expand - boolean/0, put one entry per line. Default false.
  • :syntax_highlighting - boolean/0, colour the tokens. Default true.
  • :style - map/0 of style overrides passed to the theme computation. Default %{}.
  • :class - theme class atom or list of them, normalised by Drafter.Style.normalize_classes/1 and reaching mount/1 as :classes. Default [].
  • :height - pos_integer/0 read only by preferred_height/2, never by mount/1. Default 5.

update/2 accepts every option above. Through the component tree only :data is live-updatable — update_props_from_mount/3 returns :data and :app_module alone, so :expand, :syntax_highlighting, :style and :classes are mount-only.

Usage

pretty(%{name: "Alice", age: 30, active: true})
pretty(my_struct, expand: true)

Summary

Functions

The component tag this widget registers under.

Formats a keyword list. Keys are written with a leading colon and a trailing colon, so [a: 1] prints as [:a: 1].

Formats a plain list. Each element goes through format_simple/2, so a nested collection prints as ....

Formats a map that is not a struct, one format_pair/3 per entry in key order.

Formats one map entry.

Formats data into the widget's marked-up text.

Formats a single value nested inside a collection.

Formats a struct, headed by the last segment of its module name.

Builds the props map for a {:pretty, data, opts} element.

Ignores every event and returns {:noreply, state}.

Builds the widget state from props.

The {r, g, b} colour for a "{token_kind}" marker.

The number of rows the element asks for: opts[:height], default 5.

Draws the formatted term into rect, one strip per line of output.

The token colour table, keyed by token kind.

Callback implementation for Drafter.Widget.unmount/1.

Replaces the state fields named in props, keeping the current value for any key that is absent.

Narrows a re-render to :data and :app_module.

Types

t()

@type t() :: %Drafter.Widget.Pretty{
  app_module: module() | nil,
  classes: [atom()],
  data: term(),
  expand: boolean(),
  style: map(),
  syntax_highlighting: boolean()
}

Functions

component_tag()

@spec component_tag() :: :pretty

The component tag this widget registers under.

iex> Drafter.Widget.Pretty.component_tag()
:pretty

focused(state)

format_keyword(data, highlight, bool)

@spec format_keyword(keyword(), boolean(), boolean()) :: String.t()

Formats a keyword list. Keys are written with a leading colon and a trailing colon, so [a: 1] prints as [:a: 1].

iex> Drafter.Widget.Pretty.format_keyword([a: 1, b: 2], false, false)
"[:a: 1, :b: 2]"

iex> Drafter.Widget.Pretty.format_keyword([a: 1], false, true)
"[\n  :a: 1\n]"

format_list(data, highlight, bool)

@spec format_list(list(), boolean(), boolean()) :: String.t()

Formats a plain list. Each element goes through format_simple/2, so a nested collection prints as ....

iex> Drafter.Widget.Pretty.format_list([1, 2], false, false)
"[1, 2]"

iex> Drafter.Widget.Pretty.format_list([1, 2], false, true)
"[\n  1,\n  2\n]"

format_map(data, highlight, bool)

@spec format_map(map(), boolean(), boolean()) :: String.t()

Formats a map that is not a struct, one format_pair/3 per entry in key order.

iex> Drafter.Widget.Pretty.format_map(%{a: 1}, false, false)
"%{:a: 1}"

iex> Drafter.Widget.Pretty.format_map(%{a: 1}, false, true)
"%{\n  :a: 1\n}"

format_pair(k, v, highlight)

@spec format_pair(term(), term(), boolean()) :: String.t()

Formats one map entry.

An atom key becomes :key: value; every other key becomes key => value.

iex> Drafter.Widget.Pretty.format_pair(:a, 1, false)
":a: 1"

iex> Drafter.Widget.Pretty.format_pair("k", 1, false)
"\"k\" => 1"

format_pretty(data, highlight, expand)

@spec format_pretty(term(), boolean(), boolean()) :: String.t()

Formats data into the widget's marked-up text.

With highlight set, each token is followed by a §{kind} marker that render/2 turns into a colour; without it the result is plain text. expand puts one entry of a collection per line.

Handles nil, booleans, atoms, integers, floats, binaries, lists, keyword lists, maps and structs. Any other term raises FunctionClauseError.

iex> Drafter.Widget.Pretty.format_pretty(nil, false, false)
"nil"

iex> Drafter.Widget.Pretty.format_pretty(42, true, false)
"42§{integer}"

iex> Drafter.Widget.Pretty.format_pretty("hi", false, false)
"\"hi\""

iex> Drafter.Widget.Pretty.format_pretty([1, 2, 3], false, false)
"[1, 2, 3]"

iex> Drafter.Widget.Pretty.format_pretty([a: 1, b: 2], false, false)
"[:a: 1, :b: 2]"

iex> Drafter.Widget.Pretty.format_pretty(%{a: 1}, false, false)
"%{:a: 1}"

iex> Drafter.Widget.Pretty.format_pretty(%{"k" => 1}, false, false)
"%{\"k\" => 1}"

iex> Drafter.Widget.Pretty.format_pretty([1, [2]], false, false)
"[1, ...]"

format_simple(item, highlight)

@spec format_simple(term(), boolean()) :: String.t()

Formats a single value nested inside a collection.

Handles nil, booleans, atoms, integers, floats and binaries. Anything else, including a nested list, map or tuple, returns "..." rather than recursing.

iex> Drafter.Widget.Pretty.format_simple(:ok, false)
":ok"

iex> Drafter.Widget.Pretty.format_simple(:ok, true)
":ok§{atom}"

iex> Drafter.Widget.Pretty.format_simple([1, 2], false)
"..."

format_struct(data, highlight, bool)

@spec format_struct(struct(), boolean(), boolean()) :: String.t()

Formats a struct, headed by the last segment of its module name.

Field names go through format_simple/2, so they carry a leading colon.

iex> Drafter.Widget.Pretty.format_struct(1..3, false, false)
"%Range{:first: 1, :last: 3, :step: 1}"

iex> Drafter.Widget.Pretty.format_struct(1..2//1, false, true)
"%Range{\n  :first: 1,\n  :last: 2,\n  :step: 1\n}"

from_component_opts(data, opts)

@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()

Builds the props map for a {:pretty, data, opts} element.

data becomes :data as it stands and is never re-read from opts, so pretty(data: term) leaves :data as the keyword list itself only if the renderer passes it positionally. :class is normalised into :classes and :__app_module__ becomes :app_module.

iex> props = Drafter.Widget.Pretty.from_component_opts(%{a: 1}, expand: true)
iex> {props.data, props.expand, props.syntax_highlighting, props.classes}
{%{a: 1}, true, true, []}

handle_event(event, state)

@spec handle_event(Drafter.Event.t(), t() | Drafter.Widget.props()) :: {:noreply, t()}

Ignores every event and returns {:noreply, state}.

A plain props map is passed through mount/1 first, so the returned state is always a t/0. The widget is not focusable.

mount(props)

@spec mount(Drafter.Widget.props()) :: t()

Builds the widget state from props.

Reads :data (default nil), :expand (default false), :syntax_highlighting (default true), :style (default %{}), :classes (default []) and :app_module (default nil).

iex> state = Drafter.Widget.Pretty.mount(%{data: %{a: 1}})
iex> {state.data, state.expand, state.syntax_highlighting}
{%{a: 1}, false, true}

parse_color_spec(arg)

@spec parse_color_spec(String.t()) :: {0..255, 0..255, 0..255}

The {r, g, b} colour for a "{token_kind}" marker.

spec must start with { — anything else raises FunctionClauseError. An unknown token kind falls back to the :default colour, {200, 200, 200}.

iex> Drafter.Widget.Pretty.parse_color_spec("{integer}")
{181, 206, 168}

iex> Drafter.Widget.Pretty.parse_color_spec("{not_a_token}")
{200, 200, 200}

preferred_height(args, opts)

@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()

The number of rows the element asks for: opts[:height], default 5.

iex> Drafter.Widget.Pretty.preferred_height(nil, [])
5

iex> Drafter.Widget.Pretty.preferred_height(%{a: 1}, height: 12)
12

render(state, rect)

Draws the formatted term into rect, one strip per line of output.

state may be a plain props map, in which case it is passed through mount/1 first. Each line is padded with spaces or truncated to rect.width. The number of strips follows the formatted term, not rect.height.

syntax_colors()

@spec syntax_colors() :: %{required(atom()) => {0..255, 0..255, 0..255}}

The token colour table, keyed by token kind.

iex> Drafter.Widget.Pretty.syntax_colors() |> Map.keys() |> Enum.sort()
[:atom, :boolean, :default, :float, :integer, :keyword_key, :map_key, nil, :separator, :string, :struct_name]

iex> Drafter.Widget.Pretty.syntax_colors().string
{235, 203, 139}

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

@spec update(Drafter.Widget.props(), t()) :: t()

Replaces the state fields named in props, keeping the current value for any key that is absent.

Accepts :data, :expand, :syntax_highlighting, :style, :classes and :app_module. A :data of nil in props counts as a value and clears the term.

iex> state = Drafter.Widget.Pretty.mount(%{data: 1})
iex> updated = Drafter.Widget.Pretty.update(%{expand: true}, state)
iex> {updated.data, updated.expand}
{1, true}

update_props_from_mount(mount_props, existing_state, opts)

@spec update_props_from_mount(Drafter.Widget.props(), term(), keyword()) ::
  Drafter.Widget.props()

Narrows a re-render to :data and :app_module.

:expand, :syntax_highlighting, :style and :classes are dropped, so they are mount-only through the component tree.

iex> props = Drafter.Widget.Pretty.from_component_opts(%{a: 1}, expand: true)
iex> Drafter.Widget.Pretty.update_props_from_mount(props, %{}, [])
%{data: %{a: 1}, app_module: nil}