@m4ike1/ion-tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/alt-screen-search.d.ts +46 -0
- package/dist/alt-screen-search.d.ts.map +1 -0
- package/dist/alt-screen-search.js +282 -0
- package/dist/alt-screen-search.js.map +1 -0
- package/dist/autocomplete.d.ts +75 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +749 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/components/alt-screen-flash.d.ts +13 -0
- package/dist/components/alt-screen-flash.d.ts.map +1 -0
- package/dist/components/alt-screen-flash.js +37 -0
- package/dist/components/alt-screen-flash.js.map +1 -0
- package/dist/components/box.d.ts +24 -0
- package/dist/components/box.d.ts.map +1 -0
- package/dist/components/box.js +133 -0
- package/dist/components/box.js.map +1 -0
- package/dist/components/cancellable-loader.d.ts +22 -0
- package/dist/components/cancellable-loader.d.ts.map +1 -0
- package/dist/components/cancellable-loader.js +35 -0
- package/dist/components/cancellable-loader.js.map +1 -0
- package/dist/components/editor.d.ts +266 -0
- package/dist/components/editor.d.ts.map +1 -0
- package/dist/components/editor.js +2087 -0
- package/dist/components/editor.js.map +1 -0
- package/dist/components/h-stack.d.ts +7 -0
- package/dist/components/h-stack.d.ts.map +1 -0
- package/dist/components/h-stack.js +43 -0
- package/dist/components/h-stack.js.map +1 -0
- package/dist/components/image.d.ts +28 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/image.js +90 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/input.d.ts +48 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +417 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/loader.d.ts +33 -0
- package/dist/components/loader.d.ts.map +1 -0
- package/dist/components/loader.js +76 -0
- package/dist/components/loader.js.map +1 -0
- package/dist/components/markdown.d.ts +103 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +858 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/mouse-region.d.ts +12 -0
- package/dist/components/mouse-region.d.ts.map +1 -0
- package/dist/components/mouse-region.js +21 -0
- package/dist/components/mouse-region.js.map +1 -0
- package/dist/components/scroll-view.d.ts +68 -0
- package/dist/components/scroll-view.d.ts.map +1 -0
- package/dist/components/scroll-view.js +199 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/select-list.d.ts +53 -0
- package/dist/components/select-list.d.ts.map +1 -0
- package/dist/components/select-list.js +206 -0
- package/dist/components/select-list.js.map +1 -0
- package/dist/components/settings-list.d.ts +60 -0
- package/dist/components/settings-list.d.ts.map +1 -0
- package/dist/components/settings-list.js +252 -0
- package/dist/components/settings-list.js.map +1 -0
- package/dist/components/spacer.d.ts +12 -0
- package/dist/components/spacer.d.ts.map +1 -0
- package/dist/components/spacer.js +23 -0
- package/dist/components/spacer.js.map +1 -0
- package/dist/components/stack.d.ts +32 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +111 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/text.d.ts +19 -0
- package/dist/components/text.d.ts.map +1 -0
- package/dist/components/text.js +90 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/truncated-text.d.ts +13 -0
- package/dist/components/truncated-text.d.ts.map +1 -0
- package/dist/components/truncated-text.js +51 -0
- package/dist/components/truncated-text.js.map +1 -0
- package/dist/components/v-stack.d.ts +8 -0
- package/dist/components/v-stack.d.ts.map +1 -0
- package/dist/components/v-stack.js +26 -0
- package/dist/components/v-stack.js.map +1 -0
- package/dist/components/virtual-list.d.ts +51 -0
- package/dist/components/virtual-list.d.ts.map +1 -0
- package/dist/components/virtual-list.js +170 -0
- package/dist/components/virtual-list.js.map +1 -0
- package/dist/editor-component.d.ts +39 -0
- package/dist/editor-component.d.ts.map +1 -0
- package/dist/editor-component.js +2 -0
- package/dist/editor-component.js.map +1 -0
- package/dist/fuzzy.d.ts +16 -0
- package/dist/fuzzy.d.ts.map +1 -0
- package/dist/fuzzy.js +110 -0
- package/dist/fuzzy.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/keybindings.d.ts +366 -0
- package/dist/keybindings.d.ts.map +1 -0
- package/dist/keybindings.js +331 -0
- package/dist/keybindings.js.map +1 -0
- package/dist/keys.d.ts +184 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +1174 -0
- package/dist/keys.js.map +1 -0
- package/dist/kill-ring.d.ts +28 -0
- package/dist/kill-ring.d.ts.map +1 -0
- package/dist/kill-ring.js +44 -0
- package/dist/kill-ring.js.map +1 -0
- package/dist/latex.d.ts +10 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +1285 -0
- package/dist/latex.js.map +1 -0
- package/dist/layout-node.d.ts +59 -0
- package/dist/layout-node.d.ts.map +1 -0
- package/dist/layout-node.js +12 -0
- package/dist/layout-node.js.map +1 -0
- package/dist/layout.d.ts +42 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +404 -0
- package/dist/layout.js.map +1 -0
- package/dist/native-modifiers.d.ts +4 -0
- package/dist/native-modifiers.d.ts.map +1 -0
- package/dist/native-modifiers.js +13 -0
- package/dist/native-modifiers.js.map +1 -0
- package/dist/native-module-path.d.ts +7 -0
- package/dist/native-module-path.d.ts.map +1 -0
- package/dist/native-module-path.js +19 -0
- package/dist/native-module-path.js.map +1 -0
- package/dist/native-platform.d.ts +18 -0
- package/dist/native-platform.d.ts.map +1 -0
- package/dist/native-platform.js +42 -0
- package/dist/native-platform.js.map +1 -0
- package/dist/pty-host.d.ts +224 -0
- package/dist/pty-host.d.ts.map +1 -0
- package/dist/pty-host.js +347 -0
- package/dist/pty-host.js.map +1 -0
- package/dist/stdin-buffer.d.ts +56 -0
- package/dist/stdin-buffer.d.ts.map +1 -0
- package/dist/stdin-buffer.js +366 -0
- package/dist/stdin-buffer.js.map +1 -0
- package/dist/terminal-colors.d.ts +10 -0
- package/dist/terminal-colors.d.ts.map +1 -0
- package/dist/terminal-colors.js +59 -0
- package/dist/terminal-colors.js.map +1 -0
- package/dist/terminal-image.d.ts +118 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +550 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal.d.ts +123 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +442 -0
- package/dist/terminal.js.map +1 -0
- package/dist/tui-alt-screen.d.ts +165 -0
- package/dist/tui-alt-screen.d.ts.map +1 -0
- package/dist/tui-alt-screen.js +1515 -0
- package/dist/tui-alt-screen.js.map +1 -0
- package/dist/tui-main-screen.d.ts +39 -0
- package/dist/tui-main-screen.d.ts.map +1 -0
- package/dist/tui-main-screen.js +594 -0
- package/dist/tui-main-screen.js.map +1 -0
- package/dist/tui.d.ts +431 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +1142 -0
- package/dist/tui.js.map +1 -0
- package/dist/undo-stack.d.ts +17 -0
- package/dist/undo-stack.d.ts.map +1 -0
- package/dist/undo-stack.js +25 -0
- package/dist/undo-stack.js.map +1 -0
- package/dist/utils.d.ts +98 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1200 -0
- package/dist/utils.js.map +1 -0
- package/dist/word-navigation.d.ts +25 -0
- package/dist/word-navigation.d.ts.map +1 -0
- package/dist/word-navigation.js +96 -0
- package/dist/word-navigation.js.map +1 -0
- package/native/clipboard.h +170 -0
- package/native/darwin/README.md +22 -0
- package/native/darwin/build.sh +69 -0
- package/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/native/darwin/src/darwin-platform.m +93 -0
- package/native/linux/README.md +23 -0
- package/native/linux/build.sh +65 -0
- package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/native/linux/src/clipboard-worker.h +70 -0
- package/native/linux/src/linux-platform-x11.c +274 -0
- package/native/napi.h +89 -0
- package/native/win32/README.md +31 -0
- package/native/win32/build.mjs +232 -0
- package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/native/win32/src/win32-platform.c +241 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-host.js","sourceRoot":"","sources":["../src/pty-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PTY host spike (tui-overhaul 1.1-1.5).\n *\n * Spawns the stock ion TUI inside a pseudoterminal and relays bytes\n * bidirectionally between the PTY master and the session owner. The TUI\n * itself stays raw-terminal plus alt-screen and is never rewritten for\n * hosting: this mirrors the Claude Code seam where a separate server hosts\n * the stock TUI under node-pty (see TUI.claude.md section 1.4).\n *\n * Seam rules (pinned by test/pty-host.test.ts):\n * - The core TUI (index.ts, terminal.ts, tui.ts) never imports this module.\n * - node-pty resolves at runtime with prebuilt-first lookup; when no binary\n * is available the caller gets an actionable install hint and the plain\n * terminal path keeps working.\n *\n * Transport decision (task 1.5): in-process handle. A Unix-socket transport\n * would only pay off for reattach-across-process, which no consumer needs\n * yet (the future session-share viewer attaches in-process). Sockets would\n * add framing, auth, and lifecycle management for zero current benefit, so\n * the host is a plain object owning its PTY child. Revisit when an\n * out-of-process attacher arrives.\n *\n * Reattach replay is best-effort byte replay: the host journals PTY output\n * in a bounded ring and, on reattach, nudges the child with a same-dims\n * resize (SIGWINCH repaint, the setWinSize semantic) then replays everything\n * journaled while detached. A terminal-emulator-backed screen dump is\n * future work, not part of this spike.\n *\n * Minimum dimensions stay an open product question: the spike validates\n * positive integers only. Layouts below ~40 columns are known to degrade\n * (the 40x12 golden case pins bounded behavior, not beauty).\n */\n\nimport { createRequire } from \"node:module\";\n\nconst moduleRequire = createRequire(import.meta.url);\n\n/** Transport backing session handles. Pinned to in-process by the task 1.5 decision above. */\nexport const PTY_HOST_TRANSPORT = \"in-process\";\n\n/** Default cap for the reattach replay journal. */\nexport const PTY_HOST_JOURNAL_LIMIT_BYTES = 256 * 1024;\n\n/**\n * Resync prefix used only when the journal overflowed while detached and\n * replay can no longer continue the previous screen state.\n */\nexport const PTY_RESYNC_SEQUENCE = \"\\x1b[2J\\x1b[H\";\n\n/** Caller-specified PTY dimensions in character cells. */\nexport interface PtyDimensions {\n\tcols: number;\n\trows: number;\n}\n\n/** Throw unless cols/rows are positive integers. Integers only: fractional cells do not exist. */\nexport function assertPtyDimensions(cols: number, rows: number): void {\n\tif (!Number.isInteger(cols) || cols < 1) {\n\t\tthrow new RangeError(`PTY cols must be a positive integer, got ${cols}`);\n\t}\n\tif (!Number.isInteger(rows) || rows < 1) {\n\t\tthrow new RangeError(`PTY rows must be a positive integer, got ${rows}`);\n\t}\n}\n\n/**\n * Minimal structural backend over a PTY child. Matches the node-pty\n * surface the host needs without depending on node-pty types, so tests\n * inject fakes and the core TUI never sees this module.\n */\nexport interface PtyBackend {\n\treadonly pid: number;\n\tonData(callback: (data: string) => void): void;\n\tonExit(callback: (exitCode: number, signal: number | undefined) => void): void;\n\twrite(data: string): void;\n\tresize(cols: number, rows: number): void;\n\tkill(signal?: string): void;\n\t/** Optional raw-mode mirror; real PTY slaves are raw by default so most backends omit this. */\n\tsetRawMode?(active: boolean): void;\n}\n\n/** Options passed to a backend spawn function. */\nexport interface PtySpawnOptions {\n\tcommand: string;\n\targs: string[];\n\tcols: number;\n\trows: number;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n\tname: string;\n}\n\n/** Injectable backend factory. Tests pass fakes; production passes the node-pty adapter. */\nexport type PtySpawnFn = (options: PtySpawnOptions) => PtyBackend;\n\n/** Minimal structural view of the node-pty module (spawn only). */\nexport interface NodePtyModule {\n\tspawn(\n\t\tfile: string,\n\t\targs: string[],\n\t\toptions: { name: string; cols: number; rows: number; cwd: string; env: NodeJS.ProcessEnv },\n\t): {\n\t\tpid: number;\n\t\tonData(callback: (data: string) => void): void;\n\t\tonExit(callback: (event: { exitCode: number; signal?: number }) => void): void;\n\t\twrite(data: string): void;\n\t\tresize(cols: number, rows: number): void;\n\t\tkill(signal?: string): void;\n\t};\n}\n\n/** node-pty resolved and ready: prebuilt-first lookup hit. */\nexport interface PtyResolutionSuccess {\n\tavailable: true;\n\t/** Which specifier resolved (explicit ION_PTY_MODULE override or \"node-pty\"). */\n\tsource: string;\n\tspawn: PtySpawnFn;\n}\n\n/** node-pty unavailable: hint carries the actionable install instructions. */\nexport interface PtyResolutionFailure {\n\tavailable: false;\n\thint: string;\n}\n\nexport type PtyResolution = PtyResolutionSuccess | PtyResolutionFailure;\n\nexport interface ResolveNodePtyOptions {\n\t/** Injectable loader for tests. Defaults to require relative to this module. */\n\tloadModule?: (specifier: string) => unknown;\n\tenv?: NodeJS.ProcessEnv;\n}\n\nfunction defaultLoadModule(specifier: string): unknown {\n\treturn moduleRequire(specifier);\n}\n\nfunction buildNodePtyHint(failures: string[]): string {\n\tconst lines = [\n\t\t\"node-pty is unavailable and the PTY host cannot start.\",\n\t\t\"Install it with `npm install node-pty` (prebuilt binaries are used when available;\",\n\t\t\"building from source needs python3, make, and g++).\",\n\t\t\"Set ION_PTY_MODULE to load node-pty from a custom path.\",\n\t\t\"The plain terminal path keeps working without it.\",\n\t];\n\tif (failures.length > 0) {\n\t\tlines.push(`Looked up: ${failures.join(\"; \")}`);\n\t}\n\treturn lines.join(\" \");\n}\n\nfunction adaptNodePty(moduleRef: NodePtyModule, options: PtySpawnOptions): PtyBackend {\n\tconst child = moduleRef.spawn(options.command, options.args, {\n\t\tname: options.name,\n\t\tcols: options.cols,\n\t\trows: options.rows,\n\t\tcwd: options.cwd,\n\t\tenv: options.env,\n\t});\n\treturn {\n\t\tpid: child.pid,\n\t\tonData: (callback) => child.onData(callback),\n\t\tonExit: (callback) => child.onExit((event) => callback(event.exitCode, event.signal)),\n\t\twrite: (data) => child.write(data),\n\t\tresize: (cols, rows) => child.resize(cols, rows),\n\t\tkill: (signal) => child.kill(signal),\n\t};\n}\n\n/**\n * Resolve node-pty at runtime, prebuilt-first (node-pty's own packaging\n * prefers prebuilds via prebuild-install). Lookup order: explicit\n * ION_PTY_MODULE override path first, then the \"node-pty\" package.\n * Never throws: failure returns an actionable hint instead.\n */\nexport function resolveNodePty(options: ResolveNodePtyOptions = {}): PtyResolution {\n\tconst loadModule = options.loadModule ?? defaultLoadModule;\n\tconst env = options.env ?? process.env;\n\tconst override = env.ION_PTY_MODULE?.trim();\n\tconst candidates = override ? [override, \"node-pty\"] : [\"node-pty\"];\n\tconst failures: string[] = [];\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tconst loaded = loadModule(candidate) as Partial<NodePtyModule> | undefined;\n\t\t\tif (loaded && typeof loaded.spawn === \"function\") {\n\t\t\t\tconst moduleRef = loaded as NodePtyModule;\n\t\t\t\treturn {\n\t\t\t\t\tavailable: true,\n\t\t\t\t\tsource: candidate,\n\t\t\t\t\tspawn: (spawnOptions) => adaptNodePty(moduleRef, spawnOptions),\n\t\t\t\t};\n\t\t\t}\n\t\t\tfailures.push(`${candidate}: module loaded but has no spawn() export`);\n\t\t} catch (error) {\n\t\t\tfailures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\treturn { available: false, hint: buildNodePtyHint(failures) };\n}\n\n/** Thrown by createPtyHost when node-pty is unavailable. Carries the install hint as message. */\nexport class PtyHostUnavailableError extends Error {\n\tconstructor(hint: string) {\n\t\tsuper(hint);\n\t\tthis.name = \"PtyHostUnavailableError\";\n\t}\n}\n\nexport interface PtyHostOptions {\n\t/** Stock TUI binary to spawn inside the PTY (host never rewrites the TUI). */\n\tcommand: string;\n\targs?: string[];\n\tcols: number;\n\trows: number;\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** TERM value for the PTY slave. */\n\tname?: string;\n\t/** Replay journal cap in bytes. Defaults to PTY_HOST_JOURNAL_LIMIT_BYTES. */\n\tjournalLimitBytes?: number;\n\t/** Injected backend. Production callers omit this and go through createPtyHost. */\n\tspawn: PtySpawnFn;\n\t/** Convenience exit listener, equivalent to onExit(). */\n\tonExit?: (exitCode: number, signal: number | undefined) => void;\n}\n\nexport type CreatePtyHostOptions = Omit<PtyHostOptions, \"spawn\"> & {\n\tspawn?: PtySpawnFn;\n\tresolveOptions?: ResolveNodePtyOptions;\n};\n\nfunction requireSpawn(resolveOptions?: ResolveNodePtyOptions): PtySpawnFn {\n\tconst resolution = resolveNodePty(resolveOptions);\n\tif (!resolution.available) {\n\t\tthrow new PtyHostUnavailableError(resolution.hint);\n\t}\n\treturn resolution.spawn;\n}\n\n/**\n * Create a host, resolving node-pty unless the caller injects a backend.\n * Throws PtyHostUnavailableError (message = install hint) when no PTY\n * binary resolves; callers fall back to the plain terminal path.\n */\nexport function createPtyHost(options: CreatePtyHostOptions): PtyHost {\n\tconst spawn = options.spawn ?? requireSpawn(options.resolveOptions);\n\treturn new PtyHost({ ...options, spawn });\n}\n\n/** Owner callback receiving PTY master output bytes verbatim. */\nexport type PtyOwnerCallback = (data: string) => void;\n\n/** PTY child exit listener. */\nexport type PtyExitCallback = (exitCode: number, signal: number | undefined) => void;\n\n/**\n * In-process owner of one PTY-hosted TUI session. Byte relay is fully\n * transparent in both directions: the host never interprets owner input\n * (Ctrl+C arrives as 0x03, escape sequences pass through untouched) and\n * forwards child output verbatim. Raw-mode passthrough mirrors the owner\n * raw state to backends that support it; it never gates the byte stream.\n */\nexport class PtyHost {\n\tprivate readonly backend: PtyBackend;\n\tprivate readonly journalLimit: number;\n\tprivate owner: PtyOwnerCallback | undefined;\n\tprivate readonly exitListeners = new Set<PtyExitCallback>();\n\tprivate colsValue: number;\n\tprivate rowsValue: number;\n\tprivate rawModeValue = false;\n\tprivate disposedValue = false;\n\tprivate exitedValue = false;\n\tprivate exitCodeValue: number | undefined;\n\tprivate journal: string[] = [];\n\tprivate journalBytes = 0;\n\tprivate detachChunkIndex: number | undefined;\n\tprivate detachOverflow = false;\n\n\tconstructor(options: PtyHostOptions) {\n\t\tassertPtyDimensions(options.cols, options.rows);\n\t\tthis.colsValue = options.cols;\n\t\tthis.rowsValue = options.rows;\n\t\tthis.journalLimit = options.journalLimitBytes ?? PTY_HOST_JOURNAL_LIMIT_BYTES;\n\t\tif (options.onExit) {\n\t\t\tthis.exitListeners.add(options.onExit);\n\t\t}\n\t\tthis.backend = options.spawn({\n\t\t\tcommand: options.command,\n\t\t\targs: options.args ? [...options.args] : [],\n\t\t\tcols: options.cols,\n\t\t\trows: options.rows,\n\t\t\tcwd: options.cwd ?? process.cwd(),\n\t\t\tenv: options.env ?? process.env,\n\t\t\tname: options.name ?? \"xterm-256color\",\n\t\t});\n\t\tthis.backend.onData((data) => this.handleBackendData(data));\n\t\tthis.backend.onExit((exitCode, signal) => this.handleBackendExit(exitCode, signal));\n\t}\n\n\tget cols(): number {\n\t\treturn this.colsValue;\n\t}\n\n\tget rows(): number {\n\t\treturn this.rowsValue;\n\t}\n\n\tget pid(): number {\n\t\treturn this.backend.pid;\n\t}\n\n\tget isAttached(): boolean {\n\t\treturn this.owner !== undefined;\n\t}\n\n\tget isDetached(): boolean {\n\t\treturn !this.disposedValue && this.owner === undefined;\n\t}\n\n\tget isDisposed(): boolean {\n\t\treturn this.disposedValue;\n\t}\n\n\tget exited(): boolean {\n\t\treturn this.exitedValue;\n\t}\n\n\tget exitCode(): number | undefined {\n\t\treturn this.exitCodeValue;\n\t}\n\n\tget rawMode(): boolean {\n\t\treturn this.rawModeValue;\n\t}\n\n\t/** Initial owner attach. Throws when already attached or disposed. */\n\tattach(owner: PtyOwnerCallback): void {\n\t\tthis.throwIfDisposed();\n\t\tif (this.owner) {\n\t\t\tthrow new Error(\"PtyHost already has an owner; detach first\");\n\t\t}\n\t\tthis.owner = owner;\n\t}\n\n\t/** Owner disconnects; the PTY session keeps running and output is journaled. Idempotent. */\n\tdetach(): void {\n\t\tthis.throwIfDisposed();\n\t\tthis.owner = undefined;\n\t\tthis.detachChunkIndex = this.journal.length;\n\t\tthis.detachOverflow = false;\n\t}\n\n\t/**\n\t * New owner attaches and immediately receives everything journaled\n\t * while detached (the current screen state, best-effort). A same-dims\n\t * resize nudges the child to repaint first. Throws when an owner is\n\t * already attached or the host is disposed.\n\t */\n\treattach(owner: PtyOwnerCallback): void {\n\t\tthis.throwIfDisposed();\n\t\tif (this.owner) {\n\t\t\tthrow new Error(\"PtyHost already has an owner; detach first\");\n\t\t}\n\t\tif (this.detachChunkIndex === undefined) {\n\t\t\tthis.owner = owner;\n\t\t\treturn;\n\t\t}\n\t\tconst replay = this.detachOverflow\n\t\t\t? PTY_RESYNC_SEQUENCE + this.journal.join(\"\")\n\t\t\t: this.journal.slice(this.detachChunkIndex).join(\"\");\n\t\tthis.detachChunkIndex = undefined;\n\t\tthis.detachOverflow = false;\n\t\tif (!this.exitedValue) {\n\t\t\tthis.backend.resize(this.colsValue, this.rowsValue);\n\t\t}\n\t\tthis.owner = owner;\n\t\tif (replay.length > 0) {\n\t\t\towner(replay);\n\t\t}\n\t}\n\n\t/**\n\t * Owner input reaches the PTY byte-transparent. Dropped while\n\t * detached (no owner exists to produce input); throws after dispose.\n\t */\n\twrite(data: string): void {\n\t\tthis.throwIfDisposed();\n\t\tif (!this.owner) return;\n\t\tthis.backend.write(data);\n\t}\n\n\t/**\n\t * Forward owner resize events to the PTY (setWinSize semantics); the\n\t * stock TUI re-layouts to the new dimensions itself.\n\t */\n\tresize(cols: number, rows: number): void {\n\t\tthis.throwIfDisposed();\n\t\tassertPtyDimensions(cols, rows);\n\t\tthis.colsValue = cols;\n\t\tthis.rowsValue = rows;\n\t\tif (!this.exitedValue) {\n\t\t\tthis.backend.resize(cols, rows);\n\t\t}\n\t}\n\n\t/**\n\t * Mirror owner raw-mode state to the backend when it supports it.\n\t * Returns whether the backend honored the request. The byte stream\n\t * is never gated on this flag.\n\t */\n\tsetRawMode(active: boolean): boolean {\n\t\tthis.throwIfDisposed();\n\t\tthis.rawModeValue = active;\n\t\tif (typeof this.backend.setRawMode === \"function\") {\n\t\t\tthis.backend.setRawMode(active);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Subscribe to PTY child exit. Returns an unsubscribe function. */\n\tonExit(callback: PtyExitCallback): () => void {\n\t\tthis.exitListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.exitListeners.delete(callback);\n\t\t};\n\t}\n\n\t/** Kill the PTY session and release the owner. Idempotent. */\n\tdispose(): void {\n\t\tif (this.disposedValue) return;\n\t\tthis.disposedValue = true;\n\t\tthis.owner = undefined;\n\t\tthis.exitListeners.clear();\n\t\ttry {\n\t\t\tthis.backend.kill();\n\t\t} catch {\n\t\t\t// Backend already gone; dispose still releases host state.\n\t\t}\n\t}\n\n\tprivate handleBackendData(data: string): void {\n\t\tthis.appendJournal(data);\n\t\tthis.owner?.(data);\n\t}\n\n\tprivate handleBackendExit(exitCode: number, signal: number | undefined): void {\n\t\tif (this.disposedValue) return;\n\t\tthis.exitedValue = true;\n\t\tthis.exitCodeValue = exitCode;\n\t\tfor (const listener of this.exitListeners) {\n\t\t\tlistener(exitCode, signal);\n\t\t}\n\t}\n\n\tprivate appendJournal(data: string): void {\n\t\tif (this.journalLimit <= 0 || data.length === 0) return;\n\t\tthis.journal.push(data);\n\t\tthis.journalBytes += data.length;\n\t\twhile (this.journal.length > 1 && this.journalBytes > this.journalLimit) {\n\t\t\tconst dropped = this.journal.shift() as string;\n\t\t\tthis.journalBytes -= dropped.length;\n\t\t\tif (this.detachChunkIndex !== undefined) {\n\t\t\t\tif (this.detachChunkIndex > 0) {\n\t\t\t\t\tthis.detachChunkIndex -= 1;\n\t\t\t\t} else {\n\t\t\t\t\tthis.detachOverflow = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate throwIfDisposed(): void {\n\t\tif (this.disposedValue) {\n\t\t\tthrow new Error(\"PtyHost is disposed\");\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StdinBuffer buffers input and emits complete sequences.
|
|
3
|
+
*
|
|
4
|
+
* This is necessary because stdin data events can arrive in partial chunks,
|
|
5
|
+
* especially for escape sequences like mouse events. Without buffering,
|
|
6
|
+
* partial sequences can be misinterpreted as regular keypresses.
|
|
7
|
+
*
|
|
8
|
+
* For example, the mouse SGR sequence `\x1b[<35;20;5m` might arrive as:
|
|
9
|
+
* - Event 1: `\x1b`
|
|
10
|
+
* - Event 2: `[<35`
|
|
11
|
+
* - Event 3: `;20;5m`
|
|
12
|
+
*
|
|
13
|
+
* The buffer accumulates these until a complete sequence is detected.
|
|
14
|
+
* Call the `process()` method to feed input data.
|
|
15
|
+
*
|
|
16
|
+
* Based on code from OpenTUI (https://github.com/anomalyco/opentui)
|
|
17
|
+
* MIT License - Copyright (c) 2025 opentui
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from "events";
|
|
20
|
+
export type StdinBufferOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* Maximum time to wait for an incomplete sequence such as CSI or mouse
|
|
23
|
+
* (default: 50ms).
|
|
24
|
+
*/
|
|
25
|
+
timeout?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum time to wait after a lone ESC before treating it as Escape
|
|
28
|
+
* (default: 10ms). Increase for high-latency Alt+key input (SSH).
|
|
29
|
+
*/
|
|
30
|
+
escapeTimeout?: number;
|
|
31
|
+
};
|
|
32
|
+
export type StdinBufferEventMap = {
|
|
33
|
+
data: [string];
|
|
34
|
+
paste: [string];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Buffers stdin input and emits complete sequences via the 'data' event.
|
|
38
|
+
* Handles partial escape sequences that arrive across multiple chunks.
|
|
39
|
+
*/
|
|
40
|
+
export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
41
|
+
private buffer;
|
|
42
|
+
private timeout;
|
|
43
|
+
private readonly timeoutMs;
|
|
44
|
+
private readonly escapeTimeoutMs;
|
|
45
|
+
private pasteMode;
|
|
46
|
+
private pasteBuffer;
|
|
47
|
+
private pendingKittyPrintableCodepoint;
|
|
48
|
+
constructor(options?: StdinBufferOptions);
|
|
49
|
+
process(data: string | Buffer): void;
|
|
50
|
+
private emitDataSequence;
|
|
51
|
+
flush(): string[];
|
|
52
|
+
clear(): void;
|
|
53
|
+
getBuffer(): string;
|
|
54
|
+
destroy(): void;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=stdin-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdin-buffer.d.ts","sourceRoot":"","sources":["../src/stdin-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA+OtC,MAAM,MAAM,kBAAkB,GAAG;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,mBAAmB,CAAC;IACjE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAA8C;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,8BAA8B,CAAqB;IAE3D,YAAY,OAAO,GAAE,kBAAuB,EAI3C;IAEM,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqG1C;IAED,OAAO,CAAC,gBAAgB;IAWxB,KAAK,IAAI,MAAM,EAAE,CAchB;IAED,KAAK,IAAI,IAAI,CASZ;IAED,SAAS,IAAI,MAAM,CAElB;IAED,OAAO,IAAI,IAAI,CAEd;CACD","sourcesContent":["/**\n * StdinBuffer buffers input and emits complete sequences.\n *\n * This is necessary because stdin data events can arrive in partial chunks,\n * especially for escape sequences like mouse events. Without buffering,\n * partial sequences can be misinterpreted as regular keypresses.\n *\n * For example, the mouse SGR sequence `\\x1b[<35;20;5m` might arrive as:\n * - Event 1: `\\x1b`\n * - Event 2: `[<35`\n * - Event 3: `;20;5m`\n *\n * The buffer accumulates these until a complete sequence is detected.\n * Call the `process()` method to feed input data.\n *\n * Based on code from OpenTUI (https://github.com/anomalyco/opentui)\n * MIT License - Copyright (c) 2025 opentui\n */\n\nimport { EventEmitter } from \"events\";\n\nconst ESC = \"\\x1b\";\nconst DEFAULT_SEQUENCE_TIMEOUT_MS = 50;\nconst DEFAULT_ESCAPE_TIMEOUT_MS = 10;\nconst BRACKETED_PASTE_START = \"\\x1b[200~\";\nconst BRACKETED_PASTE_END = \"\\x1b[201~\";\n\n/**\n * Check if a string is a complete escape sequence or needs more data\n */\nfunction isCompleteSequence(data: string): \"complete\" | \"incomplete\" | \"not-escape\" {\n\tif (!data.startsWith(ESC)) {\n\t\treturn \"not-escape\";\n\t}\n\n\tif (data.length === 1) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst afterEsc = data.slice(1);\n\n\t// CSI sequences: ESC [\n\tif (afterEsc.startsWith(\"[\")) {\n\t\t// Check for old-style mouse sequence: ESC[M + 3 bytes\n\t\tif (afterEsc.startsWith(\"[M\")) {\n\t\t\t// Old-style mouse needs ESC[M + 3 bytes = 6 total\n\t\t\treturn data.length >= 6 ? \"complete\" : \"incomplete\";\n\t\t}\n\t\treturn isCompleteCsiSequence(data);\n\t}\n\n\t// OSC sequences: ESC ]\n\tif (afterEsc.startsWith(\"]\")) {\n\t\treturn isCompleteOscSequence(data);\n\t}\n\n\t// DCS sequences: ESC P ... ESC \\ (includes XTVersion responses)\n\tif (afterEsc.startsWith(\"P\")) {\n\t\treturn isCompleteDcsSequence(data);\n\t}\n\n\t// APC sequences: ESC _ ... ESC \\ (includes Kitty graphics responses)\n\tif (afterEsc.startsWith(\"_\")) {\n\t\treturn isCompleteApcSequence(data);\n\t}\n\n\t// SS3 sequences: ESC O\n\tif (afterEsc.startsWith(\"O\")) {\n\t\t// ESC O followed by a single character\n\t\treturn afterEsc.length >= 2 ? \"complete\" : \"incomplete\";\n\t}\n\n\t// Meta key sequences: ESC followed by a single character\n\tif (afterEsc.length === 1) {\n\t\treturn \"complete\";\n\t}\n\n\t// Unknown escape sequence - treat as complete\n\treturn \"complete\";\n}\n\n/**\n * Check if CSI sequence is complete\n * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)\n */\nfunction isCompleteCsiSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}[`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// Need at least ESC [ and one more character\n\tif (data.length < 3) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst payload = data.slice(2);\n\n\t// CSI sequences end with a byte in the range 0x40-0x7E (@-~)\n\t// This includes all letters and several special characters\n\tconst lastChar = payload[payload.length - 1];\n\tconst lastCharCode = lastChar.charCodeAt(0);\n\n\tif (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {\n\t\t// Special handling for SGR mouse sequences\n\t\t// Format: ESC[<B;X;Ym or ESC[<B;X;YM\n\t\tif (payload.startsWith(\"<\")) {\n\t\t\t// Must have format: <digits;digits;digits[Mm]\n\t\t\tconst mouseMatch = /^<\\d+;\\d+;\\d+[Mm]$/.test(payload);\n\t\t\tif (mouseMatch) {\n\t\t\t\treturn \"complete\";\n\t\t\t}\n\t\t\t// If it ends with M or m but doesn't match the pattern, still incomplete\n\t\t\tif (lastChar === \"M\" || lastChar === \"m\") {\n\t\t\t\t// Check if we have the right structure\n\t\t\t\tconst parts = payload.slice(1, -1).split(\";\");\n\t\t\t\tif (parts.length === 3 && parts.every((p) => /^\\d+$/.test(p))) {\n\t\t\t\t\treturn \"complete\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn \"incomplete\";\n\t\t}\n\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if OSC sequence is complete\n * OSC sequences: ESC ] ... ST (where ST is ESC \\ or BEL)\n */\nfunction isCompleteOscSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}]`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// OSC sequences end with ST (ESC \\) or BEL (\\x07)\n\tif (data.endsWith(`${ESC}\\\\`) || data.endsWith(\"\\x07\")) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if DCS (Device Control String) sequence is complete\n * DCS sequences: ESC P ... ST (where ST is ESC \\)\n * Used for XTVersion responses like ESC P >| ... ESC \\\n */\nfunction isCompleteDcsSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}P`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// DCS sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if APC (Application Program Command) sequence is complete\n * APC sequences: ESC _ ... ST (where ST is ESC \\)\n * Used for Kitty graphics responses like ESC _ G ... ESC \\\n */\nfunction isCompleteApcSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}_`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// APC sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Split accumulated buffer into complete sequences\n */\nfunction parseUnmodifiedKittyPrintableCodepoint(sequence: string): number | undefined {\n\tconst match = sequence.match(/^\\x1b\\[(\\d+)(?::\\d*)?(?::\\d+)?u$/);\n\tif (!match) return undefined;\n\n\tconst codepoint = parseInt(match[1]!, 10);\n\treturn codepoint >= 32 ? codepoint : undefined;\n}\n\nfunction extractCompleteSequences(buffer: string): { sequences: string[]; remainder: string } {\n\tconst sequences: string[] = [];\n\tlet pos = 0;\n\n\twhile (pos < buffer.length) {\n\t\tconst remaining = buffer.slice(pos);\n\n\t\t// Try to extract a sequence starting at this position\n\t\tif (remaining.startsWith(ESC)) {\n\t\t\t// Find the end of this escape sequence\n\t\t\tlet seqEnd = 1;\n\t\t\twhile (seqEnd <= remaining.length) {\n\t\t\t\tconst candidate = remaining.slice(0, seqEnd);\n\t\t\t\tconst status = isCompleteSequence(candidate);\n\n\t\t\t\tif (status === \"complete\") {\n\t\t\t\t\t// WezTerm with enable_kitty_keyboard sends the Escape key press as a\n\t\t\t\t\t// raw '\\x1b' byte (simple text path in encode_kitty, ignoring\n\t\t\t\t\t// DISAMBIGUATE_ESCAPE_CODES) and the release as a full Kitty CSI-u\n\t\t\t\t\t// sequence. These arrive concatenated as '\\x1b\\x1b[27;...u'.\n\t\t\t\t\t// The buffer would normally treat '\\x1b\\x1b' as a complete meta-key\n\t\t\t\t\t// sequence (ESC + single char), leaving '[27;...u' to be typed as\n\t\t\t\t\t// plain text. If the character immediately following '\\x1b\\x1b'\n\t\t\t\t\t// would begin a new escape sequence, emit only the first ESC and\n\t\t\t\t\t// restart from the second.\n\t\t\t\t\tif (candidate === \"\\x1b\\x1b\") {\n\t\t\t\t\t\tconst nextChar = remaining[seqEnd];\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tnextChar === \"[\" || // CSI\n\t\t\t\t\t\t\tnextChar === \"]\" || // OSC\n\t\t\t\t\t\t\tnextChar === \"O\" || // SS3\n\t\t\t\t\t\t\tnextChar === \"P\" || // DCS\n\t\t\t\t\t\t\tnextChar === \"_\" // APC\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tsequences.push(ESC);\n\t\t\t\t\t\t\tpos += 1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (status === \"incomplete\") {\n\t\t\t\t\tseqEnd++;\n\t\t\t\t} else {\n\t\t\t\t\t// Should not happen when starting with ESC\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (seqEnd > remaining.length) {\n\t\t\t\treturn { sequences, remainder: remaining };\n\t\t\t}\n\t\t} else {\n\t\t\t// Not an escape sequence - take a single character\n\t\t\tsequences.push(remaining[0]!);\n\t\t\tpos++;\n\t\t}\n\t}\n\n\treturn { sequences, remainder: \"\" };\n}\n\nexport type StdinBufferOptions = {\n\t/**\n\t * Maximum time to wait for an incomplete sequence such as CSI or mouse\n\t * (default: 50ms).\n\t */\n\ttimeout?: number;\n\t/**\n\t * Maximum time to wait after a lone ESC before treating it as Escape\n\t * (default: 10ms). Increase for high-latency Alt+key input (SSH).\n\t */\n\tescapeTimeout?: number;\n};\n\nexport type StdinBufferEventMap = {\n\tdata: [string];\n\tpaste: [string];\n};\n\n/**\n * Buffers stdin input and emits complete sequences via the 'data' event.\n * Handles partial escape sequences that arrive across multiple chunks.\n */\nexport class StdinBuffer extends EventEmitter<StdinBufferEventMap> {\n\tprivate buffer: string = \"\";\n\tprivate timeout: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly escapeTimeoutMs: number;\n\tprivate pasteMode: boolean = false;\n\tprivate pasteBuffer: string = \"\";\n\tprivate pendingKittyPrintableCodepoint: number | undefined;\n\n\tconstructor(options: StdinBufferOptions = {}) {\n\t\tsuper();\n\t\tthis.timeoutMs = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS;\n\t\tthis.escapeTimeoutMs = options.escapeTimeout ?? DEFAULT_ESCAPE_TIMEOUT_MS;\n\t}\n\n\tpublic process(data: string | Buffer): void {\n\t\t// Clear any pending timeout\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\t// Handle high-byte conversion (for compatibility with parseKeypress)\n\t\t// If buffer has single byte > 127, convert to ESC + (byte - 128)\n\t\tlet str: string;\n\t\tif (Buffer.isBuffer(data)) {\n\t\t\tif (data.length === 1 && data[0]! > 127) {\n\t\t\t\tconst byte = data[0]! - 128;\n\t\t\t\tstr = `\\x1b${String.fromCharCode(byte)}`;\n\t\t\t} else {\n\t\t\t\tstr = data.toString();\n\t\t\t}\n\t\t} else {\n\t\t\tstr = data;\n\t\t}\n\n\t\tif (str.length === 0 && this.buffer.length === 0) {\n\t\t\tthis.emitDataSequence(\"\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.buffer += str;\n\n\t\tif (this.pasteMode) {\n\t\t\tthis.pasteBuffer += this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);\n\t\tif (startIndex !== -1) {\n\t\t\tif (startIndex > 0) {\n\t\t\t\tconst beforePaste = this.buffer.slice(0, startIndex);\n\t\t\t\tconst result = extractCompleteSequences(beforePaste);\n\t\t\t\tfor (const sequence of result.sequences) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\tthis.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);\n\t\t\tthis.pasteMode = true;\n\t\t\tthis.pasteBuffer = this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = extractCompleteSequences(this.buffer);\n\t\tthis.buffer = result.remainder;\n\n\t\tfor (const sequence of result.sequences) {\n\t\t\tthis.emitDataSequence(sequence);\n\t\t}\n\n\t\tif (this.buffer.length > 0) {\n\t\t\tconst timeoutMs = this.buffer === ESC ? this.escapeTimeoutMs : this.timeoutMs;\n\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\tconst flushed = this.flush();\n\n\t\t\t\tfor (const sequence of flushed) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}, timeoutMs);\n\t\t}\n\t}\n\n\tprivate emitDataSequence(sequence: string): void {\n\t\tconst rawCodepoint = sequence.length === 1 ? sequence.codePointAt(0) : undefined;\n\t\tif (rawCodepoint !== undefined && rawCodepoint === this.pendingKittyPrintableCodepoint) {\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.pendingKittyPrintableCodepoint = parseUnmodifiedKittyPrintableCodepoint(sequence);\n\t\tthis.emit(\"data\", sequence);\n\t}\n\n\tflush(): string[] {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\tif (this.buffer.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst sequences = [this.buffer];\n\t\tthis.buffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\treturn sequences;\n\t}\n\n\tclear(): void {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\t\tthis.buffer = \"\";\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t}\n\n\tgetBuffer(): string {\n\t\treturn this.buffer;\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StdinBuffer buffers input and emits complete sequences.
|
|
3
|
+
*
|
|
4
|
+
* This is necessary because stdin data events can arrive in partial chunks,
|
|
5
|
+
* especially for escape sequences like mouse events. Without buffering,
|
|
6
|
+
* partial sequences can be misinterpreted as regular keypresses.
|
|
7
|
+
*
|
|
8
|
+
* For example, the mouse SGR sequence `\x1b[<35;20;5m` might arrive as:
|
|
9
|
+
* - Event 1: `\x1b`
|
|
10
|
+
* - Event 2: `[<35`
|
|
11
|
+
* - Event 3: `;20;5m`
|
|
12
|
+
*
|
|
13
|
+
* The buffer accumulates these until a complete sequence is detected.
|
|
14
|
+
* Call the `process()` method to feed input data.
|
|
15
|
+
*
|
|
16
|
+
* Based on code from OpenTUI (https://github.com/anomalyco/opentui)
|
|
17
|
+
* MIT License - Copyright (c) 2025 opentui
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from "events";
|
|
20
|
+
const ESC = "\x1b";
|
|
21
|
+
const DEFAULT_SEQUENCE_TIMEOUT_MS = 50;
|
|
22
|
+
const DEFAULT_ESCAPE_TIMEOUT_MS = 10;
|
|
23
|
+
const BRACKETED_PASTE_START = "\x1b[200~";
|
|
24
|
+
const BRACKETED_PASTE_END = "\x1b[201~";
|
|
25
|
+
/**
|
|
26
|
+
* Check if a string is a complete escape sequence or needs more data
|
|
27
|
+
*/
|
|
28
|
+
function isCompleteSequence(data) {
|
|
29
|
+
if (!data.startsWith(ESC)) {
|
|
30
|
+
return "not-escape";
|
|
31
|
+
}
|
|
32
|
+
if (data.length === 1) {
|
|
33
|
+
return "incomplete";
|
|
34
|
+
}
|
|
35
|
+
const afterEsc = data.slice(1);
|
|
36
|
+
// CSI sequences: ESC [
|
|
37
|
+
if (afterEsc.startsWith("[")) {
|
|
38
|
+
// Check for old-style mouse sequence: ESC[M + 3 bytes
|
|
39
|
+
if (afterEsc.startsWith("[M")) {
|
|
40
|
+
// Old-style mouse needs ESC[M + 3 bytes = 6 total
|
|
41
|
+
return data.length >= 6 ? "complete" : "incomplete";
|
|
42
|
+
}
|
|
43
|
+
return isCompleteCsiSequence(data);
|
|
44
|
+
}
|
|
45
|
+
// OSC sequences: ESC ]
|
|
46
|
+
if (afterEsc.startsWith("]")) {
|
|
47
|
+
return isCompleteOscSequence(data);
|
|
48
|
+
}
|
|
49
|
+
// DCS sequences: ESC P ... ESC \ (includes XTVersion responses)
|
|
50
|
+
if (afterEsc.startsWith("P")) {
|
|
51
|
+
return isCompleteDcsSequence(data);
|
|
52
|
+
}
|
|
53
|
+
// APC sequences: ESC _ ... ESC \ (includes Kitty graphics responses)
|
|
54
|
+
if (afterEsc.startsWith("_")) {
|
|
55
|
+
return isCompleteApcSequence(data);
|
|
56
|
+
}
|
|
57
|
+
// SS3 sequences: ESC O
|
|
58
|
+
if (afterEsc.startsWith("O")) {
|
|
59
|
+
// ESC O followed by a single character
|
|
60
|
+
return afterEsc.length >= 2 ? "complete" : "incomplete";
|
|
61
|
+
}
|
|
62
|
+
// Meta key sequences: ESC followed by a single character
|
|
63
|
+
if (afterEsc.length === 1) {
|
|
64
|
+
return "complete";
|
|
65
|
+
}
|
|
66
|
+
// Unknown escape sequence - treat as complete
|
|
67
|
+
return "complete";
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if CSI sequence is complete
|
|
71
|
+
* CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)
|
|
72
|
+
*/
|
|
73
|
+
function isCompleteCsiSequence(data) {
|
|
74
|
+
if (!data.startsWith(`${ESC}[`)) {
|
|
75
|
+
return "complete";
|
|
76
|
+
}
|
|
77
|
+
// Need at least ESC [ and one more character
|
|
78
|
+
if (data.length < 3) {
|
|
79
|
+
return "incomplete";
|
|
80
|
+
}
|
|
81
|
+
const payload = data.slice(2);
|
|
82
|
+
// CSI sequences end with a byte in the range 0x40-0x7E (@-~)
|
|
83
|
+
// This includes all letters and several special characters
|
|
84
|
+
const lastChar = payload[payload.length - 1];
|
|
85
|
+
const lastCharCode = lastChar.charCodeAt(0);
|
|
86
|
+
if (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {
|
|
87
|
+
// Special handling for SGR mouse sequences
|
|
88
|
+
// Format: ESC[<B;X;Ym or ESC[<B;X;YM
|
|
89
|
+
if (payload.startsWith("<")) {
|
|
90
|
+
// Must have format: <digits;digits;digits[Mm]
|
|
91
|
+
const mouseMatch = /^<\d+;\d+;\d+[Mm]$/.test(payload);
|
|
92
|
+
if (mouseMatch) {
|
|
93
|
+
return "complete";
|
|
94
|
+
}
|
|
95
|
+
// If it ends with M or m but doesn't match the pattern, still incomplete
|
|
96
|
+
if (lastChar === "M" || lastChar === "m") {
|
|
97
|
+
// Check if we have the right structure
|
|
98
|
+
const parts = payload.slice(1, -1).split(";");
|
|
99
|
+
if (parts.length === 3 && parts.every((p) => /^\d+$/.test(p))) {
|
|
100
|
+
return "complete";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return "incomplete";
|
|
104
|
+
}
|
|
105
|
+
return "complete";
|
|
106
|
+
}
|
|
107
|
+
return "incomplete";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if OSC sequence is complete
|
|
111
|
+
* OSC sequences: ESC ] ... ST (where ST is ESC \ or BEL)
|
|
112
|
+
*/
|
|
113
|
+
function isCompleteOscSequence(data) {
|
|
114
|
+
if (!data.startsWith(`${ESC}]`)) {
|
|
115
|
+
return "complete";
|
|
116
|
+
}
|
|
117
|
+
// OSC sequences end with ST (ESC \) or BEL (\x07)
|
|
118
|
+
if (data.endsWith(`${ESC}\\`) || data.endsWith("\x07")) {
|
|
119
|
+
return "complete";
|
|
120
|
+
}
|
|
121
|
+
return "incomplete";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if DCS (Device Control String) sequence is complete
|
|
125
|
+
* DCS sequences: ESC P ... ST (where ST is ESC \)
|
|
126
|
+
* Used for XTVersion responses like ESC P >| ... ESC \
|
|
127
|
+
*/
|
|
128
|
+
function isCompleteDcsSequence(data) {
|
|
129
|
+
if (!data.startsWith(`${ESC}P`)) {
|
|
130
|
+
return "complete";
|
|
131
|
+
}
|
|
132
|
+
// DCS sequences end with ST (ESC \)
|
|
133
|
+
if (data.endsWith(`${ESC}\\`)) {
|
|
134
|
+
return "complete";
|
|
135
|
+
}
|
|
136
|
+
return "incomplete";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if APC (Application Program Command) sequence is complete
|
|
140
|
+
* APC sequences: ESC _ ... ST (where ST is ESC \)
|
|
141
|
+
* Used for Kitty graphics responses like ESC _ G ... ESC \
|
|
142
|
+
*/
|
|
143
|
+
function isCompleteApcSequence(data) {
|
|
144
|
+
if (!data.startsWith(`${ESC}_`)) {
|
|
145
|
+
return "complete";
|
|
146
|
+
}
|
|
147
|
+
// APC sequences end with ST (ESC \)
|
|
148
|
+
if (data.endsWith(`${ESC}\\`)) {
|
|
149
|
+
return "complete";
|
|
150
|
+
}
|
|
151
|
+
return "incomplete";
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Split accumulated buffer into complete sequences
|
|
155
|
+
*/
|
|
156
|
+
function parseUnmodifiedKittyPrintableCodepoint(sequence) {
|
|
157
|
+
const match = sequence.match(/^\x1b\[(\d+)(?::\d*)?(?::\d+)?u$/);
|
|
158
|
+
if (!match)
|
|
159
|
+
return undefined;
|
|
160
|
+
const codepoint = parseInt(match[1], 10);
|
|
161
|
+
return codepoint >= 32 ? codepoint : undefined;
|
|
162
|
+
}
|
|
163
|
+
function extractCompleteSequences(buffer) {
|
|
164
|
+
const sequences = [];
|
|
165
|
+
let pos = 0;
|
|
166
|
+
while (pos < buffer.length) {
|
|
167
|
+
const remaining = buffer.slice(pos);
|
|
168
|
+
// Try to extract a sequence starting at this position
|
|
169
|
+
if (remaining.startsWith(ESC)) {
|
|
170
|
+
// Find the end of this escape sequence
|
|
171
|
+
let seqEnd = 1;
|
|
172
|
+
while (seqEnd <= remaining.length) {
|
|
173
|
+
const candidate = remaining.slice(0, seqEnd);
|
|
174
|
+
const status = isCompleteSequence(candidate);
|
|
175
|
+
if (status === "complete") {
|
|
176
|
+
// WezTerm with enable_kitty_keyboard sends the Escape key press as a
|
|
177
|
+
// raw '\x1b' byte (simple text path in encode_kitty, ignoring
|
|
178
|
+
// DISAMBIGUATE_ESCAPE_CODES) and the release as a full Kitty CSI-u
|
|
179
|
+
// sequence. These arrive concatenated as '\x1b\x1b[27;...u'.
|
|
180
|
+
// The buffer would normally treat '\x1b\x1b' as a complete meta-key
|
|
181
|
+
// sequence (ESC + single char), leaving '[27;...u' to be typed as
|
|
182
|
+
// plain text. If the character immediately following '\x1b\x1b'
|
|
183
|
+
// would begin a new escape sequence, emit only the first ESC and
|
|
184
|
+
// restart from the second.
|
|
185
|
+
if (candidate === "\x1b\x1b") {
|
|
186
|
+
const nextChar = remaining[seqEnd];
|
|
187
|
+
if (nextChar === "[" || // CSI
|
|
188
|
+
nextChar === "]" || // OSC
|
|
189
|
+
nextChar === "O" || // SS3
|
|
190
|
+
nextChar === "P" || // DCS
|
|
191
|
+
nextChar === "_" // APC
|
|
192
|
+
) {
|
|
193
|
+
sequences.push(ESC);
|
|
194
|
+
pos += 1;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
sequences.push(candidate);
|
|
199
|
+
pos += seqEnd;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
else if (status === "incomplete") {
|
|
203
|
+
seqEnd++;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// Should not happen when starting with ESC
|
|
207
|
+
sequences.push(candidate);
|
|
208
|
+
pos += seqEnd;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (seqEnd > remaining.length) {
|
|
213
|
+
return { sequences, remainder: remaining };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
// Not an escape sequence - take a single character
|
|
218
|
+
sequences.push(remaining[0]);
|
|
219
|
+
pos++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { sequences, remainder: "" };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Buffers stdin input and emits complete sequences via the 'data' event.
|
|
226
|
+
* Handles partial escape sequences that arrive across multiple chunks.
|
|
227
|
+
*/
|
|
228
|
+
export class StdinBuffer extends EventEmitter {
|
|
229
|
+
buffer = "";
|
|
230
|
+
timeout = null;
|
|
231
|
+
timeoutMs;
|
|
232
|
+
escapeTimeoutMs;
|
|
233
|
+
pasteMode = false;
|
|
234
|
+
pasteBuffer = "";
|
|
235
|
+
pendingKittyPrintableCodepoint;
|
|
236
|
+
constructor(options = {}) {
|
|
237
|
+
super();
|
|
238
|
+
this.timeoutMs = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS;
|
|
239
|
+
this.escapeTimeoutMs = options.escapeTimeout ?? DEFAULT_ESCAPE_TIMEOUT_MS;
|
|
240
|
+
}
|
|
241
|
+
process(data) {
|
|
242
|
+
// Clear any pending timeout
|
|
243
|
+
if (this.timeout) {
|
|
244
|
+
clearTimeout(this.timeout);
|
|
245
|
+
this.timeout = null;
|
|
246
|
+
}
|
|
247
|
+
// Handle high-byte conversion (for compatibility with parseKeypress)
|
|
248
|
+
// If buffer has single byte > 127, convert to ESC + (byte - 128)
|
|
249
|
+
let str;
|
|
250
|
+
if (Buffer.isBuffer(data)) {
|
|
251
|
+
if (data.length === 1 && data[0] > 127) {
|
|
252
|
+
const byte = data[0] - 128;
|
|
253
|
+
str = `\x1b${String.fromCharCode(byte)}`;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
str = data.toString();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
str = data;
|
|
261
|
+
}
|
|
262
|
+
if (str.length === 0 && this.buffer.length === 0) {
|
|
263
|
+
this.emitDataSequence("");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.buffer += str;
|
|
267
|
+
if (this.pasteMode) {
|
|
268
|
+
this.pasteBuffer += this.buffer;
|
|
269
|
+
this.buffer = "";
|
|
270
|
+
const endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);
|
|
271
|
+
if (endIndex !== -1) {
|
|
272
|
+
const pastedContent = this.pasteBuffer.slice(0, endIndex);
|
|
273
|
+
const remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);
|
|
274
|
+
this.pasteMode = false;
|
|
275
|
+
this.pasteBuffer = "";
|
|
276
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
277
|
+
this.emit("paste", pastedContent);
|
|
278
|
+
if (remaining.length > 0) {
|
|
279
|
+
this.process(remaining);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);
|
|
285
|
+
if (startIndex !== -1) {
|
|
286
|
+
if (startIndex > 0) {
|
|
287
|
+
const beforePaste = this.buffer.slice(0, startIndex);
|
|
288
|
+
const result = extractCompleteSequences(beforePaste);
|
|
289
|
+
for (const sequence of result.sequences) {
|
|
290
|
+
this.emitDataSequence(sequence);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
294
|
+
this.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);
|
|
295
|
+
this.pasteMode = true;
|
|
296
|
+
this.pasteBuffer = this.buffer;
|
|
297
|
+
this.buffer = "";
|
|
298
|
+
const endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);
|
|
299
|
+
if (endIndex !== -1) {
|
|
300
|
+
const pastedContent = this.pasteBuffer.slice(0, endIndex);
|
|
301
|
+
const remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);
|
|
302
|
+
this.pasteMode = false;
|
|
303
|
+
this.pasteBuffer = "";
|
|
304
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
305
|
+
this.emit("paste", pastedContent);
|
|
306
|
+
if (remaining.length > 0) {
|
|
307
|
+
this.process(remaining);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const result = extractCompleteSequences(this.buffer);
|
|
313
|
+
this.buffer = result.remainder;
|
|
314
|
+
for (const sequence of result.sequences) {
|
|
315
|
+
this.emitDataSequence(sequence);
|
|
316
|
+
}
|
|
317
|
+
if (this.buffer.length > 0) {
|
|
318
|
+
const timeoutMs = this.buffer === ESC ? this.escapeTimeoutMs : this.timeoutMs;
|
|
319
|
+
this.timeout = setTimeout(() => {
|
|
320
|
+
const flushed = this.flush();
|
|
321
|
+
for (const sequence of flushed) {
|
|
322
|
+
this.emitDataSequence(sequence);
|
|
323
|
+
}
|
|
324
|
+
}, timeoutMs);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
emitDataSequence(sequence) {
|
|
328
|
+
const rawCodepoint = sequence.length === 1 ? sequence.codePointAt(0) : undefined;
|
|
329
|
+
if (rawCodepoint !== undefined && rawCodepoint === this.pendingKittyPrintableCodepoint) {
|
|
330
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this.pendingKittyPrintableCodepoint = parseUnmodifiedKittyPrintableCodepoint(sequence);
|
|
334
|
+
this.emit("data", sequence);
|
|
335
|
+
}
|
|
336
|
+
flush() {
|
|
337
|
+
if (this.timeout) {
|
|
338
|
+
clearTimeout(this.timeout);
|
|
339
|
+
this.timeout = null;
|
|
340
|
+
}
|
|
341
|
+
if (this.buffer.length === 0) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
const sequences = [this.buffer];
|
|
345
|
+
this.buffer = "";
|
|
346
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
347
|
+
return sequences;
|
|
348
|
+
}
|
|
349
|
+
clear() {
|
|
350
|
+
if (this.timeout) {
|
|
351
|
+
clearTimeout(this.timeout);
|
|
352
|
+
this.timeout = null;
|
|
353
|
+
}
|
|
354
|
+
this.buffer = "";
|
|
355
|
+
this.pasteMode = false;
|
|
356
|
+
this.pasteBuffer = "";
|
|
357
|
+
this.pendingKittyPrintableCodepoint = undefined;
|
|
358
|
+
}
|
|
359
|
+
getBuffer() {
|
|
360
|
+
return this.buffer;
|
|
361
|
+
}
|
|
362
|
+
destroy() {
|
|
363
|
+
this.clear();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=stdin-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdin-buffer.js","sourceRoot":"","sources":["../src/stdin-buffer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * StdinBuffer buffers input and emits complete sequences.\n *\n * This is necessary because stdin data events can arrive in partial chunks,\n * especially for escape sequences like mouse events. Without buffering,\n * partial sequences can be misinterpreted as regular keypresses.\n *\n * For example, the mouse SGR sequence `\\x1b[<35;20;5m` might arrive as:\n * - Event 1: `\\x1b`\n * - Event 2: `[<35`\n * - Event 3: `;20;5m`\n *\n * The buffer accumulates these until a complete sequence is detected.\n * Call the `process()` method to feed input data.\n *\n * Based on code from OpenTUI (https://github.com/anomalyco/opentui)\n * MIT License - Copyright (c) 2025 opentui\n */\n\nimport { EventEmitter } from \"events\";\n\nconst ESC = \"\\x1b\";\nconst DEFAULT_SEQUENCE_TIMEOUT_MS = 50;\nconst DEFAULT_ESCAPE_TIMEOUT_MS = 10;\nconst BRACKETED_PASTE_START = \"\\x1b[200~\";\nconst BRACKETED_PASTE_END = \"\\x1b[201~\";\n\n/**\n * Check if a string is a complete escape sequence or needs more data\n */\nfunction isCompleteSequence(data: string): \"complete\" | \"incomplete\" | \"not-escape\" {\n\tif (!data.startsWith(ESC)) {\n\t\treturn \"not-escape\";\n\t}\n\n\tif (data.length === 1) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst afterEsc = data.slice(1);\n\n\t// CSI sequences: ESC [\n\tif (afterEsc.startsWith(\"[\")) {\n\t\t// Check for old-style mouse sequence: ESC[M + 3 bytes\n\t\tif (afterEsc.startsWith(\"[M\")) {\n\t\t\t// Old-style mouse needs ESC[M + 3 bytes = 6 total\n\t\t\treturn data.length >= 6 ? \"complete\" : \"incomplete\";\n\t\t}\n\t\treturn isCompleteCsiSequence(data);\n\t}\n\n\t// OSC sequences: ESC ]\n\tif (afterEsc.startsWith(\"]\")) {\n\t\treturn isCompleteOscSequence(data);\n\t}\n\n\t// DCS sequences: ESC P ... ESC \\ (includes XTVersion responses)\n\tif (afterEsc.startsWith(\"P\")) {\n\t\treturn isCompleteDcsSequence(data);\n\t}\n\n\t// APC sequences: ESC _ ... ESC \\ (includes Kitty graphics responses)\n\tif (afterEsc.startsWith(\"_\")) {\n\t\treturn isCompleteApcSequence(data);\n\t}\n\n\t// SS3 sequences: ESC O\n\tif (afterEsc.startsWith(\"O\")) {\n\t\t// ESC O followed by a single character\n\t\treturn afterEsc.length >= 2 ? \"complete\" : \"incomplete\";\n\t}\n\n\t// Meta key sequences: ESC followed by a single character\n\tif (afterEsc.length === 1) {\n\t\treturn \"complete\";\n\t}\n\n\t// Unknown escape sequence - treat as complete\n\treturn \"complete\";\n}\n\n/**\n * Check if CSI sequence is complete\n * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)\n */\nfunction isCompleteCsiSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}[`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// Need at least ESC [ and one more character\n\tif (data.length < 3) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst payload = data.slice(2);\n\n\t// CSI sequences end with a byte in the range 0x40-0x7E (@-~)\n\t// This includes all letters and several special characters\n\tconst lastChar = payload[payload.length - 1];\n\tconst lastCharCode = lastChar.charCodeAt(0);\n\n\tif (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {\n\t\t// Special handling for SGR mouse sequences\n\t\t// Format: ESC[<B;X;Ym or ESC[<B;X;YM\n\t\tif (payload.startsWith(\"<\")) {\n\t\t\t// Must have format: <digits;digits;digits[Mm]\n\t\t\tconst mouseMatch = /^<\\d+;\\d+;\\d+[Mm]$/.test(payload);\n\t\t\tif (mouseMatch) {\n\t\t\t\treturn \"complete\";\n\t\t\t}\n\t\t\t// If it ends with M or m but doesn't match the pattern, still incomplete\n\t\t\tif (lastChar === \"M\" || lastChar === \"m\") {\n\t\t\t\t// Check if we have the right structure\n\t\t\t\tconst parts = payload.slice(1, -1).split(\";\");\n\t\t\t\tif (parts.length === 3 && parts.every((p) => /^\\d+$/.test(p))) {\n\t\t\t\t\treturn \"complete\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn \"incomplete\";\n\t\t}\n\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if OSC sequence is complete\n * OSC sequences: ESC ] ... ST (where ST is ESC \\ or BEL)\n */\nfunction isCompleteOscSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}]`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// OSC sequences end with ST (ESC \\) or BEL (\\x07)\n\tif (data.endsWith(`${ESC}\\\\`) || data.endsWith(\"\\x07\")) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if DCS (Device Control String) sequence is complete\n * DCS sequences: ESC P ... ST (where ST is ESC \\)\n * Used for XTVersion responses like ESC P >| ... ESC \\\n */\nfunction isCompleteDcsSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}P`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// DCS sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if APC (Application Program Command) sequence is complete\n * APC sequences: ESC _ ... ST (where ST is ESC \\)\n * Used for Kitty graphics responses like ESC _ G ... ESC \\\n */\nfunction isCompleteApcSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}_`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// APC sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Split accumulated buffer into complete sequences\n */\nfunction parseUnmodifiedKittyPrintableCodepoint(sequence: string): number | undefined {\n\tconst match = sequence.match(/^\\x1b\\[(\\d+)(?::\\d*)?(?::\\d+)?u$/);\n\tif (!match) return undefined;\n\n\tconst codepoint = parseInt(match[1]!, 10);\n\treturn codepoint >= 32 ? codepoint : undefined;\n}\n\nfunction extractCompleteSequences(buffer: string): { sequences: string[]; remainder: string } {\n\tconst sequences: string[] = [];\n\tlet pos = 0;\n\n\twhile (pos < buffer.length) {\n\t\tconst remaining = buffer.slice(pos);\n\n\t\t// Try to extract a sequence starting at this position\n\t\tif (remaining.startsWith(ESC)) {\n\t\t\t// Find the end of this escape sequence\n\t\t\tlet seqEnd = 1;\n\t\t\twhile (seqEnd <= remaining.length) {\n\t\t\t\tconst candidate = remaining.slice(0, seqEnd);\n\t\t\t\tconst status = isCompleteSequence(candidate);\n\n\t\t\t\tif (status === \"complete\") {\n\t\t\t\t\t// WezTerm with enable_kitty_keyboard sends the Escape key press as a\n\t\t\t\t\t// raw '\\x1b' byte (simple text path in encode_kitty, ignoring\n\t\t\t\t\t// DISAMBIGUATE_ESCAPE_CODES) and the release as a full Kitty CSI-u\n\t\t\t\t\t// sequence. These arrive concatenated as '\\x1b\\x1b[27;...u'.\n\t\t\t\t\t// The buffer would normally treat '\\x1b\\x1b' as a complete meta-key\n\t\t\t\t\t// sequence (ESC + single char), leaving '[27;...u' to be typed as\n\t\t\t\t\t// plain text. If the character immediately following '\\x1b\\x1b'\n\t\t\t\t\t// would begin a new escape sequence, emit only the first ESC and\n\t\t\t\t\t// restart from the second.\n\t\t\t\t\tif (candidate === \"\\x1b\\x1b\") {\n\t\t\t\t\t\tconst nextChar = remaining[seqEnd];\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tnextChar === \"[\" || // CSI\n\t\t\t\t\t\t\tnextChar === \"]\" || // OSC\n\t\t\t\t\t\t\tnextChar === \"O\" || // SS3\n\t\t\t\t\t\t\tnextChar === \"P\" || // DCS\n\t\t\t\t\t\t\tnextChar === \"_\" // APC\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tsequences.push(ESC);\n\t\t\t\t\t\t\tpos += 1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (status === \"incomplete\") {\n\t\t\t\t\tseqEnd++;\n\t\t\t\t} else {\n\t\t\t\t\t// Should not happen when starting with ESC\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (seqEnd > remaining.length) {\n\t\t\t\treturn { sequences, remainder: remaining };\n\t\t\t}\n\t\t} else {\n\t\t\t// Not an escape sequence - take a single character\n\t\t\tsequences.push(remaining[0]!);\n\t\t\tpos++;\n\t\t}\n\t}\n\n\treturn { sequences, remainder: \"\" };\n}\n\nexport type StdinBufferOptions = {\n\t/**\n\t * Maximum time to wait for an incomplete sequence such as CSI or mouse\n\t * (default: 50ms).\n\t */\n\ttimeout?: number;\n\t/**\n\t * Maximum time to wait after a lone ESC before treating it as Escape\n\t * (default: 10ms). Increase for high-latency Alt+key input (SSH).\n\t */\n\tescapeTimeout?: number;\n};\n\nexport type StdinBufferEventMap = {\n\tdata: [string];\n\tpaste: [string];\n};\n\n/**\n * Buffers stdin input and emits complete sequences via the 'data' event.\n * Handles partial escape sequences that arrive across multiple chunks.\n */\nexport class StdinBuffer extends EventEmitter<StdinBufferEventMap> {\n\tprivate buffer: string = \"\";\n\tprivate timeout: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly escapeTimeoutMs: number;\n\tprivate pasteMode: boolean = false;\n\tprivate pasteBuffer: string = \"\";\n\tprivate pendingKittyPrintableCodepoint: number | undefined;\n\n\tconstructor(options: StdinBufferOptions = {}) {\n\t\tsuper();\n\t\tthis.timeoutMs = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS;\n\t\tthis.escapeTimeoutMs = options.escapeTimeout ?? DEFAULT_ESCAPE_TIMEOUT_MS;\n\t}\n\n\tpublic process(data: string | Buffer): void {\n\t\t// Clear any pending timeout\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\t// Handle high-byte conversion (for compatibility with parseKeypress)\n\t\t// If buffer has single byte > 127, convert to ESC + (byte - 128)\n\t\tlet str: string;\n\t\tif (Buffer.isBuffer(data)) {\n\t\t\tif (data.length === 1 && data[0]! > 127) {\n\t\t\t\tconst byte = data[0]! - 128;\n\t\t\t\tstr = `\\x1b${String.fromCharCode(byte)}`;\n\t\t\t} else {\n\t\t\t\tstr = data.toString();\n\t\t\t}\n\t\t} else {\n\t\t\tstr = data;\n\t\t}\n\n\t\tif (str.length === 0 && this.buffer.length === 0) {\n\t\t\tthis.emitDataSequence(\"\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.buffer += str;\n\n\t\tif (this.pasteMode) {\n\t\t\tthis.pasteBuffer += this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);\n\t\tif (startIndex !== -1) {\n\t\t\tif (startIndex > 0) {\n\t\t\t\tconst beforePaste = this.buffer.slice(0, startIndex);\n\t\t\t\tconst result = extractCompleteSequences(beforePaste);\n\t\t\t\tfor (const sequence of result.sequences) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\tthis.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);\n\t\t\tthis.pasteMode = true;\n\t\t\tthis.pasteBuffer = this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = extractCompleteSequences(this.buffer);\n\t\tthis.buffer = result.remainder;\n\n\t\tfor (const sequence of result.sequences) {\n\t\t\tthis.emitDataSequence(sequence);\n\t\t}\n\n\t\tif (this.buffer.length > 0) {\n\t\t\tconst timeoutMs = this.buffer === ESC ? this.escapeTimeoutMs : this.timeoutMs;\n\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\tconst flushed = this.flush();\n\n\t\t\t\tfor (const sequence of flushed) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}, timeoutMs);\n\t\t}\n\t}\n\n\tprivate emitDataSequence(sequence: string): void {\n\t\tconst rawCodepoint = sequence.length === 1 ? sequence.codePointAt(0) : undefined;\n\t\tif (rawCodepoint !== undefined && rawCodepoint === this.pendingKittyPrintableCodepoint) {\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.pendingKittyPrintableCodepoint = parseUnmodifiedKittyPrintableCodepoint(sequence);\n\t\tthis.emit(\"data\", sequence);\n\t}\n\n\tflush(): string[] {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\tif (this.buffer.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst sequences = [this.buffer];\n\t\tthis.buffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\treturn sequences;\n\t}\n\n\tclear(): void {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\t\tthis.buffer = \"\";\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t}\n\n\tgetBuffer(): string {\n\t\treturn this.buffer;\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RgbColor {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
export type TerminalColorScheme = "dark" | "light";
|
|
7
|
+
export declare function isOsc11BackgroundColorResponse(data: string): boolean;
|
|
8
|
+
export declare function parseOsc11BackgroundColor(data: string): RgbColor | undefined;
|
|
9
|
+
export declare function parseTerminalColorSchemeReport(data: string): TerminalColorScheme | undefined;
|
|
10
|
+
//# sourceMappingURL=terminal-colors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-colors.d.ts","sourceRoot":"","sources":["../src/terminal-colors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACV;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,CAAC;AAwBnD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA8B5E;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAM5F","sourcesContent":["export interface RgbColor {\n\tr: number;\n\tg: number;\n\tb: number;\n}\n\nexport type TerminalColorScheme = \"dark\" | \"light\";\n\nfunction hexToRgb(hex: string): RgbColor {\n\tconst normalized = hex.startsWith(\"#\") ? hex.slice(1) : hex;\n\tconst r = parseInt(normalized.slice(0, 2), 16);\n\tconst g = parseInt(normalized.slice(2, 4), 16);\n\tconst b = parseInt(normalized.slice(4, 6), 16);\n\treturn { r, g, b };\n}\n\nfunction parseOscHexChannel(channel: string): number | undefined {\n\tif (!/^[0-9a-f]+$/i.test(channel)) {\n\t\treturn undefined;\n\t}\n\tconst max = 16 ** channel.length - 1;\n\tif (max <= 0) {\n\t\treturn undefined;\n\t}\n\treturn Math.round((parseInt(channel, 16) / max) * 255);\n}\n\nconst OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN = /^\\x1b\\]11;([^\\x07\\x1b]*)(?:\\x07|\\x1b\\\\)$/i;\nconst COLOR_SCHEME_REPORT_PATTERN = /^(?:\\x1b\\[\\?997;(1|2)n)+$/;\n\nexport function isOsc11BackgroundColorResponse(data: string): boolean {\n\treturn OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN.test(data);\n}\n\nexport function parseOsc11BackgroundColor(data: string): RgbColor | undefined {\n\tconst match = data.match(OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\n\tconst value = match[1].trim();\n\tif (value.startsWith(\"#\")) {\n\t\tconst hex = value.slice(1);\n\t\tif (/^[0-9a-f]{6}$/i.test(hex)) {\n\t\t\treturn hexToRgb(value);\n\t\t}\n\t\tif (/^[0-9a-f]{12}$/i.test(hex)) {\n\t\t\tconst r = parseOscHexChannel(hex.slice(0, 4));\n\t\t\tconst g = parseOscHexChannel(hex.slice(4, 8));\n\t\t\tconst b = parseOscHexChannel(hex.slice(8, 12));\n\t\t\treturn r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tconst rgbValue = value.replace(/^rgba?:/i, \"\");\n\tconst [red, green, blue] = rgbValue.split(\"/\");\n\tif (red === undefined || green === undefined || blue === undefined) {\n\t\treturn undefined;\n\t}\n\tconst r = parseOscHexChannel(red);\n\tconst g = parseOscHexChannel(green);\n\tconst b = parseOscHexChannel(blue);\n\treturn r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;\n}\n\nexport function parseTerminalColorSchemeReport(data: string): TerminalColorScheme | undefined {\n\tconst match = data.match(COLOR_SCHEME_REPORT_PATTERN);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn match[1] === \"2\" ? \"light\" : \"dark\";\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function hexToRgb(hex) {
|
|
2
|
+
const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
3
|
+
const r = parseInt(normalized.slice(0, 2), 16);
|
|
4
|
+
const g = parseInt(normalized.slice(2, 4), 16);
|
|
5
|
+
const b = parseInt(normalized.slice(4, 6), 16);
|
|
6
|
+
return { r, g, b };
|
|
7
|
+
}
|
|
8
|
+
function parseOscHexChannel(channel) {
|
|
9
|
+
if (!/^[0-9a-f]+$/i.test(channel)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const max = 16 ** channel.length - 1;
|
|
13
|
+
if (max <= 0) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return Math.round((parseInt(channel, 16) / max) * 255);
|
|
17
|
+
}
|
|
18
|
+
const OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN = /^\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)$/i;
|
|
19
|
+
const COLOR_SCHEME_REPORT_PATTERN = /^(?:\x1b\[\?997;(1|2)n)+$/;
|
|
20
|
+
export function isOsc11BackgroundColorResponse(data) {
|
|
21
|
+
return OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN.test(data);
|
|
22
|
+
}
|
|
23
|
+
export function parseOsc11BackgroundColor(data) {
|
|
24
|
+
const match = data.match(OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const value = match[1].trim();
|
|
29
|
+
if (value.startsWith("#")) {
|
|
30
|
+
const hex = value.slice(1);
|
|
31
|
+
if (/^[0-9a-f]{6}$/i.test(hex)) {
|
|
32
|
+
return hexToRgb(value);
|
|
33
|
+
}
|
|
34
|
+
if (/^[0-9a-f]{12}$/i.test(hex)) {
|
|
35
|
+
const r = parseOscHexChannel(hex.slice(0, 4));
|
|
36
|
+
const g = parseOscHexChannel(hex.slice(4, 8));
|
|
37
|
+
const b = parseOscHexChannel(hex.slice(8, 12));
|
|
38
|
+
return r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const rgbValue = value.replace(/^rgba?:/i, "");
|
|
43
|
+
const [red, green, blue] = rgbValue.split("/");
|
|
44
|
+
if (red === undefined || green === undefined || blue === undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const r = parseOscHexChannel(red);
|
|
48
|
+
const g = parseOscHexChannel(green);
|
|
49
|
+
const b = parseOscHexChannel(blue);
|
|
50
|
+
return r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;
|
|
51
|
+
}
|
|
52
|
+
export function parseTerminalColorSchemeReport(data) {
|
|
53
|
+
const match = data.match(COLOR_SCHEME_REPORT_PATTERN);
|
|
54
|
+
if (!match) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return match[1] === "2" ? "light" : "dark";
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=terminal-colors.js.map
|