# `Drafter.Widget.Pretty`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/widget/pretty.ex#L1)

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` - `t:boolean/0`, put one entry per line. Default `false`.
  * `:syntax_highlighting` - `t:boolean/0`, colour the tokens. Default `true`.
  * `:style` - `t: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` - `t: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)

# `t`

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

# `component_tag`

```elixir
@spec component_tag() :: :pretty
```

The component tag this widget registers under.

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

# `focused`

# `format_keyword`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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:t/0`. The widget is not focusable.

# `mount`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec render(t() | Drafter.Widget.props(), Drafter.Widget.rect()) :: [
  Drafter.Draw.Strip.t()
]
```

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`

```elixir
@spec syntax_colors() :: %{required(atom()) =&gt; {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`

# `update`

```elixir
@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`

```elixir
@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}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
