@apholdings/jensen-tui 0.0.1 → 0.0.3

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.
@@ -7,6 +7,7 @@ export declare class Input implements Component, Focusable {
7
7
  private cursor;
8
8
  onSubmit?: (value: string) => void;
9
9
  onEscape?: () => void;
10
+ promptBg?: (text: string) => string;
10
11
  /** Focusable interface - set by TUI when focus changes */
11
12
  focused: boolean;
12
13
  private pasteBuffer;
@@ -1 +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,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAS;IAGzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAGnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAA8C;IAGhE,OAAO,CAAC,SAAS,CAA+B;IAEhD,QAAQ,IAAI,MAAM,CAEjB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmK9B;IAED,OAAO,CAAC,eAAe;IAWvB,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;IAkBf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAYnB,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoE9B;CACD","sourcesContent":["import { getEditorKeybindings } 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\t/** Focusable interface - set by TUI when focus changes */\n\tfocused: boolean = false;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\t// Kill ring for Emacs-style kill/yank operations\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\t// Undo support\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\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\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\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\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\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\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\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 = getEditorKeybindings();\n\n\t\t// Escape/Cancel\n\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Undo\n\t\tif (kb.matches(data, \"undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\t// Submit\n\t\tif (kb.matches(data, \"submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\t// Deletion\n\t\tif (kb.matches(data, \"deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kill ring actions\n\t\tif (kb.matches(data, \"yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\t// Cursor movement\n\t\tif (kb.matches(data, \"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, \"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, \"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, \"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, \"cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"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\t// Undo coalescing: consecutive word chars coalesce into one undo unit\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\t// Delete the previously yanked text (still at end of ring before rotation)\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\t// Rotate and insert new entry\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\t// Skip trailing whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Skip leading whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\t// Insert at cursor position\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\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\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\t// Everything fits (leave room for cursor at end)\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\n\t\t\t// Reserve one column for cursor if it's at the end\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\t// Cursor near start\n\t\t\t\t\tstartCol = 0;\n\t\t\t\t} else if (cursorCol > totalWidth - halfWidth) {\n\t\t\t\t\t// Cursor near end\n\t\t\t\t\tstartCol = Math.max(0, totalWidth - scrollWidth);\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor in middle\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\t// Build line with fake cursor\n\t\t// Insert cursor character at cursor position\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\t// Use inverse video to show cursor\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\t// Calculate visual width\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"]}
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;AAkB1E;;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;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAE3C,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAS;IAGzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAGnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAA8C;IAGhE,OAAO,CAAC,SAAS,CAA+B;IAEhD,QAAQ,IAAI,MAAM,CAEjB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmK9B;IAED,OAAO,CAAC,eAAe;IAWvB,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;IAkBf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAYnB,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiE9B;CACD","sourcesContent":["import { getEditorKeybindings } 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 {\n\tapplyBackgroundToLine,\n\tgetSegmenter,\n\tisPunctuationChar,\n\tisWhitespaceChar,\n\tsliceByColumn,\n\tvisibleWidth,\n} 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\tpublic promptBg?: (text: string) => string;\n\n\t/** Focusable interface - set by TUI when focus changes */\n\tfocused: boolean = false;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\t// Kill ring for Emacs-style kill/yank operations\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\t// Undo support\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\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\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\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\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\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\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\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 = getEditorKeybindings();\n\n\t\t// Escape/Cancel\n\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Undo\n\t\tif (kb.matches(data, \"undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\t// Submit\n\t\tif (kb.matches(data, \"submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\t// Deletion\n\t\tif (kb.matches(data, \"deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kill ring actions\n\t\tif (kb.matches(data, \"yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\t// Cursor movement\n\t\tif (kb.matches(data, \"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, \"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, \"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, \"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, \"cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"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\t// Undo coalescing: consecutive word chars coalesce into one undo unit\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\t// Delete the previously yanked text (still at end of ring before rotation)\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\t// Rotate and insert new entry\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\t// Skip trailing whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Skip leading whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\t// Insert at cursor position\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\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\tconst raw = prompt;\n\t\t\tconst line = this.promptBg ? applyBackgroundToLine(raw, width, this.promptBg) : raw;\n\t\t\treturn [line];\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\t// Everything fits\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\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\t// Build line with fake cursor\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 ?? \" \";\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + atCursor.length);\n\n\t\t// Hardware cursor marker for IME positioning\n\t\tconst marker = this.focused ? CURSOR_MARKER : \"\";\n\n\t\t// Fake cursor\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`;\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 rawLine = prompt + textWithCursor + padding;\n\n\t\tconst line = this.promptBg ? applyBackgroundToLine(rawLine, width, this.promptBg) : rawLine;\n\t\treturn [line];\n\t}\n}\n"]}
@@ -3,7 +3,7 @@ import { decodeKittyPrintable } from "../keys.js";
3
3
  import { KillRing } from "../kill-ring.js";
4
4
  import { CURSOR_MARKER } from "../tui.js";
5
5
  import { UndoStack } from "../undo-stack.js";
6
- import { getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from "../utils.js";
6
+ import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth, } from "../utils.js";
7
7
  const segmenter = getSegmenter();
8
8
  /**
9
9
  * Input component - single-line text input with horizontal scrolling
@@ -13,6 +13,7 @@ export class Input {
13
13
  cursor = 0; // Cursor position in the value
14
14
  onSubmit;
15
15
  onEscape;
16
+ promptBg;
16
17
  /** Focusable interface - set by TUI when focus changes */
17
18
  focused = false;
18
19
  // Bracketed paste mode buffering
@@ -366,33 +367,31 @@ export class Input {
366
367
  const prompt = "> ";
367
368
  const availableWidth = width - prompt.length;
368
369
  if (availableWidth <= 0) {
369
- return [prompt];
370
+ const raw = prompt;
371
+ const line = this.promptBg ? applyBackgroundToLine(raw, width, this.promptBg) : raw;
372
+ return [line];
370
373
  }
371
374
  let visibleText = "";
372
375
  let cursorDisplay = this.cursor;
373
376
  const totalWidth = visibleWidth(this.value);
374
377
  if (totalWidth < availableWidth) {
375
- // Everything fits (leave room for cursor at end)
378
+ // Everything fits
376
379
  visibleText = this.value;
377
380
  }
378
381
  else {
379
382
  // Need horizontal scrolling
380
- // Reserve one column for cursor if it's at the end
381
383
  const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
382
384
  const cursorCol = visibleWidth(this.value.slice(0, this.cursor));
383
385
  if (scrollWidth > 0) {
384
386
  const halfWidth = Math.floor(scrollWidth / 2);
385
387
  let startCol = 0;
386
388
  if (cursorCol < halfWidth) {
387
- // Cursor near start
388
389
  startCol = 0;
389
390
  }
390
391
  else if (cursorCol > totalWidth - halfWidth) {
391
- // Cursor near end
392
392
  startCol = Math.max(0, totalWidth - scrollWidth);
393
393
  }
394
394
  else {
395
- // Cursor in middle
396
395
  startCol = Math.max(0, cursorCol - halfWidth);
397
396
  }
398
397
  visibleText = sliceByColumn(this.value, startCol, scrollWidth, true);
@@ -405,21 +404,20 @@ export class Input {
405
404
  }
406
405
  }
407
406
  // Build line with fake cursor
408
- // Insert cursor character at cursor position
409
407
  const graphemes = [...segmenter.segment(visibleText.slice(cursorDisplay))];
410
408
  const cursorGrapheme = graphemes[0];
411
409
  const beforeCursor = visibleText.slice(0, cursorDisplay);
412
- const atCursor = cursorGrapheme?.segment ?? " "; // Character at cursor, or space if at end
410
+ const atCursor = cursorGrapheme?.segment ?? " ";
413
411
  const afterCursor = visibleText.slice(cursorDisplay + atCursor.length);
414
- // Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)
412
+ // Hardware cursor marker for IME positioning
415
413
  const marker = this.focused ? CURSOR_MARKER : "";
416
- // Use inverse video to show cursor
417
- const cursorChar = `\x1b[7m${atCursor}\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal
414
+ // Fake cursor
415
+ const cursorChar = `\x1b[7m${atCursor}\x1b[27m`;
418
416
  const textWithCursor = beforeCursor + marker + cursorChar + afterCursor;
419
- // Calculate visual width
420
417
  const visualLength = visibleWidth(textWithCursor);
421
418
  const padding = " ".repeat(Math.max(0, availableWidth - visualLength));
422
- const line = prompt + textWithCursor + padding;
419
+ const rawLine = prompt + textWithCursor + padding;
420
+ const line = this.promptBg ? applyBackgroundToLine(rawLine, width, this.promptBg) : rawLine;
423
421
  return [line];
424
422
  }
425
423
  }
@@ -1 +1 @@
1
- {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"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","sourcesContent":["import { getEditorKeybindings } 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\t/** Focusable interface - set by TUI when focus changes */\n\tfocused: boolean = false;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\t// Kill ring for Emacs-style kill/yank operations\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\t// Undo support\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\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\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\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\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\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\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\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 = getEditorKeybindings();\n\n\t\t// Escape/Cancel\n\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Undo\n\t\tif (kb.matches(data, \"undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\t// Submit\n\t\tif (kb.matches(data, \"submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\t// Deletion\n\t\tif (kb.matches(data, \"deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kill ring actions\n\t\tif (kb.matches(data, \"yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\t// Cursor movement\n\t\tif (kb.matches(data, \"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, \"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, \"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, \"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, \"cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"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\t// Undo coalescing: consecutive word chars coalesce into one undo unit\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\t// Delete the previously yanked text (still at end of ring before rotation)\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\t// Rotate and insert new entry\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\t// Skip trailing whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Skip leading whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\t// Insert at cursor position\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\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\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\t// Everything fits (leave room for cursor at end)\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\n\t\t\t// Reserve one column for cursor if it's at the end\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\t// Cursor near start\n\t\t\t\t\tstartCol = 0;\n\t\t\t\t} else if (cursorCol > totalWidth - halfWidth) {\n\t\t\t\t\t// Cursor near end\n\t\t\t\t\tstartCol = Math.max(0, totalWidth - scrollWidth);\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor in middle\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\t// Build line with fake cursor\n\t\t// Insert cursor character at cursor position\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\t// Use inverse video to show cursor\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\t// Calculate visual width\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"]}
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"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","sourcesContent":["import { getEditorKeybindings } 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 {\n\tapplyBackgroundToLine,\n\tgetSegmenter,\n\tisPunctuationChar,\n\tisWhitespaceChar,\n\tsliceByColumn,\n\tvisibleWidth,\n} 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\tpublic promptBg?: (text: string) => string;\n\n\t/** Focusable interface - set by TUI when focus changes */\n\tfocused: boolean = false;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\t// Kill ring for Emacs-style kill/yank operations\n\tprivate killRing = new KillRing();\n\tprivate lastAction: \"kill\" | \"yank\" | \"type-word\" | null = null;\n\n\t// Undo support\n\tprivate undoStack = new UndoStack<InputState>();\n\n\tgetValue(): string {\n\t\treturn this.value;\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\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\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\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\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\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\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 = getEditorKeybindings();\n\n\t\t// Escape/Cancel\n\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\tif (this.onEscape) this.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Undo\n\t\tif (kb.matches(data, \"undo\")) {\n\t\t\tthis.undo();\n\t\t\treturn;\n\t\t}\n\n\t\t// Submit\n\t\tif (kb.matches(data, \"submit\") || data === \"\\n\") {\n\t\t\tif (this.onSubmit) this.onSubmit(this.value);\n\t\t\treturn;\n\t\t}\n\n\t\t// Deletion\n\t\tif (kb.matches(data, \"deleteCharBackward\")) {\n\t\t\tthis.handleBackspace();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteCharForward\")) {\n\t\t\tthis.handleForwardDelete();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordBackward\")) {\n\t\t\tthis.deleteWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteWordForward\")) {\n\t\t\tthis.deleteWordForward();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineStart\")) {\n\t\t\tthis.deleteToLineStart();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"deleteToLineEnd\")) {\n\t\t\tthis.deleteToLineEnd();\n\t\t\treturn;\n\t\t}\n\n\t\t// Kill ring actions\n\t\tif (kb.matches(data, \"yank\")) {\n\t\t\tthis.yank();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"yankPop\")) {\n\t\t\tthis.yankPop();\n\t\t\treturn;\n\t\t}\n\n\t\t// Cursor movement\n\t\tif (kb.matches(data, \"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, \"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, \"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, \"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, \"cursorWordLeft\")) {\n\t\t\tthis.moveWordBackwards();\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"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\t// Undo coalescing: consecutive word chars coalesce into one undo unit\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\t// Delete the previously yanked text (still at end of ring before rotation)\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\t// Rotate and insert new entry\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\t// Skip trailing whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Skip leading whitespace\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\t// Skip punctuation run\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\t// Skip word run\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\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\").replace(/\\t/g, \" \");\n\n\t\t// Insert at cursor position\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\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\tconst raw = prompt;\n\t\t\tconst line = this.promptBg ? applyBackgroundToLine(raw, width, this.promptBg) : raw;\n\t\t\treturn [line];\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\t// Everything fits\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\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\t// Build line with fake cursor\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 ?? \" \";\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + atCursor.length);\n\n\t\t// Hardware cursor marker for IME positioning\n\t\tconst marker = this.focused ? CURSOR_MARKER : \"\";\n\n\t\t// Fake cursor\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`;\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 rawLine = prompt + textWithCursor + padding;\n\n\t\tconst line = this.promptBg ? applyBackgroundToLine(rawLine, width, this.promptBg) : rawLine;\n\t\treturn [line];\n\t}\n}\n"]}
@@ -1,21 +1,32 @@
1
1
  import type { TUI } from "../tui.js";
2
2
  import { Text } from "./text.js";
3
+ export type LoaderState = "idle" | "running" | "stopped" | "disposed";
3
4
  /**
4
- * Loader component that updates every 80ms with spinning animation
5
+ * Loader component that updates with a moonwalking stickman animation.
6
+ * Kept as a single-line loader so it still fits the current status row layout.
5
7
  */
6
8
  export declare class Loader extends Text {
7
9
  private spinnerColorFn;
8
10
  private messageColorFn;
9
11
  private message;
10
- private frames;
12
+ private readonly frames;
13
+ private readonly frameWidth;
14
+ private readonly frameIntervalMs;
11
15
  private currentFrame;
12
16
  private intervalId;
13
17
  private ui;
18
+ private state;
19
+ private epoch;
20
+ private startTime;
21
+ private elapsedAtStop;
14
22
  constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string);
15
23
  render(width: number): string[];
16
24
  start(): void;
17
25
  stop(): void;
26
+ dispose(): void;
18
27
  setMessage(message: string): void;
28
+ private cleanupInterval;
29
+ private padFrame;
19
30
  private updateDisplay;
20
31
  }
21
32
  //# sourceMappingURL=loader.d.ts.map
@@ -1 +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;;GAEG;AACH,qBAAa,MAAO,SAAQ,IAAI;IAQ9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,OAAO;IAThB,OAAO,CAAC,MAAM,CAAsD;IACpE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,EAAE,CAAoB;IAE9B,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,EAKtC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;IAED,KAAK,SAMJ;IAED,IAAI,SAKH;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,QAGzB;IAED,OAAO,CAAC,aAAa;CAOrB","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\n/**\n * Loader component that updates every 80ms with spinning animation\n */\nexport class Loader extends Text {\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\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) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart() {\n\t\tthis.updateDisplay();\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\t}\n\n\tstop() {\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) {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tthis.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAEtE;;;GAGG;AACH,qBAAa,MAAO,SAAQ,IAAI;IAkB9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,OAAO;IAnBhB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAE9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgE;IAE3F,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAO;IAEvC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,EAAE,CAAoB;IAE9B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,aAAa,CAAK;IAE1B,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,EAKtC;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiDvC;IAED,KAAK,SAuBJ;IAED,IAAI,SAaH;IAED,OAAO,SAiBN;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,QAOzB;IAED,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,aAAa;CASrB","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { visibleWidth } from \"../utils.js\";\nimport { Text } from \"./text.js\";\n\nexport type LoaderState = \"idle\" | \"running\" | \"stopped\" | \"disposed\";\n\n/**\n * Loader component that updates with a moonwalking stickman animation.\n * Kept as a single-line loader so it still fits the current status row layout.\n */\nexport class Loader extends Text {\n\tprivate readonly frames = [\"▱▱▱\", \"▰▱▱\", \"▰▰▱\", \"▰▰▰\", \"▱▱▱\"];\n\n\tprivate readonly frameWidth = Math.max(...this.frames.map((frame) => visibleWidth(frame)));\n\n\tprivate readonly frameIntervalMs = 100;\n\n\tprivate currentFrame = 0;\n\tprivate intervalId: ReturnType<typeof setInterval> | null = null;\n\tprivate ui: TUI | null = null;\n\n\tprivate state: LoaderState = \"idle\";\n\tprivate epoch = 0;\n\tprivate startTime: number | null = null;\n\tprivate elapsedAtStop = 0;\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) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn [\"\"];\n\t\t}\n\n\t\tconst rawFrame = this.frames[this.currentFrame] ?? this.frames[0];\n\t\tconst paddedFrame = this.padFrame(rawFrame);\n\t\tconst frame = this.spinnerColorFn(paddedFrame);\n\t\tconst prefix = `${frame} ${this.messageColorFn(this.message)}`;\n\n\t\tlet elapsed = this.elapsedAtStop;\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\telapsed = performance.now() - this.startTime;\n\t\t}\n\n\t\tif (elapsed === 0 && this.state !== \"running\") {\n\t\t\tif (this.text !== prefix) {\n\t\t\t\tthis.setText(prefix);\n\t\t\t}\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst totalTenths = Math.floor(elapsed / 100);\n\t\tconst tenths = totalTenths % 10;\n\t\tconst totalSeconds = Math.floor(totalTenths / 10);\n\t\tconst seconds = totalSeconds % 60;\n\t\tconst minutes = Math.floor(totalSeconds / 60);\n\n\t\tconst minStr = minutes.toString().padStart(2, \"0\");\n\t\tconst secStr = seconds.toString().padStart(2, \"0\");\n\t\tconst timerStr = this.messageColorFn(`${minStr}:${secStr}.${tenths}`);\n\n\t\tconst contentWidth = Math.max(1, width - this.paddingX * 2);\n\t\tconst prefixWidth = visibleWidth(prefix);\n\t\tconst timerWidth = visibleWidth(timerStr);\n\n\t\tlet nextText: string;\n\t\tif (prefixWidth + timerWidth + 2 <= contentWidth) {\n\t\t\tconst padding = \" \".repeat(contentWidth - prefixWidth - timerWidth);\n\t\t\tnextText = prefix + padding + timerStr;\n\t\t} else {\n\t\t\tnextText = `${prefix} ${timerStr}`;\n\t\t}\n\n\t\tif (this.text !== nextText) {\n\t\t\tthis.setText(nextText);\n\t\t}\n\n\t\treturn super.render(width);\n\t}\n\n\tstart() {\n\t\tif (this.state === \"disposed\" || this.state === \"running\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.state = \"running\";\n\t\tthis.epoch++;\n\t\tthis.currentFrame = 0;\n\t\tthis.startTime = performance.now();\n\t\tthis.elapsedAtStop = 0;\n\n\t\tthis.updateDisplay();\n\n\t\tconst currentEpoch = this.epoch;\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tif (this.epoch !== currentEpoch || this.state !== \"running\") {\n\t\t\t\tthis.cleanupInterval();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, this.frameIntervalMs);\n\t}\n\n\tstop() {\n\t\tif (this.state === \"disposed\" || this.state === \"stopped\" || this.state === \"idle\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\tthis.elapsedAtStop = performance.now() - this.startTime;\n\t\t}\n\n\t\tthis.state = \"stopped\";\n\t\tthis.epoch++;\n\t\tthis.cleanupInterval();\n\t\tthis.updateDisplay();\n\t}\n\n\tdispose() {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\tthis.elapsedAtStop = performance.now() - this.startTime;\n\t\t}\n\n\t\tthis.state = \"disposed\";\n\t\tthis.epoch++;\n\t\tthis.cleanupInterval();\n\t\tthis.ui = null;\n\t\tthis.cachedLines = undefined;\n\t\tthis.cachedText = undefined;\n\t\tthis.cachedWidth = undefined;\n\t\tthis.text = \"\";\n\t}\n\n\tsetMessage(message: string) {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate cleanupInterval() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tprivate padFrame(frame: string): string {\n\t\tconst padding = Math.max(0, this.frameWidth - visibleWidth(frame));\n\t\treturn frame + \" \".repeat(padding);\n\t}\n\n\tprivate updateDisplay() {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
@@ -1,15 +1,23 @@
1
+ import { visibleWidth } from "../utils.js";
1
2
  import { Text } from "./text.js";
2
3
  /**
3
- * Loader component that updates every 80ms with spinning animation
4
+ * Loader component that updates with a moonwalking stickman animation.
5
+ * Kept as a single-line loader so it still fits the current status row layout.
4
6
  */
5
7
  export class Loader extends Text {
6
8
  spinnerColorFn;
7
9
  messageColorFn;
8
10
  message;
9
- frames = ["", "", "", "", "", "⠴", "⠦", "⠧", "⠇", "⠏"];
11
+ frames = ["▱▱▱", "▰▱▱", "▰▰▱", "▰▰▰", "▱▱▱"];
12
+ frameWidth = Math.max(...this.frames.map((frame) => visibleWidth(frame)));
13
+ frameIntervalMs = 100;
10
14
  currentFrame = 0;
11
15
  intervalId = null;
12
16
  ui = null;
17
+ state = "idle";
18
+ epoch = 0;
19
+ startTime = null;
20
+ elapsedAtStop = 0;
13
21
  constructor(ui, spinnerColorFn, messageColorFn, message = "Loading...") {
14
22
  super("", 1, 0);
15
23
  this.spinnerColorFn = spinnerColorFn;
@@ -19,28 +27,116 @@ export class Loader extends Text {
19
27
  this.start();
20
28
  }
21
29
  render(width) {
22
- return ["", ...super.render(width)];
30
+ if (this.state === "disposed") {
31
+ return [""];
32
+ }
33
+ const rawFrame = this.frames[this.currentFrame] ?? this.frames[0];
34
+ const paddedFrame = this.padFrame(rawFrame);
35
+ const frame = this.spinnerColorFn(paddedFrame);
36
+ const prefix = `${frame} ${this.messageColorFn(this.message)}`;
37
+ let elapsed = this.elapsedAtStop;
38
+ if (this.state === "running" && this.startTime !== null) {
39
+ elapsed = performance.now() - this.startTime;
40
+ }
41
+ if (elapsed === 0 && this.state !== "running") {
42
+ if (this.text !== prefix) {
43
+ this.setText(prefix);
44
+ }
45
+ return super.render(width);
46
+ }
47
+ const totalTenths = Math.floor(elapsed / 100);
48
+ const tenths = totalTenths % 10;
49
+ const totalSeconds = Math.floor(totalTenths / 10);
50
+ const seconds = totalSeconds % 60;
51
+ const minutes = Math.floor(totalSeconds / 60);
52
+ const minStr = minutes.toString().padStart(2, "0");
53
+ const secStr = seconds.toString().padStart(2, "0");
54
+ const timerStr = this.messageColorFn(`${minStr}:${secStr}.${tenths}`);
55
+ const contentWidth = Math.max(1, width - this.paddingX * 2);
56
+ const prefixWidth = visibleWidth(prefix);
57
+ const timerWidth = visibleWidth(timerStr);
58
+ let nextText;
59
+ if (prefixWidth + timerWidth + 2 <= contentWidth) {
60
+ const padding = " ".repeat(contentWidth - prefixWidth - timerWidth);
61
+ nextText = prefix + padding + timerStr;
62
+ }
63
+ else {
64
+ nextText = `${prefix} ${timerStr}`;
65
+ }
66
+ if (this.text !== nextText) {
67
+ this.setText(nextText);
68
+ }
69
+ return super.render(width);
23
70
  }
24
71
  start() {
72
+ if (this.state === "disposed" || this.state === "running") {
73
+ return;
74
+ }
75
+ this.state = "running";
76
+ this.epoch++;
77
+ this.currentFrame = 0;
78
+ this.startTime = performance.now();
79
+ this.elapsedAtStop = 0;
25
80
  this.updateDisplay();
81
+ const currentEpoch = this.epoch;
26
82
  this.intervalId = setInterval(() => {
83
+ if (this.epoch !== currentEpoch || this.state !== "running") {
84
+ this.cleanupInterval();
85
+ return;
86
+ }
27
87
  this.currentFrame = (this.currentFrame + 1) % this.frames.length;
28
88
  this.updateDisplay();
29
- }, 80);
89
+ }, this.frameIntervalMs);
30
90
  }
31
91
  stop() {
32
- if (this.intervalId) {
33
- clearInterval(this.intervalId);
34
- this.intervalId = null;
92
+ if (this.state === "disposed" || this.state === "stopped" || this.state === "idle") {
93
+ return;
94
+ }
95
+ if (this.state === "running" && this.startTime !== null) {
96
+ this.elapsedAtStop = performance.now() - this.startTime;
35
97
  }
98
+ this.state = "stopped";
99
+ this.epoch++;
100
+ this.cleanupInterval();
101
+ this.updateDisplay();
102
+ }
103
+ dispose() {
104
+ if (this.state === "disposed") {
105
+ return;
106
+ }
107
+ if (this.state === "running" && this.startTime !== null) {
108
+ this.elapsedAtStop = performance.now() - this.startTime;
109
+ }
110
+ this.state = "disposed";
111
+ this.epoch++;
112
+ this.cleanupInterval();
113
+ this.ui = null;
114
+ this.cachedLines = undefined;
115
+ this.cachedText = undefined;
116
+ this.cachedWidth = undefined;
117
+ this.text = "";
36
118
  }
37
119
  setMessage(message) {
120
+ if (this.state === "disposed") {
121
+ return;
122
+ }
38
123
  this.message = message;
39
124
  this.updateDisplay();
40
125
  }
126
+ cleanupInterval() {
127
+ if (this.intervalId) {
128
+ clearInterval(this.intervalId);
129
+ this.intervalId = null;
130
+ }
131
+ }
132
+ padFrame(frame) {
133
+ const padding = Math.max(0, this.frameWidth - visibleWidth(frame));
134
+ return frame + " ".repeat(padding);
135
+ }
41
136
  updateDisplay() {
42
- const frame = this.frames[this.currentFrame];
43
- this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
137
+ if (this.state === "disposed") {
138
+ return;
139
+ }
44
140
  if (this.ui) {
45
141
  this.ui.requestRender();
46
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,MAAM,OAAO,MAAO,SAAQ,IAAI;IAQtB,cAAc;IACd,cAAc;IACd,OAAO;IATR,MAAM,GAAG,CAAC,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,CAAC,CAAC;IAC5D,YAAY,GAAG,CAAC,CAAC;IACjB,UAAU,GAA0B,IAAI,CAAC;IACzC,EAAE,GAAe,IAAI,CAAC;IAE9B,YACC,EAAO,EACC,cAAuC,EACvC,cAAuC,EACvC,OAAO,GAAW,YAAY,EACrC;QACD,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;8BAJR,cAAc;8BACd,cAAc;uBACd,OAAO;QAGf,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;IAAA,CACb;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAAA,CACpC;IAED,KAAK,GAAG;QACP,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,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,EAAE,CAAC,CAAC;IAAA,CACP;IAED,IAAI,GAAG;QACN,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,EAAE;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAG;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnF,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\n/**\n * Loader component that updates every 80ms with spinning animation\n */\nexport class Loader extends Text {\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\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) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart() {\n\t\tthis.updateDisplay();\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\t}\n\n\tstop() {\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) {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tthis.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { visibleWidth } from \"../utils.js\";\nimport { Text } from \"./text.js\";\n\nexport type LoaderState = \"idle\" | \"running\" | \"stopped\" | \"disposed\";\n\n/**\n * Loader component that updates with a moonwalking stickman animation.\n * Kept as a single-line loader so it still fits the current status row layout.\n */\nexport class Loader extends Text {\n\tprivate readonly frames = [\"▱▱▱\", \"▰▱▱\", \"▰▰▱\", \"▰▰▰\", \"▱▱▱\"];\n\n\tprivate readonly frameWidth = Math.max(...this.frames.map((frame) => visibleWidth(frame)));\n\n\tprivate readonly frameIntervalMs = 100;\n\n\tprivate currentFrame = 0;\n\tprivate intervalId: ReturnType<typeof setInterval> | null = null;\n\tprivate ui: TUI | null = null;\n\n\tprivate state: LoaderState = \"idle\";\n\tprivate epoch = 0;\n\tprivate startTime: number | null = null;\n\tprivate elapsedAtStop = 0;\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) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn [\"\"];\n\t\t}\n\n\t\tconst rawFrame = this.frames[this.currentFrame] ?? this.frames[0];\n\t\tconst paddedFrame = this.padFrame(rawFrame);\n\t\tconst frame = this.spinnerColorFn(paddedFrame);\n\t\tconst prefix = `${frame} ${this.messageColorFn(this.message)}`;\n\n\t\tlet elapsed = this.elapsedAtStop;\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\telapsed = performance.now() - this.startTime;\n\t\t}\n\n\t\tif (elapsed === 0 && this.state !== \"running\") {\n\t\t\tif (this.text !== prefix) {\n\t\t\t\tthis.setText(prefix);\n\t\t\t}\n\t\t\treturn super.render(width);\n\t\t}\n\n\t\tconst totalTenths = Math.floor(elapsed / 100);\n\t\tconst tenths = totalTenths % 10;\n\t\tconst totalSeconds = Math.floor(totalTenths / 10);\n\t\tconst seconds = totalSeconds % 60;\n\t\tconst minutes = Math.floor(totalSeconds / 60);\n\n\t\tconst minStr = minutes.toString().padStart(2, \"0\");\n\t\tconst secStr = seconds.toString().padStart(2, \"0\");\n\t\tconst timerStr = this.messageColorFn(`${minStr}:${secStr}.${tenths}`);\n\n\t\tconst contentWidth = Math.max(1, width - this.paddingX * 2);\n\t\tconst prefixWidth = visibleWidth(prefix);\n\t\tconst timerWidth = visibleWidth(timerStr);\n\n\t\tlet nextText: string;\n\t\tif (prefixWidth + timerWidth + 2 <= contentWidth) {\n\t\t\tconst padding = \" \".repeat(contentWidth - prefixWidth - timerWidth);\n\t\t\tnextText = prefix + padding + timerStr;\n\t\t} else {\n\t\t\tnextText = `${prefix} ${timerStr}`;\n\t\t}\n\n\t\tif (this.text !== nextText) {\n\t\t\tthis.setText(nextText);\n\t\t}\n\n\t\treturn super.render(width);\n\t}\n\n\tstart() {\n\t\tif (this.state === \"disposed\" || this.state === \"running\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.state = \"running\";\n\t\tthis.epoch++;\n\t\tthis.currentFrame = 0;\n\t\tthis.startTime = performance.now();\n\t\tthis.elapsedAtStop = 0;\n\n\t\tthis.updateDisplay();\n\n\t\tconst currentEpoch = this.epoch;\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tif (this.epoch !== currentEpoch || this.state !== \"running\") {\n\t\t\t\tthis.cleanupInterval();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, this.frameIntervalMs);\n\t}\n\n\tstop() {\n\t\tif (this.state === \"disposed\" || this.state === \"stopped\" || this.state === \"idle\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\tthis.elapsedAtStop = performance.now() - this.startTime;\n\t\t}\n\n\t\tthis.state = \"stopped\";\n\t\tthis.epoch++;\n\t\tthis.cleanupInterval();\n\t\tthis.updateDisplay();\n\t}\n\n\tdispose() {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state === \"running\" && this.startTime !== null) {\n\t\t\tthis.elapsedAtStop = performance.now() - this.startTime;\n\t\t}\n\n\t\tthis.state = \"disposed\";\n\t\tthis.epoch++;\n\t\tthis.cleanupInterval();\n\t\tthis.ui = null;\n\t\tthis.cachedLines = undefined;\n\t\tthis.cachedText = undefined;\n\t\tthis.cachedWidth = undefined;\n\t\tthis.text = \"\";\n\t}\n\n\tsetMessage(message: string) {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate cleanupInterval() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tprivate padFrame(frame: string): string {\n\t\tconst padding = Math.max(0, this.frameWidth - visibleWidth(frame));\n\t\treturn frame + \" \".repeat(padding);\n\t}\n\n\tprivate updateDisplay() {\n\t\tif (this.state === \"disposed\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
@@ -3,13 +3,13 @@ import type { Component } from "../tui.js";
3
3
  * Text component - displays multi-line text with word wrapping
4
4
  */
5
5
  export declare class Text implements Component {
6
- private text;
7
- private paddingX;
8
- private paddingY;
6
+ protected text: string;
7
+ protected paddingX: number;
8
+ protected paddingY: number;
9
9
  private customBgFn?;
10
- private cachedText?;
11
- private cachedWidth?;
12
- private cachedLines?;
10
+ protected cachedText?: string;
11
+ protected cachedWidth?: number;
12
+ protected cachedLines?: string[];
13
13
  constructor(text?: string, paddingX?: number, paddingY?: number, customBgFn?: (text: string) => string);
14
14
  setText(text: string): void;
15
15
  setCustomBgFn(customBgFn?: (text: string) => string): void;