# TextBufferSession

Source: /tuil/docs/reference/packages/editor/api/text-buffer-session
Locale: en

class exported by @mwillbanks/tuil-editor.



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

## class [#class]

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

```ts
export class TextBufferSession implements EditorSession {
  readonly #id: string;
  readonly #type: string;
  readonly #readOnly: boolean;
  readonly #masked: boolean;
  readonly #clipboard?: EditorProviderOptions["clipboard"];
  readonly #onDocumentChange?: EditorProviderOptions["onDocumentChange"];
  readonly #observers = new Set<(snapshot: EditorSnapshot) => void>();
  readonly #undo: HistoryEntry[] = [];
  readonly #redo: HistoryEntry[] = [];
  #text: string;
  #version = 0;
  #selections: readonly EditorSelection[];
  #decorations: readonly EditorDecoration[] = [];
  #diagnostics: readonly EditorDiagnostic[] = [];
  #mode: string;
  #viewportAnchor?: EditorPosition;
  #disposed = false;

  constructor(options: EditorProviderOptions = {}) {
    this.#id = options.id ?? "document";
    this.#type = options.documentType ?? "text/plain";
    this.#text = options.value ?? "";
    this.#readOnly = options.readOnly ?? false;
    this.#masked = options.masked ?? false;
    this.#clipboard = options.clipboard;
    this.#onDocumentChange = options.onDocumentChange;
    this.#mode = options.mode ?? "insert";
    this.#viewportAnchor = options.viewportAnchor;
    this.#selections = [selection(position(0, 0))];
  }

  snapshot(): EditorSnapshot {
    return Object.freeze({
      document: Object.freeze({
        id: this.#id,
        type: this.#type,
        version: this.#version,
        text: this.#exportText(),
      }),
      selections: this.#selections,
      decorations: this.#decorations,
      diagnostics: this.#diagnostics,
      mode: this.#mode,
      readOnly: this.#readOnly,
      canUndo: this.#undo.length > 0,
      canRedo: this.#redo.length > 0,
      viewportAnchor: this.#viewportAnchor,
    });
  }

  dispatch(transaction: EditorTransaction): EditorSnapshot {
    this.#assertActive();
    const changes = [...(transaction.changes ?? [])];
    if (changes.length > 0 && this.#readOnly) {
      throw new Error("Editor session is read-only");
    }
    this.#recordTransactionHistory(changes, transaction.addToHistory);
    const edits = prepareEdits(this.#text, changes);
    this.#text = applyEdits(this.#text, edits);
    this.#applyTransactionSelection(edits, transaction.selections);
    if (changes.length > 0 || transaction.selections) this.#version += 1;
    if (changes.length > 0) this.#onDocumentChange?.(this.#text);
    this.#notify();
    return this.snapshot();
  }

  #recordTransactionHistory(
    changes: readonly EditorChange[],
    addToHistory: boolean | undefined,
  ): void {
    if (changes.length === 0 || addToHistory === false) return;
    this.#undo.push({ text: this.#text, selections: this.#selections });
    this.#redo.length = 0;
  }

  #applyTransactionSelection(
    edits: readonly PreparedEdit[],
    selections: readonly EditorSelection[] | undefined,
  ): void {
    if (selections) {
      this.#selections = freezeSelections(this.#text, selections);
      return;
    }
    if (edits.length === 0) return;
    this.#selections = edits
      .toSorted((left, right) => left.index - right.index)
      .map((edit) => {
        const precedingDelta = edits
          .filter((candidate) => candidate.offsets[0] < edit.offsets[0])
          .reduce(
            (total, candidate) =>
              total +
              candidate.change.insert.length -
              (candidate.offsets[1] - candidate.offsets[0]),
            0,
          );
        return selection(
          positionAt(
            this.#text,
            edit.offsets[0] + edit.change.insert.length + precedingDelta,
          ),
        );
      });
  }

  execute(
    command: string | EditorCommand,
    argument?: unknown,
  ): boolean | Promise<boolean> {
    this.#assertActive();
    if (typeof command !== "string") return command.execute(this, argument);
    const clipboard = executeClipboardCommand(this, command, argument);
    if (clipboard !== undefined) return clipboard;
    return this.#executeSelectionCommand(command);
  }

  #executeSelectionCommand(command: string): boolean {
    if (command === "select-all") {
      this.#selections = [
        selection(position(0, 0), positionAt(this.#text, this.#text.length)),
      ];
    } else if (command === "delete-selection") {
      this.dispatch({
        changes: this.#selections.map((range) => ({ range, insert: "" })),
      });
      return true;
    } else if (command === "cursor-start") {
      this.dispatch({
        selections: [selection(position(0, 0))],
        addToHistory: false,
      });
      return true;
    } else if (command === "cursor-end") {
      this.dispatch({
        selections: [selection(positionAt(this.#text, this.#text.length))],
        addToHistory: false,
      });
      return true;
    } else {
      return false;
    }
    this.#notify();
    return true;
  }

  copy(): string | Promise<string> {
    this.#assertActive();
    const copied = this.#selections
      .map((range) => {
        const [start, end] = offsets(this.#text, range);
        const value = this.#text.slice(start, end);
        return this.#masked ? maskText(value) : value;
      })
      .join("\n");
    if (!this.#clipboard) return copied;
    return Promise.resolve(this.#clipboard.write(copied)).then(() => copied);
  }

  cut(): string | Promise<string> {
    return cutSelections(this, this.copy());
  }

  paste(value?: string): boolean | Promise<boolean> {
    return pasteSelections(this, this.#clipboard, value);
  }

  undo(): boolean {
    return this.#restoreHistory(this.#undo, this.#redo);
  }

  redo(): boolean {
    return this.#restoreHistory(this.#redo, this.#undo);
  }

  #restoreHistory(
    source: HistoryEntry[],
    destination: HistoryEntry[],
  ): boolean {
    this.#assertActive();
    const entry = source.pop();
    if (!entry) return false;
    destination.push({ text: this.#text, selections: this.#selections });
    this.#text = entry.text;
    this.#selections = entry.selections;
    this.#version += 1;
    this.#onDocumentChange?.(this.#text);
    this.#notify();
    return true;
  }

  search(query: string | RegExp): readonly EditorRange[] {
    this.#assertActive();
    if (typeof query === "string" && query.length === 0) return [];
    const expression = createGlobalSearchExpression(query);
    const ranges: EditorRange[] = [];
    for (const match of this.#text.matchAll(expression)) {
      ranges.push(
        Object.freeze({
          anchor: positionAt(this.#text, match.index),
          head: positionAt(this.#text, match.index + match[0].length),
        }),
      );
      if (match[0].length === 0) expression.lastIndex += 1;
    }
    return Object.freeze(ranges);
  }

  replace(query: string | RegExp, replacement: string, all = true): number {
    const ranges = this.search(query);
    const selected = all ? ranges : ranges.slice(0, 1);
    if (selected.length === 0) return 0;
    this.dispatch({
      changes: selected.map((range) => ({
        range,
        insert: replacement,
      })),
    });
    return selected.length;
  }

  serialize(format: "text" | "json" | "markdown" = "text"): string {
    const text = this.#exportText();
    return format === "json"
      ? JSON.stringify(
          this.#masked
            ? { type: this.#type, text, masked: true }
            : { type: this.#type, text },
        )
      : text;
  }

  #exportText(): string {
    return this.#masked ? maskText(this.#text) : this.#text;
  }

  subscribe(observer: (snapshot: EditorSnapshot) => void): () => void {
    this.#assertActive();
    this.#observers.add(observer);
    return () => this.#observers.delete(observer);
  }

  setDecorations(decorations: readonly EditorDecoration[]): void {
    this.#decorations = Object.freeze([...decorations]);
    this.#notify();
  }

  setDiagnostics(diagnostics: readonly EditorDiagnostic[]): void {
    this.#diagnostics = Object.freeze([...diagnostics]);
    this.#notify();
  }

  setMode(mode: string): void {
    this.#mode = mode;
    this.#notify();
  }

  setViewportAnchor(anchor: EditorPosition): void {
    this.#viewportAnchor = clamp(this.#text, anchor);
    this.#notify();
  }

  dispose(): void {
    this.#disposed = true;
    this.#observers.clear();
    this.#undo.length = 0;
    this.#redo.length = 0;
  }

  #notify(): void {
    const snapshot = this.snapshot();
    for (const observer of this.#observers) observer(snapshot);
  }

  #assertActive(): void {
    if (this.#disposed) throw new Error("Editor session is disposed");
  }
}
```

