# `Drafter.Draw.Strip`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/draw/strip.ex#L1)

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"

# `t`

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

# `append`

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---

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