Drafter.Draw.Strip (drafter v0.3.1)

Copy Markdown View Source

A horizontal line of Drafter.Draw.Segment structs representing one terminal row.

Strips are the unit passed between widgets and the compositor. They track total display column width and a hash-based cache_key to skip redundant re-renders. Common operations include crop/2, pad/2, combine/2, divide/2, slice/3, center/2, and ANSI serialisation via to_ansi/1.

iex> alias Drafter.Draw.{Segment, Strip}
iex> strip = Strip.new([Segment.plain("ab"), Segment.new("cd", %{bold: true})])
iex> Strip.width(strip)
4
iex> Strip.to_ansi(strip)
"ab\e[1mcd\e[0m"

Summary

Functions

A strip with segment after the existing segments.

Merge style into every segment's style, style winning on shared keys.

The strip centred in target_width columns between unstyled space padding.

A strip whose segments are the first strip's followed by the second's.

Cut the strip down to crop_width display columns.

Split the strip into {left, right} at display column position.

A strip with no segments and zero width.

Whether the strip has no segments, or every segment has empty text.

The strip at exactly target_width columns, cropped or space-padded on the right.

A strip of one unstyled segment holding text.

A strip holding segments in order.

Narrow a strip to width.

Append an unstyled run of spaces until the strip is target_width columns wide, or return it unchanged if it already is.

A strip with segment before the existing segments.

The length columns of the strip beginning at display column start.

The row as one ANSI string.

The strip's segment text joined, with no style codes added.

The strip's width in display columns.

Types

t()

@type t() :: %Drafter.Draw.Strip{
  cache_key: term(),
  segments: [Drafter.Draw.Segment.t()],
  width: non_neg_integer()
}

Functions

append(strip, segment)

@spec append(t(), Drafter.Draw.Segment.t()) :: t()

A strip with segment after the existing segments.

Examples

iex> Drafter.Draw.Strip.from_text("ab")
...> |> Drafter.Draw.Strip.append(Drafter.Draw.Segment.plain("c"))
...> |> Drafter.Draw.Strip.to_plain_text()
"abc"

apply_style(strip, style)

@spec apply_style(t(), Drafter.Draw.Segment.style()) :: t()

Merge style into every segment's style, style winning on shared keys.

Examples

iex> Drafter.Draw.Strip.from_text("ab")
...> |> Drafter.Draw.Strip.apply_style(%{bold: true})
...> |> Map.fetch!(:segments)
[%Drafter.Draw.Segment{text: "ab", style: %{bold: true}, width: 2}]

center(strip, target_width)

@spec center(t(), non_neg_integer()) :: t()

The strip centred in target_width columns between unstyled space padding.

An odd remainder puts the extra column on the right. A strip at least as wide as target_width is cropped to it instead.

Examples

iex> Drafter.Draw.Strip.from_text("ab")
...> |> Drafter.Draw.Strip.center(7)
...> |> Drafter.Draw.Strip.to_plain_text()
"  ab   "

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.center(3)
...> |> Drafter.Draw.Strip.to_plain_text()
"hel"

combine(strip1, strip2)

@spec combine(t(), t()) :: t()

A strip whose segments are the first strip's followed by the second's.

Segments are never merged, even when their styles are equal.

Examples

iex> left = Drafter.Draw.Strip.from_text("ab")
iex> right = Drafter.Draw.Strip.from_text("cd")
iex> combined = Drafter.Draw.Strip.combine(left, right)
iex> {Drafter.Draw.Strip.to_plain_text(combined), length(combined.segments)}
{"abcd", 2}

crop(strip, crop_width)

@spec crop(t(), non_neg_integer()) :: t()

Cut the strip down to crop_width display columns.

A strip already that narrow is returned unchanged, and a crop_width of zero or less gives an empty strip. The segment straddling the boundary is cropped and the segments past it are dropped.

Examples

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.crop(3)
...> |> Drafter.Draw.Strip.to_plain_text()
"hel"

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.crop(0)
...> |> Drafter.Draw.Strip.width()
0

divide(strip, position)

@spec divide(t(), non_neg_integer()) :: {t(), t()}

Split the strip into {left, right} at display column position.

left is position columns wide. A position of zero or less puts everything in right; a position at or past the strip's width puts everything in left. A segment straddling the split is divided between the two, both halves keeping its style.

Examples

iex> {left, right} = Drafter.Draw.Strip.divide(Drafter.Draw.Strip.from_text("hello"), 2)
iex> {Drafter.Draw.Strip.to_plain_text(left), Drafter.Draw.Strip.to_plain_text(right)}
{"he", "llo"}

iex> {left, right} = Drafter.Draw.Strip.divide(Drafter.Draw.Strip.from_text("hello"), 0)
iex> {Drafter.Draw.Strip.to_plain_text(left), Drafter.Draw.Strip.to_plain_text(right)}
{"", "hello"}

iex> {left, right} = Drafter.Draw.Strip.divide(Drafter.Draw.Strip.from_text("hello"), 9)
iex> {Drafter.Draw.Strip.to_plain_text(left), Drafter.Draw.Strip.to_plain_text(right)}
{"hello", ""}

empty()

@spec empty() :: t()