## Members [#members]

| Member                       | Type                                                                                            | Required | Description                                                                                                                                                | Related types                                                                                                                                          |
| ---------------------------- | ----------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `#id`                        | `string`                                                                                        | Yes      | The `#id` member uses the `string` contract.                                                                                                               | —                                                                                                                                                      |
| `#type`                      | `string`                                                                                        | Yes      | The `#type` member uses the `string` contract.                                                                                                             | —                                                                                                                                                      |
| `#readOnly`                  | `boolean`                                                                                       | Yes      | The `#readOnly` member uses the `boolean` contract.                                                                                                        | —                                                                                                                                                      |
| `#masked`                    | `boolean`                                                                                       | Yes      | The `#masked` member uses the `boolean` contract.                                                                                                          | —                                                                                                                                                      |
| `#clipboard`                 | `import("./index").EditorClipboardAdapter \| undefined`                                         | No       | The `#clipboard` member uses the `import("./index").EditorClipboardAdapter \| undefined` contract.                                                         | —                                                                                                                                                      |
| `#onDocumentChange`          | `((value: string) => void) \| undefined`                                                        | No       | The `#onDocumentChange` member uses the `((value: string) => void) \| undefined` contract.                                                                 | —                                                                                                                                                      |
| `#observers`                 | `Set<(snapshot: EditorSnapshot) => void>`                                                       | Yes      | The `#observers` member uses the `Set<(snapshot: EditorSnapshot) => void>` contract.                                                                       | [`EditorSnapshot`](/tuil/docs/reference/packages/editor/api/editor-snapshot)                                                                                |
| `#undo`                      | `HistoryEntry[]`                                                                                | Yes      | The `#undo` member uses the `HistoryEntry[]` contract.                                                                                                     | —                                                                                                                                                      |
| `#redo`                      | `HistoryEntry[]`                                                                                | Yes      | The `#redo` member uses the `HistoryEntry[]` contract.                                                                                                     | —                                                                                                                                                      |
| `#text`                      | `string`                                                                                        | Yes      | The `#text` member uses the `string` contract.                                                                                                             | —                                                                                                                                                      |
| `#version`                   | `number`                                                                                        | Yes      | The `#version` member uses the `number` contract.                                                                                                          | —                                                                                                                                                      |
| `#selections`                | `readonly EditorSelection[]`                                                                    | Yes      | The `#selections` member uses the `readonly EditorSelection[]` contract.                                                                                   | [`EditorSelection`](/tuil/docs/reference/packages/editor/api/editor-selection)                                                                              |
| `#decorations`               | `readonly EditorDecoration[]`                                                                   | Yes      | The `#decorations` member uses the `readonly EditorDecoration[]` contract.                                                                                 | [`EditorDecoration`](/tuil/docs/reference/packages/editor/api/editor-decoration)                                                                            |
| `#diagnostics`               | `readonly EditorDiagnostic[]`                                                                   | Yes      | The `#diagnostics` member uses the `readonly EditorDiagnostic[]` contract.                                                                                 | [`EditorDiagnostic`](/tuil/docs/reference/packages/editor/api/editor-diagnostic)                                                                            |
| `#mode`                      | `string`                                                                                        | Yes      | The `#mode` member uses the `string` contract.                                                                                                             | —                                                                                                                                                      |
| `#viewportAnchor`            | `EditorPosition \| undefined`                                                                   | No       | The `#viewportAnchor` member uses the `EditorPosition \| undefined` contract.                                                                              | [`EditorPosition`](/tuil/docs/reference/packages/editor/api/editor-position)                                                                                |
| `#disposed`                  | `boolean`                                                                                       | Yes      | The `#disposed` member uses the `boolean` contract.                                                                                                        | —                                                                                                                                                      |
| `__constructor`              | `any`                                                                                           | Yes      | The `__constructor` member uses the `any` contract.                                                                                                        | —                                                                                                                                                      |
| `snapshot`                   | `() => EditorSnapshot`                                                                          | Yes      | The `snapshot` member uses the `() => EditorSnapshot` contract.                                                                                            | [`EditorSnapshot`](/tuil/docs/reference/packages/editor/api/editor-snapshot)                                                                                |
| `dispatch`                   | `(transaction: EditorTransaction) => EditorSnapshot`                                            | Yes      | The `dispatch` member uses the `(transaction: EditorTransaction) => EditorSnapshot` contract.                                                              | [`EditorSnapshot`](/tuil/docs/reference/packages/editor/api/editor-snapshot), [`EditorTransaction`](/tuil/docs/reference/packages/editor/api/editor-transaction) |
| `#recordTransactionHistory`  | `(changes: readonly EditorChange[], addToHistory: boolean \| undefined) => void`                | Yes      | The `#recordTransactionHistory` member uses the `(changes: readonly EditorChange[], addToHistory: boolean \| undefined) => void` contract.                 | [`EditorChange`](/tuil/docs/reference/packages/editor/api/editor-change)                                                                                    |
| `#applyTransactionSelection` | `(edits: readonly PreparedEdit[], selections: readonly EditorSelection[] \| undefined) => void` | Yes      | The `#applyTransactionSelection` member uses the `(edits: readonly PreparedEdit[], selections: readonly EditorSelection[] \| undefined) => void` contract. | [`EditorSelection`](/tuil/docs/reference/packages/editor/api/editor-selection)                                                                              |
| `execute`                    | `(command: string \| EditorCommand, argument?: unknown) => boolean \| Promise<boolean>`         | Yes      | The `execute` member uses the `(command: string \| EditorCommand, argument?: unknown) => boolean \| Promise<boolean>` contract.                            | [`EditorCommand`](/tuil/docs/reference/packages/editor/api/editor-command)                                                                                  |
| `#executeSelectionCommand`   | `(command: string) => boolean`                                                                  | Yes      | The `#executeSelectionCommand` member uses the `(command: string) => boolean` contract.                                                                    | —                                                                                                                                                      |
| `copy`                       | `() => string \| Promise<string>`                                                               | Yes      | The `copy` member uses the `() => string \| Promise<string>` contract.                                                                                     | —                                                                                                                                                      |
| `cut`                        | `() => string \| Promise<string>`                                                               | Yes      | The `cut` member uses the `() => string \| Promise<string>` contract.                                                                                      | —                                                                                                                                                      |
| `paste`                      | `(value?: string) => boolean \| Promise<boolean>`                                               | Yes      | The `paste` member uses the `(value?: string) => boolean \| Promise<boolean>` contract.                                                                    | —                                                                                                                                                      |
| `undo`                       | `() => boolean`                                                                                 | Yes      | The `undo` member uses the `() => boolean` contract.                                                                                                       | —                                                                                                                                                      |
| `redo`                       | `() => boolean`                                                                                 | Yes      | The `redo` member uses the `() => boolean` contract.                                                                                                       | —                                                                                                                                                      |
| `#restoreHistory`            | `(source: HistoryEntry[], destination: HistoryEntry[]) => boolean`                              | Yes      | The `#restoreHistory` member uses the `(source: HistoryEntry[], destination: HistoryEntry[]) => boolean` contract.                                         | —                                                                                                                                                      |
| `search`                     | `(query: string \| RegExp) => readonly EditorRange[]`                                           | Yes      | The `search` member uses the `(query: string \| RegExp) => readonly EditorRange[]` contract.                                                               | [`EditorRange`](/tuil/docs/reference/packages/editor/api/editor-range)                                                                                      |
| `replace`                    | `(query: string \| RegExp, replacement: string, all?: boolean) => number`                       | Yes      | The `replace` member uses the `(query: string \| RegExp, replacement: string, all?: boolean) => number` contract.                                          | —                                                                                                                                                      |
| `serialize`                  | `(format?: "text" \| "json" \| "markdown") => string`                                           | Yes      | The `serialize` member uses the `(format?: "text" \| "json" \| "markdown") => string` contract.                                                            | —                                                                                                                                                      |
| `#exportText`                | `() => string`                                                                                  | Yes      | The `#exportText` member uses the `() => string` contract.                                                                                                 | —                                                                                                                                                      |
| `subscribe`                  | `(observer: (snapshot: EditorSnapshot) => void) => () => void`                                  | Yes      | The `subscribe` member uses the `(observer: (snapshot: EditorSnapshot) => void) => () => void` contract.                                                   | [`EditorSnapshot`](/tuil/docs/reference/packages/editor/api/editor-snapshot)                                                                                |
| `setDecorations`             | `(decorations: readonly EditorDecoration[]) => void`                                            | Yes      | The `setDecorations` member uses the `(decorations: readonly EditorDecoration[]) => void` contract.                                                        | [`EditorDecoration`](/tuil/docs/reference/packages/editor/api/editor-decoration)                                                                            |
| `setDiagnostics`             | `(diagnostics: readonly EditorDiagnostic[]) => void`                                            | Yes      | The `setDiagnostics` member uses the `(diagnostics: readonly EditorDiagnostic[]) => void` contract.                                                        | [`EditorDiagnostic`](/tuil/docs/reference/packages/editor/api/editor-diagnostic)                                                                            |
| `setMode`                    | `(mode: string) => void`                                                                        | Yes      | The `setMode` member uses the `(mode: string) => void` contract.                                                                                           | —                                                                                                                                                      |
| `setViewportAnchor`          | `(anchor: EditorPosition) => void`                                                              | Yes      | The `setViewportAnchor` member uses the `(anchor: EditorPosition) => void` contract.                                                                       | [`EditorPosition`](/tuil/docs/reference/packages/editor/api/editor-position)                                                                                |
| `dispose`                    | `() => void`                                                                                    | Yes      | The `dispose` member uses the `() => void` contract.                                                                                                       | —                                                                                                                                                      |
| `#notify`                    | `() => void`                                                                                    | Yes      | The `#notify` member uses the `() => void` contract.                                                                                                       | —                                                                                                                                                      |
| `#assertActive`              | `() => void`                                                                                    | Yes      | The `#assertActive` member uses the `() => 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]

* [`EditorChange`](/tuil/docs/reference/packages/editor/api/editor-change)
* [`EditorCommand`](/tuil/docs/reference/packages/editor/api/editor-command)
* [`EditorDecoration`](/tuil/docs/reference/packages/editor/api/editor-decoration)
* [`EditorDiagnostic`](/tuil/docs/reference/packages/editor/api/editor-diagnostic)
* [`EditorPosition`](/tuil/docs/reference/packages/editor/api/editor-position)
* [`EditorProviderOptions`](/tuil/docs/reference/packages/editor/api/editor-provider-options)
* [`EditorRange`](/tuil/docs/reference/packages/editor/api/editor-range)
* [`EditorSelection`](/tuil/docs/reference/packages/editor/api/editor-selection)
* [`EditorSession`](/tuil/docs/reference/packages/editor/api/editor-session)
* [`EditorSnapshot`](/tuil/docs/reference/packages/editor/api/editor-snapshot)
* [`EditorTransaction`](/tuil/docs/reference/packages/editor/api/editor-transaction)

## Source [#source]

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

## Package [#package]

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