# CellBuffer

Source: /tuil/docs/reference/packages/cell/api/cell-buffer
Locale: en

class exported by @mwillbanks/tuil-cell.



{/* Generated by tooling/docs/generate-reference.ts. */}

## class [#class]

Public class exported by `@mwillbanks/tuil-cell`.

```ts
export class CellBuffer {
  readonly width: number;
  readonly height: number;
  readonly #cells: Cell[];
  #cursor?: CursorState;

  constructor(width: number, height: number, fill: Cell = emptyCell) {
    if (
      !Number.isSafeInteger(width) ||
      !Number.isSafeInteger(height) ||
      width < 1 ||
      height < 1
    ) {
      throw new Error("Cell buffer dimensions must be positive integers");
    }
    this.width = width;
    this.height = height;
    this.#cells = Array.from({ length: width * height }, () => cloneCell(fill));
  }

  get(x: number, y: number): Cell | undefined {
    if (!this.#contains(x, y)) return undefined;
    return this.#cells[y * this.width + x];
  }

  set(x: number, y: number, cell: Cell, clip?: TerminalBounds): void {
    if (!this.#contains(x, y) || (clip && !inRect(x, y, clip))) return;
    if (!cell.continuation) this.#clearWideCellAt(x, y);
    this.#setRaw(x, y, cell);
  }

  write(
    x: number,
    y: number,
    value: string,
    style: Partial<Omit<Cell, "grapheme" | "continuation">> = {},
    clip: TerminalBounds = {
      x: 0,
      y: 0,
      width: this.width,
      height: this.height,
    },
  ): number {
    let cursor = x;
    const safeValue = escapeTerminalControlCharacters(value);
    for (const grapheme of graphemeSegmenter.segment(safeValue)) {
      const width = stringWidth(grapheme.segment);
      if (width === 0) {
        this.#appendZeroWidth(cursor, y, grapheme.segment, clip);
        continue;
      }
      if (cursor + width > clip.x + clip.width || cursor + width > this.width)
        break;
      this.#writeGrapheme(cursor, y, grapheme.segment, width, style, clip);
      cursor += width;
    }
    return cursor;
  }

  fill(rect: TerminalBounds, cell: Cell, clip?: TerminalBounds): void {
    for (let y = rect.y; y < rect.y + rect.height; y += 1) {
      for (let x = rect.x; x < rect.x + rect.width; x += 1)
        this.set(x, y, cell, clip);
    }
  }

  clear(cell: Cell = emptyCell): void {
    this.#cells.fill(cloneCell(cell));
    this.#cursor = undefined;
  }

  erase(rect: TerminalBounds): void {
    this.fill(rect, emptyCell);
  }

  border(
    rect: TerminalBounds,
    style: Partial<Omit<Cell, "grapheme" | "continuation">> = {},
    glyphs = {
      top: "─",
      bottom: "─",
      left: "│",
      right: "│",
      topLeft: "┌",
      topRight: "┐",
      bottomLeft: "└",
      bottomRight: "┘",
    },
    clip: TerminalBounds = rect,
  ): void {
    if (rect.width < 2 || rect.height < 2) return;
    this.write(
      rect.x,
      rect.y,
      glyphs.topLeft + glyphs.top.repeat(rect.width - 2) + glyphs.topRight,
      style,
      clip,
    );
    this.write(
      rect.x,
      rect.y + rect.height - 1,
      glyphs.bottomLeft +
        glyphs.bottom.repeat(rect.width - 2) +
        glyphs.bottomRight,
      style,
      clip,
    );
    for (let y = rect.y + 1; y < rect.y + rect.height - 1; y += 1) {
      this.write(rect.x, y, glyphs.left, style, clip);
      this.write(rect.x + rect.width - 1, y, glyphs.right, style, clip);
    }
  }

  composite(
    source: CellFrame,
    xOffset = 0,
    yOffset = 0,
    clip?: TerminalBounds,
  ): void {
    const actualClip = clip ?? {
      x: 0,
      y: 0,
      width: this.width,
      height: this.height,
    };
    for (let y = 0; y < source.height; y += 1) {
      for (let x = 0; x < source.width; x += 1) {
        const cell = source.cells[y * source.width + x];
        if (cell) this.set(x + xOffset, y + yOffset, cell, actualClip);
      }
    }
  }

  setCursor(cursor: CursorState | undefined): void {
    validateRendererCursor(cursor, this.width, this.height);
    this.#cursor = cursor ? Object.freeze({ ...cursor }) : undefined;
  }

  frame(): CellFrame {
    return Object.freeze({
      width: this.width,
      height: this.height,
      cells: Object.freeze(this.#cells.map(cloneCell)),
      cursor: this.#cursor,
    });
  }

  #contains(x: number, y: number): boolean {
    return (
      Number.isSafeInteger(x) &&
      Number.isSafeInteger(y) &&
      x >= 0 &&
      y >= 0 &&
      x < this.width &&
      y < this.height
    );
  }

  #setRaw(x: number, y: number, cell: Cell): void {
    this.#cells[y * this.width + x] = cloneCell(cell);
  }

  #appendZeroWidth(
    cursor: number,
    y: number,
    grapheme: string,
    clip: TerminalBounds,
  ): void {
    const previous = this.get(cursor - 1, y);
    if (!previous) return;
    this.set(
      cursor - 1,
      y,
      { ...previous, grapheme: previous.grapheme + grapheme },
      clip,
    );
  }

  #writeGrapheme(
    x: number,
    y: number,
    grapheme: string,
    width: number,
    style: Partial<Omit<Cell, "grapheme" | "continuation">>,
    clip: TerminalBounds,
  ): void {
    const cell: Cell = {
      grapheme,
      foreground: style.foreground ?? defaultColor,
      background: style.background ?? defaultColor,
      attributes: style.attributes ?? {},
      link: style.link,
    };
    this.set(x, y, cell, clip);
    for (let offset = 1; offset < width; offset += 1) {
      const continuationX = x + offset;
      if (inRect(continuationX, y, clip)) {
        this.#setRaw(continuationX, y, {
          ...cell,
          grapheme: "",
          continuation: true,
        });
      }
    }
  }

  #clearWideCellAt(x: number, y: number): void {
    const start = this.#wideCellStart(x, y);
    const leading = this.get(start, y);
    if (start !== x || (leading && stringWidth(leading.grapheme) > 1)) {
      this.#setRaw(start, y, emptyCell);
    }
    this.#clearContinuations(start, y);
  }

  #wideCellStart(x: number, y: number): number {
    let start = x;
    while (start > 0 && this.get(start, y)?.continuation) start -= 1;
    return start;
  }

  #clearContinuations(start: number, y: number): void {
    for (
      let column = start + 1;
      column < this.width && this.get(column, y)?.continuation;
      column += 1
    ) {
      this.#setRaw(column, y, emptyCell);
    }
  }
}
```

