Core Package - rahulpandita/react-term GitHub Wiki

The @next_term/core package provides the fundamental data structures and algorithms: the SharedArrayBuffer-backed cell grid, buffer management, the VT parser, and Unicode character-width utilities.

classDiagram
  direction LR
  class CellGrid {
    +SharedArrayBuffer data
    +SharedArrayBuffer dirtyRows
    +setCell(row, col, cp, attrs)
    +isWide(row, col) bool
    +isSpacerCell(row, col) bool
    +markDirty(row)
    +rotateUp()
    +extractText(row) string
  }
  class Buffer {
    +CellGrid grid
    +CursorState cursor
    +scrollTop number
    +scrollBottom number
    +scrollUp()
    +scrollDown()
    +saveCursor()
    +restoreCursor()
  }
  class BufferSet {
    +Buffer normal
    +Buffer alternate
    +Buffer active
    +scrollback Uint32Array[]
    +activateAlternate()
    +activateNormal()
    +pushScrollback()
    +borrowRowBuffer(size)
  }
  class VTParser {
    +parse(data Uint8Array)
    +onOsc(code, data)
    +onDcs(data)
    +onPrint(cp)
    +onExecute(cp)
  }
  class wcwidth {
    +wcwidth(cp) 0|1|2
    +isCombining(cp) bool
  }
  Buffer --> CellGrid
  BufferSet --> Buffer
  VTParser --> BufferSet
  VTParser --> wcwidth

CellGrid

CellGrid is the low-level terminal display buffer backed by SharedArrayBuffer. It can be shared across Web Workers with zero-copy access via getBuffer(), which returns the underlying SAB.

Each cell occupies 8 bytes (2 Γ— Uint32):

Word Content
Word 0 Unicode codepoint
Word 1 (bits 0–7) Foreground ANSI color index
Word 1 (bits 8–14) Attribute flags: bold, italic, underline, blink, strikethrough, inverse
Word 1 (bit 15) ATTR_WIDE β€” set on the left cell of a 2-column character
Word 1 (bits 16–31) Reserved
const grid = new CellGrid(cols, rows);

// Share the backing buffer with a worker
worker.postMessage({ sab: grid.getBuffer() });

// Read and write cells
grid.setCell(row, col, codepoint, attrs);
grid.isWide(row, col);        // true if this is the left cell of a wide character
grid.isSpacerCell(row, col);  // true if this is the right-half spacer cell

Circular scroll: rotateUp() advances the row-origin pointer in O(1) time β€” no data is copied. Workers sharing the same SAB see the rotation immediately.

Buffer and BufferSet

Buffer wraps a CellGrid with cursor state, scroll region (top/bottom), and tab stops. It implements full-screen scrolls using O(1) circular rotation and partial scroll regions using copyWithin.

BufferSet manages the normal screen, alternate screen (DECALTBUF), and scrollback history.

Scroll optimization: When the scrollback buffer is full and a line is about to be evicted, borrowRowBuffer(size) returns the evicted Uint32Array for reuse. copyRowInto(rowIdx, dest) fills it in place. This eliminates ~60 K allocations per 5 MB of output and reduces scrollback time by approximately 38%.

const bs = new BufferSet(cols, rows, maxScrollback);
bs.activateAlternate(); // Enter alternate screen (e.g., vim, less)
bs.activateNormal();    // Return to normal screen

VTParser

VTParser is a VT100/ANSI state machine that handles the full escape sequence repertoire including CSI, OSC, DCS, and Kitty keyboard extensions.

Wide character handling: For non-ASCII codepoints (cp >= 0x80), the parser calls wcwidth(cp) to determine display width. Wide characters (width === 2) are written with ATTR_WIDE set and a spacer cell (codepoint 0) placed in the next column. A wide character at the last column causes an automatic line wrap. Combining marks (isCombining(cp) === true) are absorbed without advancing the cursor.

OSC callbacks:

Code Purpose
OSC 4 / 104 Set / reset indexed color palette
OSC 7 Report working directory
OSC 8 Hyperlink
OSC 10 / 11 / 12 Set/query foreground, background, cursor colors
OSC 52 Clipboard access
OSC 133 Shell integration (semantic zones)

Kitty keyboard: Supports push/pop/query of keyboard protocol modes via CSI = sequences.

wcwidth Module

wcwidth.ts provides Unicode 15.1 character-width lookup. Both wcwidth and isCombining are exported from @next_term/core.

import { wcwidth, isCombining } from '`@next_term/core`';

wcwidth(0x0041);    // 1 β€” 'A' (normal ASCII)
wcwidth(0x4e2d);    // 2 β€” 'δΈ­' (CJK Unified Ideograph)
wcwidth(0x1f600);   // 2 β€” 'πŸ˜€' (emoji)
wcwidth(0x0301);    // 0 β€” combining acute accent

isCombining(0x0301); // true  β€” attaches to previous character
isCombining(0x4e2d); // false

Implementation:

  • O(1) BMP lookup β€” A 64 KB Uint8Array indexed by codepoint (U+0000–U+FFFF) is populated once at module load from East Asian Width and General Category data. Each entry stores width 0, 1, or 2.
  • Binary search for supplementary planes β€” Codepoints U+10000 and above use binary search over compacted zero-width and wide range tables covering emoji, CJK Ext B–I, and tag components.
Return value Meaning Examples
0 Zero-width Combining marks (U+0300+), ZWJ (U+200D), variation selectors, BOM
1 Normal β€” 1 column ASCII, Latin, Greek, Cyrillic, most BMP characters
2 Wide β€” 2 columns CJK Unified Ideographs, Hangul syllables, fullwidth forms, emoji

isCombining(cp) returns true for codepoints β‰₯ U+0300 that have width 0 β€” the subset the parser uses to identify marks that attach to the preceding character without advancing the cursor.

Types

types.ts exports the shared TypeScript interfaces:

Type Fields
CursorState row, col, visible, style, wrapPending
TerminalOptions cols, rows, scrollback, theme?
Theme foreground, background, cursor, cursorAccent, selection, 16 ANSI color slots
DirtyState NONE / PARTIAL / FULL enum
SelectionState anchor, focus
SelectionRange startRow, startCol, endRow, endCol