A strip with no segments and zero width.

Examples

iex> Drafter.Draw.Strip.empty()
%Drafter.Draw.Strip{segments: [], width: 0, cache_key: nil}

empty?(strip)

@spec empty?(t()) :: boolean()

Whether the strip has no segments, or every segment has empty text.

Examples

iex> Drafter.Draw.Strip.empty() |> Drafter.Draw.Strip.empty?()
true

iex> Drafter.Draw.Strip.from_text("") |> Drafter.Draw.Strip.empty?()
true

iex> Drafter.Draw.Strip.from_text(" ") |> Drafter.Draw.Strip.empty?()
false

fit_to_width(strip, target_width)

@spec fit_to_width(t(), non_neg_integer()) :: t()

The strip at exactly target_width columns, cropped or space-padded on the right.

Examples

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.fit_to_width(3)
...> |> Drafter.Draw.Strip.to_plain_text()
"hel"

iex> Drafter.Draw.Strip.from_text("hi")
...> |> Drafter.Draw.Strip.fit_to_width(4)
...> |> Drafter.Draw.Strip.to_plain_text()
"hi  "

from_text(text)

@spec from_text(String.t()) :: t()

A strip of one unstyled segment holding text.

Examples

iex> Drafter.Draw.Strip.from_text("hi").segments
[%Drafter.Draw.Segment{text: "hi", style: %{}, width: 2}]

new(segments)

@spec new([Drafter.Draw.Segment.t()]) :: t()

A strip holding segments in order.

The strip's width is the sum of the segment widths, and its cache_key a hash of the segments.

Examples

iex> strip = Drafter.Draw.Strip.new([Drafter.Draw.Segment.plain("日本")])
iex> strip.width
4

overflow(strip, width, mode \\ :clip)

@spec overflow(t(), non_neg_integer(), :clip | :ellipsis) :: t()

Narrow a strip to width.

mode defaults to :clip, which cuts at the boundary. :ellipsis cuts at width - 1 and appends in the trailing segment's style. A strip already within width is returned unchanged, and a width of zero or less clips whatever the mode.

Examples

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.overflow(3)
...> |> Drafter.Draw.Strip.to_plain_text()
"hel"

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.overflow(3, :ellipsis)
...> |> Drafter.Draw.Strip.to_plain_text()
"he…"

iex> Drafter.Draw.Strip.from_text("hi")
...> |> Drafter.Draw.Strip.overflow(5, :ellipsis)
...> |> Drafter.Draw.Strip.to_plain_text()
"hi"

pad(strip, target_width)

@spec pad(t(), non_neg_integer()) :: t()

Append an unstyled run of spaces until the strip is target_width columns wide, or return it unchanged if it already is.

Examples

iex> Drafter.Draw.Strip.from_text("ab")
...> |> Drafter.Draw.Strip.pad(5)
...> |> Drafter.Draw.Strip.to_plain_text()
"ab   "

prepend(strip, segment)

@spec prepend(t(), Drafter.Draw.Segment.t()) :: t()

A strip with segment before the existing segments.

Examples

iex> Drafter.Draw.Strip.from_text("bc")
...> |> Drafter.Draw.Strip.prepend(Drafter.Draw.Segment.plain("a"))
...> |> Drafter.Draw.Strip.to_plain_text()
"abc"

slice(strip, start, length)

@spec slice(t(), non_neg_integer(), non_neg_integer()) :: t()

The length columns of the strip beginning at display column start.

Examples

iex> Drafter.Draw.Strip.from_text("hello")
...> |> Drafter.Draw.Strip.slice(1, 3)
...> |> Drafter.Draw.Strip.to_plain_text()
"ell"

to_ansi(strip)

@spec to_ansi(t()) :: String.t()

The row as one ANSI string.

Style codes carry across segments: a segment whose style equals the previous one's emits only its text, and a segment that adds keys emits only the codes for those keys. A full reset (\e[0m) is emitted before a segment that drops a key the previous segment had set, and once more at the end of the row unless the last segment is unstyled.

Examples

iex> alias Drafter.Draw.{Segment, Strip}
iex> Strip.new([Segment.plain("ab"), Segment.new("cd", %{bold: true})])
...> |> Strip.to_ansi()
"ab\e[1mcd\e[0m"

iex> alias Drafter.Draw.{Segment, Strip}
iex> Strip.new([
...>   Segment.new("A", %{bold: true}),
...>   Segment.new("B", %{bold: true, italic: true}),
...>   Segment.plain("C")
...> ])
...> |> Strip.to_ansi()
"\e[1mA\e[3mB\e[0mC"

to_plain_text(strip)

@spec to_plain_text(t()) :: String.t()

The strip's segment text joined, with no style codes added.

Any ANSI sequences already embedded in a segment's text are kept.

Examples

iex> alias Drafter.Draw.{Segment, Strip}
iex> Strip.new([Segment.plain("ab"), Segment.new("cd", %{bold: true})])
...> |> Strip.to_plain_text()
"abcd"

width(strip)

@spec width(t()) :: non_neg_integer()

The strip's width in display columns.

Examples

iex> Drafter.Draw.Strip.from_text("日本") |> Drafter.Draw.Strip.width()
4