## Members [#members]

| Member                | Type                                                                                                                                                                                                                                                                             | Required | Description                                                                                                                                                                                                                                                                                                             | Related types                                                                                                                      |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `width`               | `number`                                                                                                                                                                                                                                                                         | Yes      | The `width` member uses the `number` contract.                                                                                                                                                                                                                                                                          | —                                                                                                                                  |
| `height`              | `number`                                                                                                                                                                                                                                                                         | Yes      | The `height` member uses the `number` contract.                                                                                                                                                                                                                                                                         | —                                                                                                                                  |
| `#cells`              | `Cell[]`                                                                                                                                                                                                                                                                         | Yes      | The `#cells` member uses the `Cell[]` contract.                                                                                                                                                                                                                                                                         | [`Cell`](/tuil/docs/reference/packages/cell/api/cell)                                                                                   |
| `#cursor`             | `CursorState \| undefined`                                                                                                                                                                                                                                                       | No       | The `#cursor` member uses the `CursorState \| undefined` contract.                                                                                                                                                                                                                                                      | [`CursorState`](/tuil/docs/reference/packages/cell/api/cursor-state)                                                                    |
| `__constructor`       | `any`                                                                                                                                                                                                                                                                            | Yes      | The `__constructor` member uses the `any` contract.                                                                                                                                                                                                                                                                     | —                                                                                                                                  |
| `get`                 | `(x: number, y: number) => Cell \| undefined`                                                                                                                                                                                                                                    | Yes      | The `get` member uses the `(x: number, y: number) => Cell \| undefined` contract.                                                                                                                                                                                                                                       | [`Cell`](/tuil/docs/reference/packages/cell/api/cell)                                                                                   |
| `set`                 | `(x: number, y: number, cell: Cell, clip?: TerminalBounds) => void`                                                                                                                                                                                                              | Yes      | The `set` member uses the `(x: number, y: number, cell: Cell, clip?: TerminalBounds) => void` contract.                                                                                                                                                                                                                 | [`Cell`](/tuil/docs/reference/packages/cell/api/cell), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)            |
| `write`               | `(x: number, y: number, value: string, style?: Partial<Omit<Cell, "grapheme" \| "continuation">>, clip?: TerminalBounds) => number`                                                                                                                                              | Yes      | The `write` member uses the `(x: number, y: number, value: string, style?: Partial<Omit<Cell, "grapheme" \| "continuation">>, clip?: TerminalBounds) => number` contract.                                                                                                                                               | [`Cell`](/tuil/docs/reference/packages/cell/api/cell), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)            |
| `fill`                | `(rect: TerminalBounds, cell: Cell, clip?: TerminalBounds) => void`                                                                                                                                                                                                              | Yes      | The `fill` member uses the `(rect: TerminalBounds, cell: Cell, clip?: TerminalBounds) => void` contract.                                                                                                                                                                                                                | [`Cell`](/tuil/docs/reference/packages/cell/api/cell), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)            |
| `clear`               | `(cell?: Cell) => void`                                                                                                                                                                                                                                                          | Yes      | The `clear` member uses the `(cell?: Cell) => void` contract.                                                                                                                                                                                                                                                           | [`Cell`](/tuil/docs/reference/packages/cell/api/cell)                                                                                   |
| `erase`               | `(rect: TerminalBounds) => void`                                                                                                                                                                                                                                                 | Yes      | The `erase` member uses the `(rect: TerminalBounds) => void` contract.                                                                                                                                                                                                                                                  | [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)                                                              |
| `border`              | `(rect: TerminalBounds, style?: Partial<Omit<Cell, "grapheme" \| "continuation">>, glyphs?: &#123; top: string; bottom: string; left: string; right: string; topLeft: string; topRight: string; bottomLeft: string; bottomRight: string; &#125;, clip?: TerminalBounds) => void` | Yes      | The `border` member uses the `(rect: TerminalBounds, style?: Partial<Omit<Cell, "grapheme" \| "continuation">>, glyphs?: &#123; top: string; bottom: string; left: string; right: string; topLeft: string; topRight: string; bottomLeft: string; bottomRight: string; &#125;, clip?: TerminalBounds) => void` contract. | [`Cell`](/tuil/docs/reference/packages/cell/api/cell), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)            |
| `composite`           | `(source: CellFrame, xOffset?: number, yOffset?: number, clip?: TerminalBounds) => void`                                                                                                                                                                                         | Yes      | The `composite` member uses the `(source: CellFrame, xOffset?: number, yOffset?: number, clip?: TerminalBounds) => void` contract.                                                                                                                                                                                      | [`CellFrame`](/tuil/docs/reference/packages/cell/api/cell-frame), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds) |
| `setCursor`           | `(cursor: CursorState \| undefined) => void`                                                                                                                                                                                                                                     | Yes      | The `setCursor` member uses the `(cursor: CursorState \| undefined) => void` contract.                                                                                                                                                                                                                                  | [`CursorState`](/tuil/docs/reference/packages/cell/api/cursor-state)                                                                    |
| `frame`               | `() => CellFrame`                                                                                                                                                                                                                                                                | Yes      | The `frame` member uses the `() => CellFrame` contract.                                                                                                                                                                                                                                                                 | [`CellFrame`](/tuil/docs/reference/packages/cell/api/cell-frame)                                                                        |
| `#contains`           | `(x: number, y: number) => boolean`                                                                                                                                                                                                                                              | Yes      | The `#contains` member uses the `(x: number, y: number) => boolean` contract.                                                                                                                                                                                                                                           | —                                                                                                                                  |
| `#setRaw`             | `(x: number, y: number, cell: Cell) => void`                                                                                                                                                                                                                                     | Yes      | The `#setRaw` member uses the `(x: number, y: number, cell: Cell) => void` contract.                                                                                                                                                                                                                                    | [`Cell`](/tuil/docs/reference/packages/cell/api/cell)                                                                                   |
| `#appendZeroWidth`    | `(cursor: number, y: number, grapheme: string, clip: TerminalBounds) => void`                                                                                                                                                                                                    | Yes      | The `#appendZeroWidth` member uses the `(cursor: number, y: number, grapheme: string, clip: TerminalBounds) => void` contract.                                                                                                                                                                                          | [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)                                                              |
| `#writeGrapheme`      | `(x: number, y: number, grapheme: string, width: number, style: Partial<Omit<Cell, "grapheme" \| "continuation">>, clip: TerminalBounds) => void`                                                                                                                                | Yes      | The `#writeGrapheme` member uses the `(x: number, y: number, grapheme: string, width: number, style: Partial<Omit<Cell, "grapheme" \| "continuation">>, clip: TerminalBounds) => void` contract.                                                                                                                        | [`Cell`](/tuil/docs/reference/packages/cell/api/cell), [`TerminalBounds`](/tuil/docs/reference/packages/core/api/terminal-bounds)            |
| `#clearWideCellAt`    | `(x: number, y: number) => void`                                                                                                                                                                                                                                                 | Yes      | The `#clearWideCellAt` member uses the `(x: number, y: number) => void` contract.                                                                                                                                                                                                                                       | —                                                                                                                                  |
| `#wideCellStart`      | `(x: number, y: number) => number`                                                                                                                                                                                                                                               | Yes      | The `#wideCellStart` member uses the `(x: number, y: number) => number` contract.                                                                                                                                                                                                                                       | —                                                                                                                                  |
| `#clearContinuations` | `(start: number, y: number) => void`                                                                                                                                                                                                                                             | Yes      | The `#clearContinuations` member uses the `(start: number, y: number) => void` contract.                                                                                                                                                                                                                                | —                                                                                                                                  |

## Parameters [#parameters]

This declaration has no public members.

## Returns [#returns]

This declaration does not return a value.

## Throws [#throws]

No thrown errors are documented for this declaration.

## Related types [#related-types]

* [`Cell`](/tuil/docs/reference/packages/cell/api/cell)
* [`CellFrame`](/tuil/docs/reference/packages/cell/api/cell-frame)
* [`CursorState`](/tuil/docs/reference/packages/cell/api/cursor-state)

## Source [#source]

[View the secondary source reference](https://github.com/mwillbanks/tuil/blob/main/packages/cell/src/index.ts)

## Package [#package]

[@mwillbanks/tuil-cell](/tuil/docs/reference/packages/cell)
