@asm-agent/tui 0.8.2
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 +795 -0
- package/dist/autocomplete.d.ts +61 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +601 -0
- package/dist/autocomplete.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 +114 -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 +275 -0
- package/dist/components/editor.d.ts.map +1 -0
- package/dist/components/editor.js +1968 -0
- package/dist/components/editor.js.map +1 -0
- package/dist/components/image.d.ts +34 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/image.js +96 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/input.d.ts +37 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +385 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/loader.d.ts +29 -0
- package/dist/components/loader.d.ts.map +1 -0
- package/dist/components/loader.js +69 -0
- package/dist/components/loader.js.map +1 -0
- package/dist/components/markdown.d.ts +100 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +773 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/select-list.d.ts +61 -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 +49 -0
- package/dist/components/settings-list.d.ts.map +1 -0
- package/dist/components/settings-list.js +170 -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 +21 -0
- package/dist/components/spacer.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 +77 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/truncated-text.d.ts +14 -0
- package/dist/components/truncated-text.d.ts.map +1 -0
- package/dist/components/truncated-text.js +44 -0
- package/dist/components/truncated-text.js.map +1 -0
- package/dist/editor-component.d.ts +51 -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/fullscreen.d.ts +105 -0
- package/dist/fullscreen.d.ts.map +1 -0
- package/dist/fullscreen.js +597 -0
- package/dist/fullscreen.js.map +1 -0
- package/dist/fuzzy.d.ts +26 -0
- package/dist/fuzzy.d.ts.map +1 -0
- package/dist/fuzzy.js +114 -0
- package/dist/fuzzy.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/keybindings.d.ts +239 -0
- package/dist/keybindings.d.ts.map +1 -0
- package/dist/keybindings.js +222 -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 +1163 -0
- package/dist/keys.js.map +1 -0
- package/dist/kill-ring.d.ts +26 -0
- package/dist/kill-ring.d.ts.map +1 -0
- package/dist/kill-ring.js +42 -0
- package/dist/kill-ring.js.map +1 -0
- package/dist/latex.d.ts +21 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +797 -0
- package/dist/latex.js.map +1 -0
- package/dist/mouse.d.ts +24 -0
- package/dist/mouse.d.ts.map +1 -0
- package/dist/mouse.js +34 -0
- package/dist/mouse.js.map +1 -0
- package/dist/render-cache.d.ts +9 -0
- package/dist/render-cache.d.ts.map +1 -0
- package/dist/render-cache.js +23 -0
- package/dist/render-cache.js.map +1 -0
- package/dist/selection-metadata.d.ts +22 -0
- package/dist/selection-metadata.d.ts.map +1 -0
- package/dist/selection-metadata.js +141 -0
- package/dist/selection-metadata.js.map +1 -0
- package/dist/slash-command-context.d.ts +12 -0
- package/dist/slash-command-context.d.ts.map +1 -0
- package/dist/slash-command-context.js +32 -0
- package/dist/slash-command-context.js.map +1 -0
- package/dist/stdin-buffer.d.ts +50 -0
- package/dist/stdin-buffer.d.ts.map +1 -0
- package/dist/stdin-buffer.js +313 -0
- package/dist/stdin-buffer.js.map +1 -0
- package/dist/terminal-colors.d.ts +31 -0
- package/dist/terminal-colors.d.ts.map +1 -0
- package/dist/terminal-colors.js +161 -0
- package/dist/terminal-colors.js.map +1 -0
- package/dist/terminal-image.d.ts +85 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +324 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal.d.ts +113 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +486 -0
- package/dist/terminal.js.map +1 -0
- package/dist/tui.d.ts +310 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +1673 -0
- package/dist/tui.js.map +1 -0
- package/dist/undo-stack.d.ts +16 -0
- package/dist/undo-stack.d.ts.map +1 -0
- package/dist/undo-stack.js +26 -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 +1233 -0
- package/dist/utils.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAAiB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAW1E;;GAEG;AACH,qBAAa,KAAM,YAAW,SAAS,EAAE,SAAS;IACjD,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAAa;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAE7B,OAAO,EAAE,OAAO,CAAS;IAEzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAEnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAA8C;IAEhE,OAAO,CAAC,SAAS,CAA+B;IAEhD,QAAQ,IAAI,MAAM,CAEjB;IAED,SAAS,IAAI,MAAM,CAElB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkJ9B;IAED,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,WAAW;IAUnB,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAyD9B;CACD","sourcesContent":["import { getKeybindings } from \"../keybindings.js\";\nimport { decodeKittyPrintable } from \"../keys.js\";\nimport { KillRing } from \"../kill-ring.js\";\nimport { type Component, CURSOR_MARKER, type Focusable } from \"../tui.js\";\nimport { UndoStack } from \"../undo-stack.js\";\nimport { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from \"../utils.js\";\n\nconst segmenter = getSegmenter();\n\ninterface InputState {\n\tvalue: string;\n\tcursor: number;\n}\n\n/**\n * Input component - single-line text input with horizontal scrolling\n */\nexport class Input implements Component, Focusable {\n\tprivate value: string = \"\";\n\tprivate cursor: number = 0; // Cursor position in the value\n\tpublic onSubmit?: (value: string) => void;\n\tpublic onEscape?: () => void;\n\n\tfocused: boolean = false;\n\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\n\t}\n\n\tgetCursor(): number {\n\t\treturn this.cursor;\n\t}\n\n\tsetValue(value: string): void {\n\t\tthis.value = value;\n\t\tthis.cursor = Math.min(this.cursor, value.length);\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (data.includes(\"\\x1b[200~\")) {\n\t\t\tthis.isInPaste = true;\n\t\t\tthis.pasteBuffer = \"\";\n\t\t\tdata = data.replace(\"\\x1b[200~\", \"\");\n\t\t}\n\n\t\tif (this.isInPaste) {\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tif (remaining) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.input.submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.editor.yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLeft\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\t\tthis.cursor -= lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorRight\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\t\tthis.cursor += firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineStart\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineEnd\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = this.value.length;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordRight\")) {\n\t\t\tthis.moveWordForwards();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kitty CSI-u printable character (e.g. \\x1b[97u for 'a').\n\t\t// Terminals with Kitty protocol flag 1 (disambiguate) send CSI-u for all keys,\n\t\t// including plain printable characters. Decode before the control-char check\n\t\t// since CSI-u sequences contain \\x1b which would be rejected.\n\t\tconst kittyPrintable = decodeKittyPrintable(data);\n\t\tif (kittyPrintable !== undefined) {\n\t\t\tthis.insertCharacter(kittyPrintable);\n\t\t\treturn;\n\t\t}\n\n\t\t// Regular character input - accept printable characters including Unicode,\n\t\t// but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)\n\t\tconst hasControlChars = [...data].some((ch) => {\n\t\t\tconst code = ch.charCodeAt(0);\n\t\t\treturn code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);\n\t\t});\n\t\tif (!hasControlChars) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate insertCharacter(char: string): void {\n\t\tif (isWhitespaceChar(char) || this.lastAction !== \"type-word\") {\n\t\t\tthis.pushUndo();\n\t\t}\n\t\tthis.lastAction = \"type-word\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);\n\t\tthis.cursor += char.length;\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor > 0) {\n\t\t\tthis.pushUndo();\n\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\tconst graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor - graphemeLength) + this.value.slice(this.cursor);\n\t\t\tthis.cursor -= graphemeLength;\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor < this.value.length) {\n\t\t\tthis.pushUndo();\n\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\tconst graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + graphemeLength);\n\t\t}\n\t}\n\n\tprivate deleteToLineStart(): void {\n\t\tif (this.cursor === 0) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(0, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(this.cursor);\n\t\tthis.cursor = 0;\n\t}\n\n\tprivate deleteToLineEnd(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(0, this.cursor);\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tif (this.cursor === 0) return;\n\n\t\t// Save lastAction before cursor movement (moveWordBackwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordBackwards();\n\t\tconst deleteFrom = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(deleteFrom, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, deleteFrom) + this.value.slice(this.cursor);\n\t\tthis.cursor = deleteFrom;\n\t}\n\n\tprivate deleteWordForward(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\n\t\t// Save lastAction before cursor movement (moveWordForwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordForwards();\n\t\tconst deleteTo = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(this.cursor, deleteTo);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(deleteTo);\n\t}\n\n\tprivate yank(): void {\n\t\tconst text = this.killRing.peek();\n\t\tif (!text) return;\n\n\t\tthis.pushUndo();\n\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate yankPop(): void {\n\t\tif (this.lastAction !== \"yank\" || this.killRing.length <= 1) return;\n\n\t\tthis.pushUndo();\n\n\t\tconst prevText = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor - prevText.length) + this.value.slice(this.cursor);\n\t\tthis.cursor -= prevText.length;\n\n\t\tthis.killRing.rotate();\n\t\tconst text = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate pushUndo(): void {\n\t\tthis.undoStack.push({ value: this.value, cursor: this.cursor });\n\t}\n\n\tprivate undo(): void {\n\t\tconst snapshot = this.undoStack.pop();\n\t\tif (!snapshot) return;\n\t\tthis.value = snapshot.value;\n\t\tthis.cursor = snapshot.cursor;\n\t\tthis.lastAction = null;\n\t}\n\n\tprivate moveWordBackwards(): void {\n\t\tif (this.cursor === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textBeforeCursor = this.value.slice(0, this.cursor);\n\t\tconst graphemes = [...segmenter.segment(textBeforeCursor)];\n\n\t\twhile (graphemes.length > 0 && isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t}\n\n\t\tif (graphemes.length > 0) {\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1]?.segment || \"\";\n\t\t\tif (isPunctuationChar(lastGrapheme)) {\n\t\t\t\twhile (graphemes.length > 0 && isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile (\n\t\t\t\t\tgraphemes.length > 0 &&\n\t\t\t\t\t!isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\") &&\n\t\t\t\t\t!isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")\n\t\t\t\t) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate moveWordForwards(): void {\n\t\tif (this.cursor >= this.value.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textAfterCursor = this.value.slice(this.cursor);\n\t\tconst segments = segmenter.segment(textAfterCursor);\n\t\tconst iterator = segments[Symbol.iterator]();\n\t\tlet next = iterator.next();\n\n\t\twhile (!next.done && isWhitespaceChar(next.value.segment)) {\n\t\t\tthis.cursor += next.value.segment.length;\n\t\t\tnext = iterator.next();\n\t\t}\n\n\t\tif (!next.done) {\n\t\t\tconst firstGrapheme = next.value.segment;\n\t\t\tif (isPunctuationChar(firstGrapheme)) {\n\t\t\t\twhile (!next.done && isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile (!next.done && !isWhitespaceChar(next.value.segment) && !isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\tthis.lastAction = null;\n\t\tthis.pushUndo();\n\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\tthis.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);\n\t\tthis.cursor += cleanText.length;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\treturn [prompt];\n\t\t}\n\n\t\tlet visibleText = \"\";\n\t\tlet cursorDisplay = this.cursor;\n\t\tconst totalWidth = visibleWidth(this.value);\n\n\t\tif (totalWidth < availableWidth) {\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\tconst scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;\n\t\t\tconst cursorCol = visibleWidth(this.value.slice(0, this.cursor));\n\n\t\t\tif (scrollWidth > 0) {\n\t\t\t\tconst halfWidth = Math.floor(scrollWidth / 2);\n\t\t\t\tlet startCol = 0;\n\n\t\t\t\tif (cursorCol < halfWidth) {\n\t\t\t\t\tstartCol = 0;\n\t\t\t\t} else if (cursorCol > totalWidth - halfWidth) {\n\t\t\t\t\tstartCol = Math.max(0, totalWidth - scrollWidth);\n\t\t\t\t} else {\n\t\t\t\t\tstartCol = Math.max(0, cursorCol - halfWidth);\n\t\t\t\t}\n\n\t\t\t\tvisibleText = sliceByColumn(this.value, startCol, scrollWidth, true);\n\t\t\t\tconst beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol), true);\n\t\t\t\tcursorDisplay = beforeCursor.length;\n\t\t\t} else {\n\t\t\t\tvisibleText = \"\";\n\t\t\t\tcursorDisplay = 0;\n\t\t\t}\n\t\t}\n\n\t\tconst graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];\n\t\tconst cursorGrapheme = graphemes[0];\n\n\t\tconst beforeCursor = visibleText.slice(0, cursorDisplay);\n\t\tconst atCursor = cursorGrapheme?.segment ?? \" \"; // Character at cursor, or space if at end\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + atCursor.length);\n\n\t\t// Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)\n\t\tconst marker = this.focused ? CURSOR_MARKER : \"\";\n\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal\n\t\tconst textWithCursor = beforeCursor + marker + cursorChar + afterCursor;\n\n\t\tconst visualLength = visibleWidth(textWithCursor);\n\t\tconst padding = \" \".repeat(Math.max(0, availableWidth - visualLength));\n\t\tconst line = prompt + textWithCursor + padding;\n\n\t\treturn [line];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { getKeybindings } from "../keybindings.js";
|
|
2
|
+
import { decodeKittyPrintable } from "../keys.js";
|
|
3
|
+
import { KillRing } from "../kill-ring.js";
|
|
4
|
+
import { CURSOR_MARKER } from "../tui.js";
|
|
5
|
+
import { UndoStack } from "../undo-stack.js";
|
|
6
|
+
import { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from "../utils.js";
|
|
7
|
+
const segmenter = getSegmenter();
|
|
8
|
+
/**
|
|
9
|
+
* Input component - single-line text input with horizontal scrolling
|
|
10
|
+
*/
|
|
11
|
+
export class Input {
|
|
12
|
+
value = "";
|
|
13
|
+
cursor = 0; // Cursor position in the value
|
|
14
|
+
onSubmit;
|
|
15
|
+
onEscape;
|
|
16
|
+
focused = false;
|
|
17
|
+
pasteBuffer = "";
|
|
18
|
+
isInPaste = false;
|
|
19
|
+
killRing = new KillRing();
|
|
20
|
+
lastAction = null;
|
|
21
|
+
undoStack = new UndoStack();
|
|
22
|
+
getValue() {
|
|
23
|
+
return this.value;
|
|
24
|
+
}
|
|
25
|
+
getCursor() {
|
|
26
|
+
return this.cursor;
|
|
27
|
+
}
|
|
28
|
+
setValue(value) {
|
|
29
|
+
this.value = value;
|
|
30
|
+
this.cursor = Math.min(this.cursor, value.length);
|
|
31
|
+
}
|
|
32
|
+
handleInput(data) {
|
|
33
|
+
if (data.includes("\x1b[200~")) {
|
|
34
|
+
this.isInPaste = true;
|
|
35
|
+
this.pasteBuffer = "";
|
|
36
|
+
data = data.replace("\x1b[200~", "");
|
|
37
|
+
}
|
|
38
|
+
if (this.isInPaste) {
|
|
39
|
+
this.pasteBuffer += data;
|
|
40
|
+
const endIndex = this.pasteBuffer.indexOf("\x1b[201~");
|
|
41
|
+
if (endIndex !== -1) {
|
|
42
|
+
const pasteContent = this.pasteBuffer.substring(0, endIndex);
|
|
43
|
+
this.handlePaste(pasteContent);
|
|
44
|
+
this.isInPaste = false;
|
|
45
|
+
const remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \x1b[201~
|
|
46
|
+
this.pasteBuffer = "";
|
|
47
|
+
if (remaining) {
|
|
48
|
+
this.handleInput(remaining);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const kb = getKeybindings();
|
|
54
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
55
|
+
if (this.onEscape)
|
|
56
|
+
this.onEscape();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (kb.matches(data, "tui.editor.undo")) {
|
|
60
|
+
this.undo();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (kb.matches(data, "tui.input.submit") || data === "\n") {
|
|
64
|
+
if (this.onSubmit)
|
|
65
|
+
this.onSubmit(this.value);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (kb.matches(data, "tui.editor.deleteCharBackward")) {
|
|
69
|
+
this.handleBackspace();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (kb.matches(data, "tui.editor.deleteCharForward")) {
|
|
73
|
+
this.handleForwardDelete();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (kb.matches(data, "tui.editor.deleteWordBackward")) {
|
|
77
|
+
this.deleteWordBackwards();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (kb.matches(data, "tui.editor.deleteWordForward")) {
|
|
81
|
+
this.deleteWordForward();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (kb.matches(data, "tui.editor.deleteToLineStart")) {
|
|
85
|
+
this.deleteToLineStart();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (kb.matches(data, "tui.editor.deleteToLineEnd")) {
|
|
89
|
+
this.deleteToLineEnd();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (kb.matches(data, "tui.editor.yank")) {
|
|
93
|
+
this.yank();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (kb.matches(data, "tui.editor.yankPop")) {
|
|
97
|
+
this.yankPop();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (kb.matches(data, "tui.editor.cursorLeft")) {
|
|
101
|
+
this.lastAction = null;
|
|
102
|
+
if (this.cursor > 0) {
|
|
103
|
+
const beforeCursor = this.value.slice(0, this.cursor);
|
|
104
|
+
const graphemes = [...segmenter.segment(beforeCursor)];
|
|
105
|
+
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
106
|
+
this.cursor -= lastGrapheme ? lastGrapheme.segment.length : 1;
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (kb.matches(data, "tui.editor.cursorRight")) {
|
|
111
|
+
this.lastAction = null;
|
|
112
|
+
if (this.cursor < this.value.length) {
|
|
113
|
+
const afterCursor = this.value.slice(this.cursor);
|
|
114
|
+
const graphemes = [...segmenter.segment(afterCursor)];
|
|
115
|
+
const firstGrapheme = graphemes[0];
|
|
116
|
+
this.cursor += firstGrapheme ? firstGrapheme.segment.length : 1;
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (kb.matches(data, "tui.editor.cursorLineStart")) {
|
|
121
|
+
this.lastAction = null;
|
|
122
|
+
this.cursor = 0;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (kb.matches(data, "tui.editor.cursorLineEnd")) {
|
|
126
|
+
this.lastAction = null;
|
|
127
|
+
this.cursor = this.value.length;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (kb.matches(data, "tui.editor.cursorWordLeft")) {
|
|
131
|
+
this.moveWordBackwards();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (kb.matches(data, "tui.editor.cursorWordRight")) {
|
|
135
|
+
this.moveWordForwards();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Kitty CSI-u printable character (e.g. \x1b[97u for 'a').
|
|
139
|
+
// Terminals with Kitty protocol flag 1 (disambiguate) send CSI-u for all keys,
|
|
140
|
+
// including plain printable characters. Decode before the control-char check
|
|
141
|
+
// since CSI-u sequences contain \x1b which would be rejected.
|
|
142
|
+
const kittyPrintable = decodeKittyPrintable(data);
|
|
143
|
+
if (kittyPrintable !== undefined) {
|
|
144
|
+
this.insertCharacter(kittyPrintable);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Regular character input - accept printable characters including Unicode,
|
|
148
|
+
// but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)
|
|
149
|
+
const hasControlChars = [...data].some((ch) => {
|
|
150
|
+
const code = ch.charCodeAt(0);
|
|
151
|
+
return code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);
|
|
152
|
+
});
|
|
153
|
+
if (!hasControlChars) {
|
|
154
|
+
this.insertCharacter(data);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
insertCharacter(char) {
|
|
158
|
+
if (isWhitespaceChar(char) || this.lastAction !== "type-word") {
|
|
159
|
+
this.pushUndo();
|
|
160
|
+
}
|
|
161
|
+
this.lastAction = "type-word";
|
|
162
|
+
this.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);
|
|
163
|
+
this.cursor += char.length;
|
|
164
|
+
}
|
|
165
|
+
handleBackspace() {
|
|
166
|
+
this.lastAction = null;
|
|
167
|
+
if (this.cursor > 0) {
|
|
168
|
+
this.pushUndo();
|
|
169
|
+
const beforeCursor = this.value.slice(0, this.cursor);
|
|
170
|
+
const graphemes = [...segmenter.segment(beforeCursor)];
|
|
171
|
+
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
172
|
+
const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
|
|
173
|
+
this.value = this.value.slice(0, this.cursor - graphemeLength) + this.value.slice(this.cursor);
|
|
174
|
+
this.cursor -= graphemeLength;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
handleForwardDelete() {
|
|
178
|
+
this.lastAction = null;
|
|
179
|
+
if (this.cursor < this.value.length) {
|
|
180
|
+
this.pushUndo();
|
|
181
|
+
const afterCursor = this.value.slice(this.cursor);
|
|
182
|
+
const graphemes = [...segmenter.segment(afterCursor)];
|
|
183
|
+
const firstGrapheme = graphemes[0];
|
|
184
|
+
const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
|
|
185
|
+
this.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + graphemeLength);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
deleteToLineStart() {
|
|
189
|
+
if (this.cursor === 0)
|
|
190
|
+
return;
|
|
191
|
+
this.pushUndo();
|
|
192
|
+
const deletedText = this.value.slice(0, this.cursor);
|
|
193
|
+
this.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === "kill" });
|
|
194
|
+
this.lastAction = "kill";
|
|
195
|
+
this.value = this.value.slice(this.cursor);
|
|
196
|
+
this.cursor = 0;
|
|
197
|
+
}
|
|
198
|
+
deleteToLineEnd() {
|
|
199
|
+
if (this.cursor >= this.value.length)
|
|
200
|
+
return;
|
|
201
|
+
this.pushUndo();
|
|
202
|
+
const deletedText = this.value.slice(this.cursor);
|
|
203
|
+
this.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === "kill" });
|
|
204
|
+
this.lastAction = "kill";
|
|
205
|
+
this.value = this.value.slice(0, this.cursor);
|
|
206
|
+
}
|
|
207
|
+
deleteWordBackwards() {
|
|
208
|
+
if (this.cursor === 0)
|
|
209
|
+
return;
|
|
210
|
+
// Save lastAction before cursor movement (moveWordBackwards resets it)
|
|
211
|
+
const wasKill = this.lastAction === "kill";
|
|
212
|
+
this.pushUndo();
|
|
213
|
+
const oldCursor = this.cursor;
|
|
214
|
+
this.moveWordBackwards();
|
|
215
|
+
const deleteFrom = this.cursor;
|
|
216
|
+
this.cursor = oldCursor;
|
|
217
|
+
const deletedText = this.value.slice(deleteFrom, this.cursor);
|
|
218
|
+
this.killRing.push(deletedText, { prepend: true, accumulate: wasKill });
|
|
219
|
+
this.lastAction = "kill";
|
|
220
|
+
this.value = this.value.slice(0, deleteFrom) + this.value.slice(this.cursor);
|
|
221
|
+
this.cursor = deleteFrom;
|
|
222
|
+
}
|
|
223
|
+
deleteWordForward() {
|
|
224
|
+
if (this.cursor >= this.value.length)
|
|
225
|
+
return;
|
|
226
|
+
// Save lastAction before cursor movement (moveWordForwards resets it)
|
|
227
|
+
const wasKill = this.lastAction === "kill";
|
|
228
|
+
this.pushUndo();
|
|
229
|
+
const oldCursor = this.cursor;
|
|
230
|
+
this.moveWordForwards();
|
|
231
|
+
const deleteTo = this.cursor;
|
|
232
|
+
this.cursor = oldCursor;
|
|
233
|
+
const deletedText = this.value.slice(this.cursor, deleteTo);
|
|
234
|
+
this.killRing.push(deletedText, { prepend: false, accumulate: wasKill });
|
|
235
|
+
this.lastAction = "kill";
|
|
236
|
+
this.value = this.value.slice(0, this.cursor) + this.value.slice(deleteTo);
|
|
237
|
+
}
|
|
238
|
+
yank() {
|
|
239
|
+
const text = this.killRing.peek();
|
|
240
|
+
if (!text)
|
|
241
|
+
return;
|
|
242
|
+
this.pushUndo();
|
|
243
|
+
this.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);
|
|
244
|
+
this.cursor += text.length;
|
|
245
|
+
this.lastAction = "yank";
|
|
246
|
+
}
|
|
247
|
+
yankPop() {
|
|
248
|
+
if (this.lastAction !== "yank" || this.killRing.length <= 1)
|
|
249
|
+
return;
|
|
250
|
+
this.pushUndo();
|
|
251
|
+
const prevText = this.killRing.peek() || "";
|
|
252
|
+
this.value = this.value.slice(0, this.cursor - prevText.length) + this.value.slice(this.cursor);
|
|
253
|
+
this.cursor -= prevText.length;
|
|
254
|
+
this.killRing.rotate();
|
|
255
|
+
const text = this.killRing.peek() || "";
|
|
256
|
+
this.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);
|
|
257
|
+
this.cursor += text.length;
|
|
258
|
+
this.lastAction = "yank";
|
|
259
|
+
}
|
|
260
|
+
pushUndo() {
|
|
261
|
+
this.undoStack.push({ value: this.value, cursor: this.cursor });
|
|
262
|
+
}
|
|
263
|
+
undo() {
|
|
264
|
+
const snapshot = this.undoStack.pop();
|
|
265
|
+
if (!snapshot)
|
|
266
|
+
return;
|
|
267
|
+
this.value = snapshot.value;
|
|
268
|
+
this.cursor = snapshot.cursor;
|
|
269
|
+
this.lastAction = null;
|
|
270
|
+
}
|
|
271
|
+
moveWordBackwards() {
|
|
272
|
+
if (this.cursor === 0) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
this.lastAction = null;
|
|
276
|
+
const textBeforeCursor = this.value.slice(0, this.cursor);
|
|
277
|
+
const graphemes = [...segmenter.segment(textBeforeCursor)];
|
|
278
|
+
while (graphemes.length > 0 && isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
279
|
+
this.cursor -= graphemes.pop()?.segment.length || 0;
|
|
280
|
+
}
|
|
281
|
+
if (graphemes.length > 0) {
|
|
282
|
+
const lastGrapheme = graphemes[graphemes.length - 1]?.segment || "";
|
|
283
|
+
if (isPunctuationChar(lastGrapheme)) {
|
|
284
|
+
while (graphemes.length > 0 && isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
285
|
+
this.cursor -= graphemes.pop()?.segment.length || 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
while (graphemes.length > 0 &&
|
|
290
|
+
!isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
291
|
+
!isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
292
|
+
this.cursor -= graphemes.pop()?.segment.length || 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
moveWordForwards() {
|
|
298
|
+
if (this.cursor >= this.value.length) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.lastAction = null;
|
|
302
|
+
const textAfterCursor = this.value.slice(this.cursor);
|
|
303
|
+
const segments = segmenter.segment(textAfterCursor);
|
|
304
|
+
const iterator = segments[Symbol.iterator]();
|
|
305
|
+
let next = iterator.next();
|
|
306
|
+
while (!next.done && isWhitespaceChar(next.value.segment)) {
|
|
307
|
+
this.cursor += next.value.segment.length;
|
|
308
|
+
next = iterator.next();
|
|
309
|
+
}
|
|
310
|
+
if (!next.done) {
|
|
311
|
+
const firstGrapheme = next.value.segment;
|
|
312
|
+
if (isPunctuationChar(firstGrapheme)) {
|
|
313
|
+
while (!next.done && isPunctuationChar(next.value.segment)) {
|
|
314
|
+
this.cursor += next.value.segment.length;
|
|
315
|
+
next = iterator.next();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
while (!next.done && !isWhitespaceChar(next.value.segment) && !isPunctuationChar(next.value.segment)) {
|
|
320
|
+
this.cursor += next.value.segment.length;
|
|
321
|
+
next = iterator.next();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
handlePaste(pastedText) {
|
|
327
|
+
this.lastAction = null;
|
|
328
|
+
this.pushUndo();
|
|
329
|
+
const cleanText = pastedText.replace(/\r\n/g, "").replace(/\r/g, "").replace(/\n/g, "").replace(/\t/g, " ");
|
|
330
|
+
this.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);
|
|
331
|
+
this.cursor += cleanText.length;
|
|
332
|
+
}
|
|
333
|
+
invalidate() { }
|
|
334
|
+
render(width) {
|
|
335
|
+
const prompt = "> ";
|
|
336
|
+
const availableWidth = width - prompt.length;
|
|
337
|
+
if (availableWidth <= 0) {
|
|
338
|
+
return [prompt];
|
|
339
|
+
}
|
|
340
|
+
let visibleText = "";
|
|
341
|
+
let cursorDisplay = this.cursor;
|
|
342
|
+
const totalWidth = visibleWidth(this.value);
|
|
343
|
+
if (totalWidth < availableWidth) {
|
|
344
|
+
visibleText = this.value;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
|
|
348
|
+
const cursorCol = visibleWidth(this.value.slice(0, this.cursor));
|
|
349
|
+
if (scrollWidth > 0) {
|
|
350
|
+
const halfWidth = Math.floor(scrollWidth / 2);
|
|
351
|
+
let startCol = 0;
|
|
352
|
+
if (cursorCol < halfWidth) {
|
|
353
|
+
startCol = 0;
|
|
354
|
+
}
|
|
355
|
+
else if (cursorCol > totalWidth - halfWidth) {
|
|
356
|
+
startCol = Math.max(0, totalWidth - scrollWidth);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
startCol = Math.max(0, cursorCol - halfWidth);
|
|
360
|
+
}
|
|
361
|
+
visibleText = sliceByColumn(this.value, startCol, scrollWidth, true);
|
|
362
|
+
const beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol), true);
|
|
363
|
+
cursorDisplay = beforeCursor.length;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
visibleText = "";
|
|
367
|
+
cursorDisplay = 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];
|
|
371
|
+
const cursorGrapheme = graphemes[0];
|
|
372
|
+
const beforeCursor = visibleText.slice(0, cursorDisplay);
|
|
373
|
+
const atCursor = cursorGrapheme?.segment ?? " "; // Character at cursor, or space if at end
|
|
374
|
+
const afterCursor = visibleText.slice(cursorDisplay + atCursor.length);
|
|
375
|
+
// Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)
|
|
376
|
+
const marker = this.focused ? CURSOR_MARKER : "";
|
|
377
|
+
const cursorChar = `\x1b[7m${atCursor}\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal
|
|
378
|
+
const textWithCursor = beforeCursor + marker + cursorChar + afterCursor;
|
|
379
|
+
const visualLength = visibleWidth(textWithCursor);
|
|
380
|
+
const padding = " ".repeat(Math.max(0, availableWidth - visualLength));
|
|
381
|
+
const line = prompt + textWithCursor + padding;
|
|
382
|
+
return [line];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"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","sourcesContent":["import { getKeybindings } from \"../keybindings.js\";\nimport { decodeKittyPrintable } from \"../keys.js\";\nimport { KillRing } from \"../kill-ring.js\";\nimport { type Component, CURSOR_MARKER, type Focusable } from \"../tui.js\";\nimport { UndoStack } from \"../undo-stack.js\";\nimport { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from \"../utils.js\";\n\nconst segmenter = getSegmenter();\n\ninterface InputState {\n\tvalue: string;\n\tcursor: number;\n}\n\n/**\n * Input component - single-line text input with horizontal scrolling\n */\nexport class Input implements Component, Focusable {\n\tprivate value: string = \"\";\n\tprivate cursor: number = 0; // Cursor position in the value\n\tpublic onSubmit?: (value: string) => void;\n\tpublic onEscape?: () => void;\n\n\tfocused: boolean = false;\n\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\n\t}\n\n\tgetCursor(): number {\n\t\treturn this.cursor;\n\t}\n\n\tsetValue(value: string): void {\n\t\tthis.value = value;\n\t\tthis.cursor = Math.min(this.cursor, value.length);\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (data.includes(\"\\x1b[200~\")) {\n\t\t\tthis.isInPaste = true;\n\t\t\tthis.pasteBuffer = \"\";\n\t\t\tdata = data.replace(\"\\x1b[200~\", \"\");\n\t\t}\n\n\t\tif (this.isInPaste) {\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tif (remaining) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.input.submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.editor.yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLeft\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\t\tthis.cursor -= lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorRight\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\t\tthis.cursor += firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineStart\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorLineEnd\")) {\n\t\t\tthis.lastAction = null;\n\t\t\tthis.cursor = this.value.length;\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.editor.cursorWordRight\")) {\n\t\t\tthis.moveWordForwards();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kitty CSI-u printable character (e.g. \\x1b[97u for 'a').\n\t\t// Terminals with Kitty protocol flag 1 (disambiguate) send CSI-u for all keys,\n\t\t// including plain printable characters. Decode before the control-char check\n\t\t// since CSI-u sequences contain \\x1b which would be rejected.\n\t\tconst kittyPrintable = decodeKittyPrintable(data);\n\t\tif (kittyPrintable !== undefined) {\n\t\t\tthis.insertCharacter(kittyPrintable);\n\t\t\treturn;\n\t\t}\n\n\t\t// Regular character input - accept printable characters including Unicode,\n\t\t// but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)\n\t\tconst hasControlChars = [...data].some((ch) => {\n\t\t\tconst code = ch.charCodeAt(0);\n\t\t\treturn code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);\n\t\t});\n\t\tif (!hasControlChars) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate insertCharacter(char: string): void {\n\t\tif (isWhitespaceChar(char) || this.lastAction !== \"type-word\") {\n\t\t\tthis.pushUndo();\n\t\t}\n\t\tthis.lastAction = \"type-word\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);\n\t\tthis.cursor += char.length;\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor > 0) {\n\t\t\tthis.pushUndo();\n\t\t\tconst beforeCursor = this.value.slice(0, this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(beforeCursor)];\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1];\n\t\t\tconst graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor - graphemeLength) + this.value.slice(this.cursor);\n\t\t\tthis.cursor -= graphemeLength;\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tthis.lastAction = null;\n\t\tif (this.cursor < this.value.length) {\n\t\t\tthis.pushUndo();\n\t\t\tconst afterCursor = this.value.slice(this.cursor);\n\t\t\tconst graphemes = [...segmenter.segment(afterCursor)];\n\t\t\tconst firstGrapheme = graphemes[0];\n\t\t\tconst graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;\n\t\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + graphemeLength);\n\t\t}\n\t}\n\n\tprivate deleteToLineStart(): void {\n\t\tif (this.cursor === 0) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(0, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(this.cursor);\n\t\tthis.cursor = 0;\n\t}\n\n\tprivate deleteToLineEnd(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\t\tthis.pushUndo();\n\t\tconst deletedText = this.value.slice(this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: this.lastAction === \"kill\" });\n\t\tthis.lastAction = \"kill\";\n\t\tthis.value = this.value.slice(0, this.cursor);\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tif (this.cursor === 0) return;\n\n\t\t// Save lastAction before cursor movement (moveWordBackwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordBackwards();\n\t\tconst deleteFrom = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(deleteFrom, this.cursor);\n\t\tthis.killRing.push(deletedText, { prepend: true, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, deleteFrom) + this.value.slice(this.cursor);\n\t\tthis.cursor = deleteFrom;\n\t}\n\n\tprivate deleteWordForward(): void {\n\t\tif (this.cursor >= this.value.length) return;\n\n\t\t// Save lastAction before cursor movement (moveWordForwards resets it)\n\t\tconst wasKill = this.lastAction === \"kill\";\n\n\t\tthis.pushUndo();\n\n\t\tconst oldCursor = this.cursor;\n\t\tthis.moveWordForwards();\n\t\tconst deleteTo = this.cursor;\n\t\tthis.cursor = oldCursor;\n\n\t\tconst deletedText = this.value.slice(this.cursor, deleteTo);\n\t\tthis.killRing.push(deletedText, { prepend: false, accumulate: wasKill });\n\t\tthis.lastAction = \"kill\";\n\n\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(deleteTo);\n\t}\n\n\tprivate yank(): void {\n\t\tconst text = this.killRing.peek();\n\t\tif (!text) return;\n\n\t\tthis.pushUndo();\n\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate yankPop(): void {\n\t\tif (this.lastAction !== \"yank\" || this.killRing.length <= 1) return;\n\n\t\tthis.pushUndo();\n\n\t\tconst prevText = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor - prevText.length) + this.value.slice(this.cursor);\n\t\tthis.cursor -= prevText.length;\n\n\t\tthis.killRing.rotate();\n\t\tconst text = this.killRing.peek() || \"\";\n\t\tthis.value = this.value.slice(0, this.cursor) + text + this.value.slice(this.cursor);\n\t\tthis.cursor += text.length;\n\t\tthis.lastAction = \"yank\";\n\t}\n\n\tprivate pushUndo(): void {\n\t\tthis.undoStack.push({ value: this.value, cursor: this.cursor });\n\t}\n\n\tprivate undo(): void {\n\t\tconst snapshot = this.undoStack.pop();\n\t\tif (!snapshot) return;\n\t\tthis.value = snapshot.value;\n\t\tthis.cursor = snapshot.cursor;\n\t\tthis.lastAction = null;\n\t}\n\n\tprivate moveWordBackwards(): void {\n\t\tif (this.cursor === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textBeforeCursor = this.value.slice(0, this.cursor);\n\t\tconst graphemes = [...segmenter.segment(textBeforeCursor)];\n\n\t\twhile (graphemes.length > 0 && isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t}\n\n\t\tif (graphemes.length > 0) {\n\t\t\tconst lastGrapheme = graphemes[graphemes.length - 1]?.segment || \"\";\n\t\t\tif (isPunctuationChar(lastGrapheme)) {\n\t\t\t\twhile (graphemes.length > 0 && isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile (\n\t\t\t\t\tgraphemes.length > 0 &&\n\t\t\t\t\t!isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || \"\") &&\n\t\t\t\t\t!isPunctuationChar(graphemes[graphemes.length - 1]?.segment || \"\")\n\t\t\t\t) {\n\t\t\t\t\tthis.cursor -= graphemes.pop()?.segment.length || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate moveWordForwards(): void {\n\t\tif (this.cursor >= this.value.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.lastAction = null;\n\t\tconst textAfterCursor = this.value.slice(this.cursor);\n\t\tconst segments = segmenter.segment(textAfterCursor);\n\t\tconst iterator = segments[Symbol.iterator]();\n\t\tlet next = iterator.next();\n\n\t\twhile (!next.done && isWhitespaceChar(next.value.segment)) {\n\t\t\tthis.cursor += next.value.segment.length;\n\t\t\tnext = iterator.next();\n\t\t}\n\n\t\tif (!next.done) {\n\t\t\tconst firstGrapheme = next.value.segment;\n\t\t\tif (isPunctuationChar(firstGrapheme)) {\n\t\t\t\twhile (!next.done && isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile (!next.done && !isWhitespaceChar(next.value.segment) && !isPunctuationChar(next.value.segment)) {\n\t\t\t\t\tthis.cursor += next.value.segment.length;\n\t\t\t\t\tnext = iterator.next();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\tthis.lastAction = null;\n\t\tthis.pushUndo();\n\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\tthis.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);\n\t\tthis.cursor += cleanText.length;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\treturn [prompt];\n\t\t}\n\n\t\tlet visibleText = \"\";\n\t\tlet cursorDisplay = this.cursor;\n\t\tconst totalWidth = visibleWidth(this.value);\n\n\t\tif (totalWidth < availableWidth) {\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\tconst scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;\n\t\t\tconst cursorCol = visibleWidth(this.value.slice(0, this.cursor));\n\n\t\t\tif (scrollWidth > 0) {\n\t\t\t\tconst halfWidth = Math.floor(scrollWidth / 2);\n\t\t\t\tlet startCol = 0;\n\n\t\t\t\tif (cursorCol < halfWidth) {\n\t\t\t\t\tstartCol = 0;\n\t\t\t\t} else if (cursorCol > totalWidth - halfWidth) {\n\t\t\t\t\tstartCol = Math.max(0, totalWidth - scrollWidth);\n\t\t\t\t} else {\n\t\t\t\t\tstartCol = Math.max(0, cursorCol - halfWidth);\n\t\t\t\t}\n\n\t\t\t\tvisibleText = sliceByColumn(this.value, startCol, scrollWidth, true);\n\t\t\t\tconst beforeCursor = sliceByColumn(this.value, startCol, Math.max(0, cursorCol - startCol), true);\n\t\t\t\tcursorDisplay = beforeCursor.length;\n\t\t\t} else {\n\t\t\t\tvisibleText = \"\";\n\t\t\t\tcursorDisplay = 0;\n\t\t\t}\n\t\t}\n\n\t\tconst graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];\n\t\tconst cursorGrapheme = graphemes[0];\n\n\t\tconst beforeCursor = visibleText.slice(0, cursorDisplay);\n\t\tconst atCursor = cursorGrapheme?.segment ?? \" \"; // Character at cursor, or space if at end\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + atCursor.length);\n\n\t\t// Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)\n\t\tconst marker = this.focused ? CURSOR_MARKER : \"\";\n\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal\n\t\tconst textWithCursor = beforeCursor + marker + cursorChar + afterCursor;\n\n\t\tconst visualLength = visibleWidth(textWithCursor);\n\t\tconst padding = \" \".repeat(Math.max(0, availableWidth - visualLength));\n\t\tconst line = prompt + textWithCursor + padding;\n\n\t\treturn [line];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TUI } from "../tui.js";
|
|
2
|
+
import { Text } from "./text.js";
|
|
3
|
+
export interface LoaderIndicatorOptions {
|
|
4
|
+
frames?: string[];
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Loader component that updates with an optional spinning animation.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Loader extends Text {
|
|
11
|
+
private spinnerColorFn;
|
|
12
|
+
private messageColorFn;
|
|
13
|
+
private message;
|
|
14
|
+
private frames;
|
|
15
|
+
private intervalMs;
|
|
16
|
+
private currentFrame;
|
|
17
|
+
private intervalId;
|
|
18
|
+
private ui;
|
|
19
|
+
private renderIndicatorVerbatim;
|
|
20
|
+
constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string, indicator?: LoaderIndicatorOptions);
|
|
21
|
+
render(width: number): string[];
|
|
22
|
+
start(): void;
|
|
23
|
+
stop(): void;
|
|
24
|
+
setMessage(message: string): void;
|
|
25
|
+
setIndicator(indicator?: LoaderIndicatorOptions): void;
|
|
26
|
+
private restartAnimation;
|
|
27
|
+
private updateDisplay;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,sBAAsB;IACtC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;GAEG;AACH,qBAAa,MAAO,SAAQ,IAAI;IAU9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,OAAO;IAXhB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,uBAAuB,CAAS;IAExC,YACC,EAAE,EAAE,GAAG,EACC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EACvC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EACvC,OAAO,GAAE,MAAqB,EACtC,SAAS,CAAC,EAAE,sBAAsB,EAKlC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,IAAI,IAAI,IAAI,CAKX;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhC;IAED,YAAY,CAAC,SAAS,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAMrD;IAED,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,aAAa;CASrB","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\nexport interface LoaderIndicatorOptions {\n\tframes?: string[];\n\tintervalMs?: number;\n}\n\nconst DEFAULT_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst DEFAULT_INTERVAL_MS = 80;\n\n/**\n * Loader component that updates with an optional spinning animation.\n */\nexport class Loader extends Text {\n\tprivate frames = [...DEFAULT_FRAMES];\n\tprivate intervalMs = DEFAULT_INTERVAL_MS;\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\tprivate renderIndicatorVerbatim = false;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate messageColorFn: (str: string) => string,\n\t\tprivate message: string = \"Loading...\",\n\t\tindicator?: LoaderIndicatorOptions,\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.setIndicator(indicator);\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart(): void {\n\t\tthis.updateDisplay();\n\t\tthis.restartAnimation();\n\t}\n\n\tstop(): void {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tsetMessage(message: string): void {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetIndicator(indicator?: LoaderIndicatorOptions): void {\n\t\tthis.renderIndicatorVerbatim = indicator !== undefined;\n\t\tthis.frames = indicator?.frames !== undefined ? [...indicator.frames] : [...DEFAULT_FRAMES];\n\t\tthis.intervalMs = indicator?.intervalMs && indicator.intervalMs > 0 ? indicator.intervalMs : DEFAULT_INTERVAL_MS;\n\t\tthis.currentFrame = 0;\n\t\tthis.start();\n\t}\n\n\tprivate restartAnimation(): void {\n\t\tthis.stop();\n\t\tif (this.frames.length <= 1) {\n\t\t\treturn;\n\t\t}\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, this.intervalMs);\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst frame = this.frames[this.currentFrame] ?? \"\";\n\t\tconst renderedFrame = this.renderIndicatorVerbatim ? frame : this.spinnerColorFn(frame);\n\t\tconst indicator = frame.length > 0 ? `${renderedFrame} ` : \"\";\n\t\tthis.setText(`${indicator}${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Text } from "./text.js";
|
|
2
|
+
const DEFAULT_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
3
|
+
const DEFAULT_INTERVAL_MS = 80;
|
|
4
|
+
/**
|
|
5
|
+
* Loader component that updates with an optional spinning animation.
|
|
6
|
+
*/
|
|
7
|
+
export class Loader extends Text {
|
|
8
|
+
spinnerColorFn;
|
|
9
|
+
messageColorFn;
|
|
10
|
+
message;
|
|
11
|
+
frames = [...DEFAULT_FRAMES];
|
|
12
|
+
intervalMs = DEFAULT_INTERVAL_MS;
|
|
13
|
+
currentFrame = 0;
|
|
14
|
+
intervalId = null;
|
|
15
|
+
ui = null;
|
|
16
|
+
renderIndicatorVerbatim = false;
|
|
17
|
+
constructor(ui, spinnerColorFn, messageColorFn, message = "Loading...", indicator) {
|
|
18
|
+
super("", 1, 0);
|
|
19
|
+
this.spinnerColorFn = spinnerColorFn;
|
|
20
|
+
this.messageColorFn = messageColorFn;
|
|
21
|
+
this.message = message;
|
|
22
|
+
this.ui = ui;
|
|
23
|
+
this.setIndicator(indicator);
|
|
24
|
+
}
|
|
25
|
+
render(width) {
|
|
26
|
+
return ["", ...super.render(width)];
|
|
27
|
+
}
|
|
28
|
+
start() {
|
|
29
|
+
this.updateDisplay();
|
|
30
|
+
this.restartAnimation();
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
if (this.intervalId) {
|
|
34
|
+
clearInterval(this.intervalId);
|
|
35
|
+
this.intervalId = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
setMessage(message) {
|
|
39
|
+
this.message = message;
|
|
40
|
+
this.updateDisplay();
|
|
41
|
+
}
|
|
42
|
+
setIndicator(indicator) {
|
|
43
|
+
this.renderIndicatorVerbatim = indicator !== undefined;
|
|
44
|
+
this.frames = indicator?.frames !== undefined ? [...indicator.frames] : [...DEFAULT_FRAMES];
|
|
45
|
+
this.intervalMs = indicator?.intervalMs && indicator.intervalMs > 0 ? indicator.intervalMs : DEFAULT_INTERVAL_MS;
|
|
46
|
+
this.currentFrame = 0;
|
|
47
|
+
this.start();
|
|
48
|
+
}
|
|
49
|
+
restartAnimation() {
|
|
50
|
+
this.stop();
|
|
51
|
+
if (this.frames.length <= 1) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.intervalId = setInterval(() => {
|
|
55
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
56
|
+
this.updateDisplay();
|
|
57
|
+
}, this.intervalMs);
|
|
58
|
+
}
|
|
59
|
+
updateDisplay() {
|
|
60
|
+
const frame = this.frames[this.currentFrame] ?? "";
|
|
61
|
+
const renderedFrame = this.renderIndicatorVerbatim ? frame : this.spinnerColorFn(frame);
|
|
62
|
+
const indicator = frame.length > 0 ? `${renderedFrame} ` : "";
|
|
63
|
+
this.setText(`${indicator}${this.messageColorFn(this.message)}`);
|
|
64
|
+
if (this.ui) {
|
|
65
|
+
this.ui.requestRender();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,MAAM,cAAc,GAAG,CAAC,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,CAAC,CAAC;AAC1E,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;GAEG;AACH,MAAM,OAAO,MAAO,SAAQ,IAAI;IAUtB,cAAc;IACd,cAAc;IACd,OAAO;IAXR,MAAM,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC;IAC7B,UAAU,GAAG,mBAAmB,CAAC;IACjC,YAAY,GAAG,CAAC,CAAC;IACjB,UAAU,GAA0B,IAAI,CAAC;IACzC,EAAE,GAAe,IAAI,CAAC;IACtB,uBAAuB,GAAG,KAAK,CAAC;IAExC,YACC,EAAO,EACC,cAAuC,EACvC,cAAuC,EACvC,OAAO,GAAW,YAAY,EACtC,SAAkC,EACjC;QACD,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;8BALR,cAAc;8BACd,cAAc;uBACd,OAAO;QAIf,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAAA,CAC7B;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAAA,CACpC;IAED,KAAK,GAAS;QACb,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAAA,CACxB;IAED,IAAI,GAAS;QACZ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACxB,CAAC;IAAA,CACD;IAED,UAAU,CAAC,OAAe,EAAQ;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,YAAY,CAAC,SAAkC,EAAQ;QACtD,IAAI,CAAC,uBAAuB,GAAG,SAAS,KAAK,SAAS,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC;QAC5F,IAAI,CAAC,UAAU,GAAG,SAAS,EAAE,UAAU,IAAI,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACjH,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,EAAE,CAAC;IAAA,CACb;IAEO,gBAAgB,GAAS;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YACnC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YACjE,IAAI,CAAC,aAAa,EAAE,CAAC;QAAA,CACrB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAAA,CACpB;IAEO,aAAa,GAAS;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACnD,MAAM,aAAa,GAAG,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACxF,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\nexport interface LoaderIndicatorOptions {\n\tframes?: string[];\n\tintervalMs?: number;\n}\n\nconst DEFAULT_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst DEFAULT_INTERVAL_MS = 80;\n\n/**\n * Loader component that updates with an optional spinning animation.\n */\nexport class Loader extends Text {\n\tprivate frames = [...DEFAULT_FRAMES];\n\tprivate intervalMs = DEFAULT_INTERVAL_MS;\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\tprivate renderIndicatorVerbatim = false;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate messageColorFn: (str: string) => string,\n\t\tprivate message: string = \"Loading...\",\n\t\tindicator?: LoaderIndicatorOptions,\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.setIndicator(indicator);\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart(): void {\n\t\tthis.updateDisplay();\n\t\tthis.restartAnimation();\n\t}\n\n\tstop(): void {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tsetMessage(message: string): void {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetIndicator(indicator?: LoaderIndicatorOptions): void {\n\t\tthis.renderIndicatorVerbatim = indicator !== undefined;\n\t\tthis.frames = indicator?.frames !== undefined ? [...indicator.frames] : [...DEFAULT_FRAMES];\n\t\tthis.intervalMs = indicator?.intervalMs && indicator.intervalMs > 0 ? indicator.intervalMs : DEFAULT_INTERVAL_MS;\n\t\tthis.currentFrame = 0;\n\t\tthis.start();\n\t}\n\n\tprivate restartAnimation(): void {\n\t\tthis.stop();\n\t\tif (this.frames.length <= 1) {\n\t\t\treturn;\n\t\t}\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, this.intervalMs);\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst frame = this.frames[this.currentFrame] ?? \"\";\n\t\tconst renderedFrame = this.renderIndicatorVerbatim ? frame : this.spinnerColorFn(frame);\n\t\tconst indicator = frame.length > 0 ? `${renderedFrame} ` : \"\";\n\t\tthis.setText(`${indicator}${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
|