@mariozechner/pi-tui 0.7.26 → 0.7.27

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.
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/components/editor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAgC,MAAM,oBAAoB,CAAC;AAC7F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAe3C,MAAM,WAAW,gBAAgB;CAEhC;AAED,qBAAa,MAAO,YAAW,SAAS;IACvC,OAAO,CAAC,KAAK,CAIX;IAEF,OAAO,CAAC,MAAM,CAAwB;IAG/B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAc;IAGzD,OAAO,CAAC,oBAAoB,CAAC,CAAuB;IACpD,OAAO,CAAC,gBAAgB,CAAC,CAAa;IACtC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,kBAAkB,CAAc;IAGxC,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAE5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,EAAE,OAAO,CAAS;IAEtC,YAAY,MAAM,CAAC,EAAE,gBAAgB,EAIpC;IAED,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAEjD;IAED,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAE5D;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiE9B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkN9B;IAED,OAAO,CAAC,UAAU;IAoElB,OAAO,IAAI,MAAM,CAEhB;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAe1B;IAGD,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,WAAW;IAwFnB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAgD3B,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,sBAAsB;IA6B9B,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,4BAA4B;IAMpC,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,kBAAkB;IAMnB,qBAAqB,IAAI,OAAO,CAEtC;IAED,OAAO,CAAC,kBAAkB;CAoB1B","sourcesContent":["import chalk from \"chalk\";\nimport type { AutocompleteProvider, CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { Component } from \"../tui.js\";\nimport { SelectList } from \"./select-list.js\";\n\ninterface EditorState {\n\tlines: string[];\n\tcursorLine: number;\n\tcursorCol: number;\n}\n\ninterface LayoutLine {\n\ttext: string;\n\thasCursor: boolean;\n\tcursorPos?: number;\n}\n\nexport interface TextEditorConfig {\n\t// Configuration options for text editor (none currently)\n}\n\nexport class Editor implements Component {\n\tprivate state: EditorState = {\n\t\tlines: [\"\"],\n\t\tcursorLine: 0,\n\t\tcursorCol: 0,\n\t};\n\n\tprivate config: TextEditorConfig = {};\n\n\t// Border color (can be changed dynamically)\n\tpublic borderColor: (str: string) => string = chalk.gray;\n\n\t// Autocomplete support\n\tprivate autocompleteProvider?: AutocompleteProvider;\n\tprivate autocompleteList?: SelectList;\n\tprivate isAutocompleting: boolean = false;\n\tprivate autocompletePrefix: string = \"\";\n\n\t// Paste tracking for large pastes\n\tprivate pastes: Map<number, string> = new Map();\n\tprivate pasteCounter: number = 0;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tpublic onSubmit?: (text: string) => void;\n\tpublic onChange?: (text: string) => void;\n\tpublic disableSubmit: boolean = false;\n\n\tconstructor(config?: TextEditorConfig) {\n\t\tif (config) {\n\t\t\tthis.config = { ...this.config, ...config };\n\t\t}\n\t}\n\n\tconfigure(config: Partial<TextEditorConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tsetAutocompleteProvider(provider: AutocompleteProvider): void {\n\t\tthis.autocompleteProvider = provider;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst horizontal = this.borderColor(\"─\");\n\n\t\t// Layout the text - use full width\n\t\tconst layoutLines = this.layoutText(width);\n\n\t\tconst result: string[] = [];\n\n\t\t// Render top border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Render each layout line\n\t\tfor (const layoutLine of layoutLines) {\n\t\t\tlet displayText = layoutLine.text;\n\t\t\tlet visibleLength = layoutLine.text.length;\n\n\t\t\t// Add cursor if this line has it\n\t\t\tif (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {\n\t\t\t\tconst before = displayText.slice(0, layoutLine.cursorPos);\n\t\t\t\tconst after = displayText.slice(layoutLine.cursorPos);\n\n\t\t\t\tif (after.length > 0) {\n\t\t\t\t\t// Cursor is on a character - replace it with highlighted version\n\t\t\t\t\tconst cursor = `\\x1b[7m${after[0]}\\x1b[0m`;\n\t\t\t\t\tconst restAfter = after.slice(1);\n\t\t\t\t\tdisplayText = before + cursor + restAfter;\n\t\t\t\t\t// visibleLength stays the same - we're replacing, not adding\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor is at the end - check if we have room for the space\n\t\t\t\t\tif (layoutLine.text.length < width) {\n\t\t\t\t\t\t// We have room - add highlighted space\n\t\t\t\t\t\tconst cursor = \"\\x1b[7m \\x1b[0m\";\n\t\t\t\t\t\tdisplayText = before + cursor;\n\t\t\t\t\t\t// visibleLength increases by 1 - we're adding a space\n\t\t\t\t\t\tvisibleLength = layoutLine.text.length + 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Line is at full width - use reverse video on last character if possible\n\t\t\t\t\t\t// or just show cursor at the end without adding space\n\t\t\t\t\t\tif (before.length > 0) {\n\t\t\t\t\t\t\tconst lastChar = before[before.length - 1];\n\t\t\t\t\t\t\tconst cursor = `\\x1b[7m${lastChar}\\x1b[0m`;\n\t\t\t\t\t\t\tdisplayText = before.slice(0, -1) + cursor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// visibleLength stays the same\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate padding based on actual visible length\n\t\t\tconst padding = \" \".repeat(Math.max(0, width - visibleLength));\n\n\t\t\t// Render the line (no side borders, just horizontal lines above and below)\n\t\t\tresult.push(displayText + padding);\n\t\t}\n\n\t\t// Render bottom border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Add autocomplete list if active\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\tconst autocompleteResult = this.autocompleteList.render(width);\n\t\t\tresult.push(...autocompleteResult);\n\t\t}\n\n\t\treturn result;\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\t// Remove the start marker and keep the rest\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// Append data to buffer first (end marker could be split across chunks)\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\t// Check if the accumulated buffer contains the end marker\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract content before the end marker\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// Process any remaining data after the end marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t// Still accumulating, wait for more data\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Handle special key combinations first\n\n\t\t// Ctrl+C - Exit (let parent handle this)\n\t\tif (data.charCodeAt(0) === 3) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle autocomplete special keys first (but don't block other input)\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\t// Escape - cancel autocomplete\n\t\t\tif (data === \"\\x1b\") {\n\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Let the autocomplete list handle navigation and selection\n\t\t\telse if (data === \"\\x1b[A\" || data === \"\\x1b[B\" || data === \"\\r\" || data === \"\\t\") {\n\t\t\t\t// Only pass arrow keys to the list, not Enter/Tab (we handle those directly)\n\t\t\t\tif (data === \"\\x1b[A\" || data === \"\\x1b[B\") {\n\t\t\t\t\tthis.autocompleteList.handleInput(data);\n\t\t\t\t}\n\n\t\t\t\t// If Tab or Enter was pressed, apply the selection\n\t\t\t\tif (data === \"\\t\" || data === \"\\r\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\t// For other keys, handle normally within autocomplete\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// For other keys (like regular typing), DON'T return here\n\t\t\t// Let them fall through to normal character handling\n\t\t}\n\n\t\t// Tab key - context-aware completion (but not when already autocompleting)\n\t\tif (data === \"\\t\" && !this.isAutocompleting) {\n\t\t\tthis.handleTabCompletion();\n\t\t\treturn;\n\t\t}\n\n\t\t// Continue with rest of input handling\n\t\t// Ctrl+K - Delete to end of line\n\t\tif (data.charCodeAt(0) === 11) {\n\t\t\tthis.deleteToEndOfLine();\n\t\t}\n\t\t// Ctrl+U - Delete to start of line\n\t\telse if (data.charCodeAt(0) === 21) {\n\t\t\tthis.deleteToStartOfLine();\n\t\t}\n\t\t// Ctrl+W - Delete word backwards\n\t\telse if (data.charCodeAt(0) === 23) {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)\n\t\telse if (data === \"\\x1b\\x7f\") {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Ctrl+A - Move to start of line\n\t\telse if (data.charCodeAt(0) === 1) {\n\t\t\tthis.moveToLineStart();\n\t\t}\n\t\t// Ctrl+E - Move to end of line\n\t\telse if (data.charCodeAt(0) === 5) {\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// New line shortcuts (but not plain LF/CR which should be submit)\n\t\telse if (\n\t\t\t(data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers\n\t\t\tdata === \"\\x1b\\r\" || // Option+Enter in some terminals\n\t\t\tdata === \"\\x1b[13;2~\" || // Shift+Enter in some terminals\n\t\t\t(data.length > 1 && data.includes(\"\\x1b\") && data.includes(\"\\r\")) ||\n\t\t\t(data === \"\\n\" && data.length === 1) || // Shift+Enter from iTerm2 mapping\n\t\t\tdata === \"\\\\\\r\" // Shift+Enter in VS Code terminal\n\t\t) {\n\t\t\t// Modifier + Enter = new line\n\t\t\tthis.addNewLine();\n\t\t}\n\t\t// Plain Enter (char code 13 for CR) - only CR submits, LF adds new line\n\t\telse if (data.charCodeAt(0) === 13 && data.length === 1) {\n\t\t\t// If submit is disabled, do nothing\n\t\t\tif (this.disableSubmit) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Get text and substitute paste markers with actual content\n\t\t\tlet result = this.state.lines.join(\"\\n\").trim();\n\n\t\t\t// Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content\n\t\t\tfor (const [pasteId, pasteContent] of this.pastes) {\n\t\t\t\t// Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]\n\t\t\t\tconst markerRegex = new RegExp(`\\\\[paste #${pasteId}( (\\\\+\\\\d+ lines|\\\\d+ chars))?\\\\]`, \"g\");\n\t\t\t\tresult = result.replace(markerRegex, pasteContent);\n\t\t\t}\n\n\t\t\t// Reset editor and clear pastes\n\t\t\tthis.state = {\n\t\t\t\tlines: [\"\"],\n\t\t\t\tcursorLine: 0,\n\t\t\t\tcursorCol: 0,\n\t\t\t};\n\t\t\tthis.pastes.clear();\n\t\t\tthis.pasteCounter = 0;\n\n\t\t\t// Notify that editor is now empty\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(\"\");\n\t\t\t}\n\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(result);\n\t\t\t}\n\t\t}\n\t\t// Backspace\n\t\telse if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {\n\t\t\tthis.handleBackspace();\n\t\t}\n\t\t// Line navigation shortcuts (Home/End keys)\n\t\telse if (data === \"\\x1b[H\" || data === \"\\x1b[1~\" || data === \"\\x1b[7~\") {\n\t\t\t// Home key\n\t\t\tthis.moveToLineStart();\n\t\t} else if (data === \"\\x1b[F\" || data === \"\\x1b[4~\" || data === \"\\x1b[8~\") {\n\t\t\t// End key\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// Forward delete (Fn+Backspace or Delete key)\n\t\telse if (data === \"\\x1b[3~\") {\n\t\t\t// Delete key\n\t\t\tthis.handleForwardDelete();\n\t\t}\n\t\t// Arrow keys\n\t\telse if (data === \"\\x1b[A\") {\n\t\t\t// Up\n\t\t\tthis.moveCursor(-1, 0);\n\t\t} else if (data === \"\\x1b[B\") {\n\t\t\t// Down\n\t\t\tthis.moveCursor(1, 0);\n\t\t} else if (data === \"\\x1b[C\") {\n\t\t\t// Right\n\t\t\tthis.moveCursor(0, 1);\n\t\t} else if (data === \"\\x1b[D\") {\n\t\t\t// Left\n\t\t\tthis.moveCursor(0, -1);\n\t\t}\n\t\t// Regular characters (printable characters and unicode, but not control characters)\n\t\telse if (data.charCodeAt(0) >= 32) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate layoutText(contentWidth: number): LayoutLine[] {\n\t\tconst layoutLines: LayoutLine[] = [];\n\n\t\tif (this.state.lines.length === 0 || (this.state.lines.length === 1 && this.state.lines[0] === \"\")) {\n\t\t\t// Empty editor\n\t\t\tlayoutLines.push({\n\t\t\t\ttext: \"\",\n\t\t\t\thasCursor: true,\n\t\t\t\tcursorPos: 0,\n\t\t\t});\n\t\t\treturn layoutLines;\n\t\t}\n\n\t\t// Process each logical line\n\t\tfor (let i = 0; i < this.state.lines.length; i++) {\n\t\t\tconst line = this.state.lines[i] || \"\";\n\t\t\tconst isCurrentLine = i === this.state.cursorLine;\n\t\t\tconst maxLineLength = contentWidth;\n\n\t\t\tif (line.length <= maxLineLength) {\n\t\t\t\t// Line fits in one layout line\n\t\t\t\tif (isCurrentLine) {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\tcursorPos: this.state.cursorCol,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Line needs wrapping\n\t\t\t\tconst chunks = [];\n\t\t\t\tfor (let pos = 0; pos < line.length; pos += maxLineLength) {\n\t\t\t\t\tchunks.push(line.slice(pos, pos + maxLineLength));\n\t\t\t\t}\n\n\t\t\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {\n\t\t\t\t\tconst chunk = chunks[chunkIndex];\n\t\t\t\t\tif (!chunk) continue;\n\n\t\t\t\t\tconst chunkStart = chunkIndex * maxLineLength;\n\t\t\t\t\tconst chunkEnd = chunkStart + chunk.length;\n\t\t\t\t\tconst cursorPos = this.state.cursorCol;\n\t\t\t\t\tconst hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;\n\n\t\t\t\t\tif (hasCursorInChunk) {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\t\tcursorPos: cursorPos - chunkStart,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn layoutLines;\n\t}\n\n\tgetText(): string {\n\t\treturn this.state.lines.join(\"\\n\");\n\t}\n\n\tsetText(text: string): void {\n\t\t// Split text into lines, handling different line endings\n\t\tconst lines = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n\t\t// Ensure at least one empty line\n\t\tthis.state.lines = lines.length === 0 ? [\"\"] : lines;\n\n\t\t// Reset cursor to end of text\n\t\tthis.state.cursorLine = this.state.lines.length - 1;\n\t\tthis.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\t// All the editor methods from before...\n\tprivate insertCharacter(char: string): void {\n\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = line.slice(0, this.state.cursorCol);\n\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\tthis.state.lines[this.state.cursorLine] = before + char + after;\n\t\tthis.state.cursorCol += char.length; // Fix: increment by the length of the inserted string\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Check if we should trigger or update autocomplete\n\t\tif (!this.isAutocompleting) {\n\t\t\t// Auto-trigger for \"/\" at the start of a line (slash commands)\n\t\t\tif (char === \"/\" && this.isAtStartOfMessage()) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t\t// Also auto-trigger when typing letters in a slash command context\n\t\t\telse if (/[a-zA-Z0-9]/.test(char)) {\n\t\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\t\t// Check if we're in a slash command with a space (i.e., typing arguments)\n\t\t\t\tif (textBeforeCursor.startsWith(\"/\") && textBeforeCursor.includes(\" \")) {\n\t\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Convert tabs to spaces (4 spaces per tab)\n\t\tconst tabExpandedText = cleanText.replace(/\\t/g, \" \");\n\n\t\t// Filter out non-printable characters except newlines\n\t\tconst filteredText = tabExpandedText\n\t\t\t.split(\"\")\n\t\t\t.filter((char) => char === \"\\n\" || char.charCodeAt(0) >= 32)\n\t\t\t.join(\"\");\n\n\t\t// Split into lines\n\t\tconst pastedLines = filteredText.split(\"\\n\");\n\n\t\t// Check if this is a large paste (> 10 lines or > 1000 characters)\n\t\tconst totalChars = filteredText.length;\n\t\tif (pastedLines.length > 10 || totalChars > 1000) {\n\t\t\t// Store the paste and insert a marker\n\t\t\tthis.pasteCounter++;\n\t\t\tconst pasteId = this.pasteCounter;\n\t\t\tthis.pastes.set(pasteId, filteredText);\n\n\t\t\t// Insert marker like \"[paste #1 +123 lines]\" or \"[paste #1 1234 chars]\"\n\t\t\tconst marker =\n\t\t\t\tpastedLines.length > 10\n\t\t\t\t\t? `[paste #${pasteId} +${pastedLines.length} lines]`\n\t\t\t\t\t: `[paste #${pasteId} ${totalChars} chars]`;\n\t\t\tfor (const char of marker) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (pastedLines.length === 1) {\n\t\t\t// Single line - just insert each character\n\t\t\tconst text = pastedLines[0] || \"\";\n\t\t\tfor (const char of text) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Multi-line paste - be very careful with array manipulation\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\tconst afterCursor = currentLine.slice(this.state.cursorCol);\n\n\t\t// Build the new lines array step by step\n\t\tconst newLines: string[] = [];\n\n\t\t// Add all lines before current line\n\t\tfor (let i = 0; i < this.state.cursorLine; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Add the first pasted line merged with before cursor text\n\t\tnewLines.push(beforeCursor + (pastedLines[0] || \"\"));\n\n\t\t// Add all middle pasted lines\n\t\tfor (let i = 1; i < pastedLines.length - 1; i++) {\n\t\t\tnewLines.push(pastedLines[i] || \"\");\n\t\t}\n\n\t\t// Add the last pasted line with after cursor text\n\t\tnewLines.push((pastedLines[pastedLines.length - 1] || \"\") + afterCursor);\n\n\t\t// Add all lines after current line\n\t\tfor (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Replace the entire lines array\n\t\tthis.state.lines = newLines;\n\n\t\t// Update cursor position to end of pasted content\n\t\tthis.state.cursorLine += pastedLines.length - 1;\n\t\tthis.state.cursorCol = (pastedLines[pastedLines.length - 1] || \"\").length;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate addNewLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\tconst after = currentLine.slice(this.state.cursorCol);\n\n\t\t// Split current line\n\t\tthis.state.lines[this.state.cursorLine] = before;\n\t\tthis.state.lines.splice(this.state.cursorLine + 1, 0, after);\n\n\t\t// Move cursor to start of new line\n\t\tthis.state.cursorLine++;\n\t\tthis.state.cursorCol = 0;\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete character in current line\n\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t\tconst before = line.slice(0, this.state.cursorCol - 1);\n\t\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t\tthis.state.cursorCol--;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// Merge with previous line\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Update autocomplete after backspace\n\t\tif (this.isAutocompleting) {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate moveToLineStart(): void {\n\t\tthis.state.cursorCol = 0;\n\t}\n\n\tprivate moveToLineEnd(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tthis.state.cursorCol = currentLine.length;\n\t}\n\n\tprivate deleteToStartOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete from start of line up to cursor\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = 0;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// At start of line - merge with previous line\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteToEndOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete from cursor to end of line\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t// If at start of line, behave like backspace at column 0 (merge with previous line)\n\t\tif (this.state.cursorCol === 0) {\n\t\t\tif (this.state.cursorLine > 0) {\n\t\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\t\tthis.state.cursorLine--;\n\t\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t\t}\n\t\t} else {\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t\tconst isWhitespace = (char: string): boolean => /\\s/.test(char);\n\t\t\tconst isPunctuation = (char: string): boolean => {\n\t\t\t\t// Treat obvious code punctuation as boundaries\n\t\t\t\treturn /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/.test(char);\n\t\t\t};\n\n\t\t\tlet deleteFrom = this.state.cursorCol;\n\t\t\tconst lastChar = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\n\t\t\t// If immediately on whitespace or punctuation, delete that single boundary char\n\t\t\tif (isWhitespace(lastChar) || isPunctuation(lastChar)) {\n\t\t\t\tdeleteFrom -= 1;\n\t\t\t} else {\n\t\t\t\t// Otherwise, delete a run of non-boundary characters (the \"word\")\n\t\t\t\twhile (deleteFrom > 0) {\n\t\t\t\t\tconst ch = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\t\t\t\t\tif (isWhitespace(ch) || isPunctuation(ch)) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdeleteFrom -= 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.state.lines[this.state.cursorLine] =\n\t\t\t\tcurrentLine.slice(0, deleteFrom) + currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = deleteFrom;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete character at cursor position (forward delete)\n\t\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\t\tconst after = currentLine.slice(this.state.cursorCol + 1);\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate moveCursor(deltaLine: number, deltaCol: number): void {\n\t\tif (deltaLine !== 0) {\n\t\t\tconst newLine = this.state.cursorLine + deltaLine;\n\t\t\tif (newLine >= 0 && newLine < this.state.lines.length) {\n\t\t\t\tthis.state.cursorLine = newLine;\n\t\t\t\t// Clamp cursor column to new line length\n\t\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tthis.state.cursorCol = Math.min(this.state.cursorCol, line.length);\n\t\t\t}\n\t\t}\n\n\t\tif (deltaCol !== 0) {\n\t\t\t// Move column\n\t\t\tconst newCol = this.state.cursorCol + deltaCol;\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst maxCol = currentLine.length;\n\t\t\tthis.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));\n\t\t}\n\t}\n\n\t// Helper method to check if cursor is at start of message (for slash command detection)\n\tprivate isAtStartOfMessage(): boolean {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// At start if line is empty, only contains whitespace, or is just \"/\"\n\t\treturn beforeCursor.trim() === \"\" || beforeCursor.trim() === \"/\";\n\t}\n\n\t// Autocomplete methods\n\tprivate tryTriggerAutocomplete(explicitTab: boolean = false): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if we should trigger file completion on Tab\n\t\tif (explicitTab) {\n\t\t\tconst provider = this.autocompleteProvider as CombinedAutocompleteProvider;\n\t\t\tconst shouldTrigger =\n\t\t\t\t!provider.shouldTriggerFileCompletion ||\n\t\t\t\tprovider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol);\n\t\t\tif (!shouldTrigger) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleTabCompletion(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// Check if we're in a slash command context\n\t\tif (beforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\tthis.handleSlashCommandCompletion();\n\t\t} else {\n\t\t\tthis.forceFileAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleSlashCommandCompletion(): void {\n\t\t// For now, fall back to regular autocomplete (slash commands)\n\t\t// This can be extended later to handle command-specific argument completion\n\t\tthis.tryTriggerAutocomplete(true);\n\t}\n\n\tprivate forceFileAutocomplete(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if provider has the force method\n\t\tconst provider = this.autocompleteProvider as any;\n\t\tif (!provider.getForceFileSuggestions) {\n\t\t\tthis.tryTriggerAutocomplete(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggestions = provider.getForceFileSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate cancelAutocomplete(): void {\n\t\tthis.isAutocompleting = false;\n\t\tthis.autocompleteList = undefined as any;\n\t\tthis.autocompletePrefix = \"\";\n\t}\n\n\tpublic isShowingAutocomplete(): boolean {\n\t\treturn this.isAutocompleting;\n\t}\n\n\tprivate updateAutocomplete(): void {\n\t\tif (!this.isAutocompleting || !this.autocompleteProvider) return;\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tif (this.autocompleteList) {\n\t\t\t\t// Update the existing list with new items\n\t\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\t}\n\t\t} else {\n\t\t\t// No more matches, cancel autocomplete\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/components/editor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAgC,MAAM,oBAAoB,CAAC;AAC7F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAe3C,MAAM,WAAW,gBAAgB;CAEhC;AAED,qBAAa,MAAO,YAAW,SAAS;IACvC,OAAO,CAAC,KAAK,CAIX;IAEF,OAAO,CAAC,MAAM,CAAwB;IAG/B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAc;IAGzD,OAAO,CAAC,oBAAoB,CAAC,CAAuB;IACpD,OAAO,CAAC,gBAAgB,CAAC,CAAa;IACtC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,kBAAkB,CAAc;IAGxC,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAkB;IAE5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,EAAE,OAAO,CAAS;IAEtC,YAAY,MAAM,CAAC,EAAE,gBAAgB,EAIpC;IAED,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAEjD;IAED,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAE5D;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiE9B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA6O9B;IAED,OAAO,CAAC,UAAU;IAoElB,OAAO,IAAI,MAAM,CAEhB;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAe1B;IAGD,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,WAAW;IAwFnB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,eAAe;IAuCvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAgD3B,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,sBAAsB;IA6B9B,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,4BAA4B;IAMpC,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,kBAAkB;IAMnB,qBAAqB,IAAI,OAAO,CAEtC;IAED,OAAO,CAAC,kBAAkB;CAoB1B","sourcesContent":["import chalk from \"chalk\";\nimport type { AutocompleteProvider, CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { Component } from \"../tui.js\";\nimport { SelectList } from \"./select-list.js\";\n\ninterface EditorState {\n\tlines: string[];\n\tcursorLine: number;\n\tcursorCol: number;\n}\n\ninterface LayoutLine {\n\ttext: string;\n\thasCursor: boolean;\n\tcursorPos?: number;\n}\n\nexport interface TextEditorConfig {\n\t// Configuration options for text editor (none currently)\n}\n\nexport class Editor implements Component {\n\tprivate state: EditorState = {\n\t\tlines: [\"\"],\n\t\tcursorLine: 0,\n\t\tcursorCol: 0,\n\t};\n\n\tprivate config: TextEditorConfig = {};\n\n\t// Border color (can be changed dynamically)\n\tpublic borderColor: (str: string) => string = chalk.gray;\n\n\t// Autocomplete support\n\tprivate autocompleteProvider?: AutocompleteProvider;\n\tprivate autocompleteList?: SelectList;\n\tprivate isAutocompleting: boolean = false;\n\tprivate autocompletePrefix: string = \"\";\n\n\t// Paste tracking for large pastes\n\tprivate pastes: Map<number, string> = new Map();\n\tprivate pasteCounter: number = 0;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tpublic onSubmit?: (text: string) => void;\n\tpublic onChange?: (text: string) => void;\n\tpublic disableSubmit: boolean = false;\n\n\tconstructor(config?: TextEditorConfig) {\n\t\tif (config) {\n\t\t\tthis.config = { ...this.config, ...config };\n\t\t}\n\t}\n\n\tconfigure(config: Partial<TextEditorConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tsetAutocompleteProvider(provider: AutocompleteProvider): void {\n\t\tthis.autocompleteProvider = provider;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst horizontal = this.borderColor(\"─\");\n\n\t\t// Layout the text - use full width\n\t\tconst layoutLines = this.layoutText(width);\n\n\t\tconst result: string[] = [];\n\n\t\t// Render top border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Render each layout line\n\t\tfor (const layoutLine of layoutLines) {\n\t\t\tlet displayText = layoutLine.text;\n\t\t\tlet visibleLength = layoutLine.text.length;\n\n\t\t\t// Add cursor if this line has it\n\t\t\tif (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {\n\t\t\t\tconst before = displayText.slice(0, layoutLine.cursorPos);\n\t\t\t\tconst after = displayText.slice(layoutLine.cursorPos);\n\n\t\t\t\tif (after.length > 0) {\n\t\t\t\t\t// Cursor is on a character - replace it with highlighted version\n\t\t\t\t\tconst cursor = `\\x1b[7m${after[0]}\\x1b[0m`;\n\t\t\t\t\tconst restAfter = after.slice(1);\n\t\t\t\t\tdisplayText = before + cursor + restAfter;\n\t\t\t\t\t// visibleLength stays the same - we're replacing, not adding\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor is at the end - check if we have room for the space\n\t\t\t\t\tif (layoutLine.text.length < width) {\n\t\t\t\t\t\t// We have room - add highlighted space\n\t\t\t\t\t\tconst cursor = \"\\x1b[7m \\x1b[0m\";\n\t\t\t\t\t\tdisplayText = before + cursor;\n\t\t\t\t\t\t// visibleLength increases by 1 - we're adding a space\n\t\t\t\t\t\tvisibleLength = layoutLine.text.length + 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Line is at full width - use reverse video on last character if possible\n\t\t\t\t\t\t// or just show cursor at the end without adding space\n\t\t\t\t\t\tif (before.length > 0) {\n\t\t\t\t\t\t\tconst lastChar = before[before.length - 1];\n\t\t\t\t\t\t\tconst cursor = `\\x1b[7m${lastChar}\\x1b[0m`;\n\t\t\t\t\t\t\tdisplayText = before.slice(0, -1) + cursor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// visibleLength stays the same\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate padding based on actual visible length\n\t\t\tconst padding = \" \".repeat(Math.max(0, width - visibleLength));\n\n\t\t\t// Render the line (no side borders, just horizontal lines above and below)\n\t\t\tresult.push(displayText + padding);\n\t\t}\n\n\t\t// Render bottom border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Add autocomplete list if active\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\tconst autocompleteResult = this.autocompleteList.render(width);\n\t\t\tresult.push(...autocompleteResult);\n\t\t}\n\n\t\treturn result;\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\t// Remove the start marker and keep the rest\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// Append data to buffer first (end marker could be split across chunks)\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\t// Check if the accumulated buffer contains the end marker\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract content before the end marker\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// Process any remaining data after the end marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t// Still accumulating, wait for more data\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Handle special key combinations first\n\n\t\t// Ctrl+C - Exit (let parent handle this)\n\t\tif (data.charCodeAt(0) === 3) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle autocomplete special keys first (but don't block other input)\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\t// Escape - cancel autocomplete\n\t\t\tif (data === \"\\x1b\") {\n\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Let the autocomplete list handle navigation and selection\n\t\t\telse if (data === \"\\x1b[A\" || data === \"\\x1b[B\" || data === \"\\r\" || data === \"\\t\") {\n\t\t\t\t// Only pass arrow keys to the list, not Enter/Tab (we handle those directly)\n\t\t\t\tif (data === \"\\x1b[A\" || data === \"\\x1b[B\") {\n\t\t\t\t\tthis.autocompleteList.handleInput(data);\n\t\t\t\t}\n\n\t\t\t\t// If Tab was pressed, always apply the selection\n\t\t\t\tif (data === \"\\t\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// If Enter was pressed on a slash command, cancel autocomplete and let it submit\n\t\t\t\tif (data === \"\\r\" && this.autocompletePrefix.startsWith(\"/\")) {\n\t\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\t\t// Don't return - fall through to submission logic\n\t\t\t\t}\n\t\t\t\t// If Enter was pressed on a file path, apply completion\n\t\t\t\telse if (data === \"\\r\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// For other keys (like regular typing), DON'T return here\n\t\t\t// Let them fall through to normal character handling\n\t\t}\n\n\t\t// Tab key - context-aware completion (but not when already autocompleting)\n\t\tif (data === \"\\t\" && !this.isAutocompleting) {\n\t\t\tthis.handleTabCompletion();\n\t\t\treturn;\n\t\t}\n\n\t\t// Continue with rest of input handling\n\t\t// Ctrl+K - Delete to end of line\n\t\tif (data.charCodeAt(0) === 11) {\n\t\t\tthis.deleteToEndOfLine();\n\t\t}\n\t\t// Ctrl+U - Delete to start of line\n\t\telse if (data.charCodeAt(0) === 21) {\n\t\t\tthis.deleteToStartOfLine();\n\t\t}\n\t\t// Ctrl+W - Delete word backwards\n\t\telse if (data.charCodeAt(0) === 23) {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)\n\t\telse if (data === \"\\x1b\\x7f\") {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Ctrl+A - Move to start of line\n\t\telse if (data.charCodeAt(0) === 1) {\n\t\t\tthis.moveToLineStart();\n\t\t}\n\t\t// Ctrl+E - Move to end of line\n\t\telse if (data.charCodeAt(0) === 5) {\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// New line shortcuts (but not plain LF/CR which should be submit)\n\t\telse if (\n\t\t\t(data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers\n\t\t\tdata === \"\\x1b\\r\" || // Option+Enter in some terminals\n\t\t\tdata === \"\\x1b[13;2~\" || // Shift+Enter in some terminals\n\t\t\t(data.length > 1 && data.includes(\"\\x1b\") && data.includes(\"\\r\")) ||\n\t\t\t(data === \"\\n\" && data.length === 1) || // Shift+Enter from iTerm2 mapping\n\t\t\tdata === \"\\\\\\r\" // Shift+Enter in VS Code terminal\n\t\t) {\n\t\t\t// Modifier + Enter = new line\n\t\t\tthis.addNewLine();\n\t\t}\n\t\t// Plain Enter (char code 13 for CR) - only CR submits, LF adds new line\n\t\telse if (data.charCodeAt(0) === 13 && data.length === 1) {\n\t\t\t// If submit is disabled, do nothing\n\t\t\tif (this.disableSubmit) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Get text and substitute paste markers with actual content\n\t\t\tlet result = this.state.lines.join(\"\\n\").trim();\n\n\t\t\t// Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content\n\t\t\tfor (const [pasteId, pasteContent] of this.pastes) {\n\t\t\t\t// Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]\n\t\t\t\tconst markerRegex = new RegExp(`\\\\[paste #${pasteId}( (\\\\+\\\\d+ lines|\\\\d+ chars))?\\\\]`, \"g\");\n\t\t\t\tresult = result.replace(markerRegex, pasteContent);\n\t\t\t}\n\n\t\t\t// Reset editor and clear pastes\n\t\t\tthis.state = {\n\t\t\t\tlines: [\"\"],\n\t\t\t\tcursorLine: 0,\n\t\t\t\tcursorCol: 0,\n\t\t\t};\n\t\t\tthis.pastes.clear();\n\t\t\tthis.pasteCounter = 0;\n\n\t\t\t// Notify that editor is now empty\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(\"\");\n\t\t\t}\n\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(result);\n\t\t\t}\n\t\t}\n\t\t// Backspace\n\t\telse if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {\n\t\t\tthis.handleBackspace();\n\t\t}\n\t\t// Line navigation shortcuts (Home/End keys)\n\t\telse if (data === \"\\x1b[H\" || data === \"\\x1b[1~\" || data === \"\\x1b[7~\") {\n\t\t\t// Home key\n\t\t\tthis.moveToLineStart();\n\t\t} else if (data === \"\\x1b[F\" || data === \"\\x1b[4~\" || data === \"\\x1b[8~\") {\n\t\t\t// End key\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// Forward delete (Fn+Backspace or Delete key)\n\t\telse if (data === \"\\x1b[3~\") {\n\t\t\t// Delete key\n\t\t\tthis.handleForwardDelete();\n\t\t}\n\t\t// Arrow keys\n\t\telse if (data === \"\\x1b[A\") {\n\t\t\t// Up\n\t\t\tthis.moveCursor(-1, 0);\n\t\t} else if (data === \"\\x1b[B\") {\n\t\t\t// Down\n\t\t\tthis.moveCursor(1, 0);\n\t\t} else if (data === \"\\x1b[C\") {\n\t\t\t// Right\n\t\t\tthis.moveCursor(0, 1);\n\t\t} else if (data === \"\\x1b[D\") {\n\t\t\t// Left\n\t\t\tthis.moveCursor(0, -1);\n\t\t}\n\t\t// Regular characters (printable characters and unicode, but not control characters)\n\t\telse if (data.charCodeAt(0) >= 32) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate layoutText(contentWidth: number): LayoutLine[] {\n\t\tconst layoutLines: LayoutLine[] = [];\n\n\t\tif (this.state.lines.length === 0 || (this.state.lines.length === 1 && this.state.lines[0] === \"\")) {\n\t\t\t// Empty editor\n\t\t\tlayoutLines.push({\n\t\t\t\ttext: \"\",\n\t\t\t\thasCursor: true,\n\t\t\t\tcursorPos: 0,\n\t\t\t});\n\t\t\treturn layoutLines;\n\t\t}\n\n\t\t// Process each logical line\n\t\tfor (let i = 0; i < this.state.lines.length; i++) {\n\t\t\tconst line = this.state.lines[i] || \"\";\n\t\t\tconst isCurrentLine = i === this.state.cursorLine;\n\t\t\tconst maxLineLength = contentWidth;\n\n\t\t\tif (line.length <= maxLineLength) {\n\t\t\t\t// Line fits in one layout line\n\t\t\t\tif (isCurrentLine) {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\tcursorPos: this.state.cursorCol,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Line needs wrapping\n\t\t\t\tconst chunks = [];\n\t\t\t\tfor (let pos = 0; pos < line.length; pos += maxLineLength) {\n\t\t\t\t\tchunks.push(line.slice(pos, pos + maxLineLength));\n\t\t\t\t}\n\n\t\t\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {\n\t\t\t\t\tconst chunk = chunks[chunkIndex];\n\t\t\t\t\tif (!chunk) continue;\n\n\t\t\t\t\tconst chunkStart = chunkIndex * maxLineLength;\n\t\t\t\t\tconst chunkEnd = chunkStart + chunk.length;\n\t\t\t\t\tconst cursorPos = this.state.cursorCol;\n\t\t\t\t\tconst hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;\n\n\t\t\t\t\tif (hasCursorInChunk) {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\t\tcursorPos: cursorPos - chunkStart,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn layoutLines;\n\t}\n\n\tgetText(): string {\n\t\treturn this.state.lines.join(\"\\n\");\n\t}\n\n\tsetText(text: string): void {\n\t\t// Split text into lines, handling different line endings\n\t\tconst lines = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n\t\t// Ensure at least one empty line\n\t\tthis.state.lines = lines.length === 0 ? [\"\"] : lines;\n\n\t\t// Reset cursor to end of text\n\t\tthis.state.cursorLine = this.state.lines.length - 1;\n\t\tthis.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\t// All the editor methods from before...\n\tprivate insertCharacter(char: string): void {\n\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = line.slice(0, this.state.cursorCol);\n\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\tthis.state.lines[this.state.cursorLine] = before + char + after;\n\t\tthis.state.cursorCol += char.length; // Fix: increment by the length of the inserted string\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Check if we should trigger or update autocomplete\n\t\tif (!this.isAutocompleting) {\n\t\t\t// Auto-trigger for \"/\" at the start of a line (slash commands)\n\t\t\tif (char === \"/\" && this.isAtStartOfMessage()) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t\t// Also auto-trigger when typing letters in a slash command context\n\t\t\telse if (/[a-zA-Z0-9]/.test(char)) {\n\t\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\t\t// Check if we're in a slash command (with or without space for arguments)\n\t\t\t\tif (textBeforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Convert tabs to spaces (4 spaces per tab)\n\t\tconst tabExpandedText = cleanText.replace(/\\t/g, \" \");\n\n\t\t// Filter out non-printable characters except newlines\n\t\tconst filteredText = tabExpandedText\n\t\t\t.split(\"\")\n\t\t\t.filter((char) => char === \"\\n\" || char.charCodeAt(0) >= 32)\n\t\t\t.join(\"\");\n\n\t\t// Split into lines\n\t\tconst pastedLines = filteredText.split(\"\\n\");\n\n\t\t// Check if this is a large paste (> 10 lines or > 1000 characters)\n\t\tconst totalChars = filteredText.length;\n\t\tif (pastedLines.length > 10 || totalChars > 1000) {\n\t\t\t// Store the paste and insert a marker\n\t\t\tthis.pasteCounter++;\n\t\t\tconst pasteId = this.pasteCounter;\n\t\t\tthis.pastes.set(pasteId, filteredText);\n\n\t\t\t// Insert marker like \"[paste #1 +123 lines]\" or \"[paste #1 1234 chars]\"\n\t\t\tconst marker =\n\t\t\t\tpastedLines.length > 10\n\t\t\t\t\t? `[paste #${pasteId} +${pastedLines.length} lines]`\n\t\t\t\t\t: `[paste #${pasteId} ${totalChars} chars]`;\n\t\t\tfor (const char of marker) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (pastedLines.length === 1) {\n\t\t\t// Single line - just insert each character\n\t\t\tconst text = pastedLines[0] || \"\";\n\t\t\tfor (const char of text) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Multi-line paste - be very careful with array manipulation\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\tconst afterCursor = currentLine.slice(this.state.cursorCol);\n\n\t\t// Build the new lines array step by step\n\t\tconst newLines: string[] = [];\n\n\t\t// Add all lines before current line\n\t\tfor (let i = 0; i < this.state.cursorLine; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Add the first pasted line merged with before cursor text\n\t\tnewLines.push(beforeCursor + (pastedLines[0] || \"\"));\n\n\t\t// Add all middle pasted lines\n\t\tfor (let i = 1; i < pastedLines.length - 1; i++) {\n\t\t\tnewLines.push(pastedLines[i] || \"\");\n\t\t}\n\n\t\t// Add the last pasted line with after cursor text\n\t\tnewLines.push((pastedLines[pastedLines.length - 1] || \"\") + afterCursor);\n\n\t\t// Add all lines after current line\n\t\tfor (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Replace the entire lines array\n\t\tthis.state.lines = newLines;\n\n\t\t// Update cursor position to end of pasted content\n\t\tthis.state.cursorLine += pastedLines.length - 1;\n\t\tthis.state.cursorCol = (pastedLines[pastedLines.length - 1] || \"\").length;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate addNewLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\tconst after = currentLine.slice(this.state.cursorCol);\n\n\t\t// Split current line\n\t\tthis.state.lines[this.state.cursorLine] = before;\n\t\tthis.state.lines.splice(this.state.cursorLine + 1, 0, after);\n\n\t\t// Move cursor to start of new line\n\t\tthis.state.cursorLine++;\n\t\tthis.state.cursorCol = 0;\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete character in current line\n\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t\tconst before = line.slice(0, this.state.cursorCol - 1);\n\t\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t\tthis.state.cursorCol--;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// Merge with previous line\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Update or re-trigger autocomplete after backspace\n\t\tif (this.isAutocompleting) {\n\t\t\tthis.updateAutocomplete();\n\t\t} else {\n\t\t\t// If autocomplete was cancelled (no matches), re-trigger if we're in slash command context\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\tif (textBeforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate moveToLineStart(): void {\n\t\tthis.state.cursorCol = 0;\n\t}\n\n\tprivate moveToLineEnd(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tthis.state.cursorCol = currentLine.length;\n\t}\n\n\tprivate deleteToStartOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete from start of line up to cursor\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = 0;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// At start of line - merge with previous line\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteToEndOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete from cursor to end of line\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t// If at start of line, behave like backspace at column 0 (merge with previous line)\n\t\tif (this.state.cursorCol === 0) {\n\t\t\tif (this.state.cursorLine > 0) {\n\t\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\t\tthis.state.cursorLine--;\n\t\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t\t}\n\t\t} else {\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t\tconst isWhitespace = (char: string): boolean => /\\s/.test(char);\n\t\t\tconst isPunctuation = (char: string): boolean => {\n\t\t\t\t// Treat obvious code punctuation as boundaries\n\t\t\t\treturn /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/.test(char);\n\t\t\t};\n\n\t\t\tlet deleteFrom = this.state.cursorCol;\n\t\t\tconst lastChar = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\n\t\t\t// If immediately on whitespace or punctuation, delete that single boundary char\n\t\t\tif (isWhitespace(lastChar) || isPunctuation(lastChar)) {\n\t\t\t\tdeleteFrom -= 1;\n\t\t\t} else {\n\t\t\t\t// Otherwise, delete a run of non-boundary characters (the \"word\")\n\t\t\t\twhile (deleteFrom > 0) {\n\t\t\t\t\tconst ch = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\t\t\t\t\tif (isWhitespace(ch) || isPunctuation(ch)) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdeleteFrom -= 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.state.lines[this.state.cursorLine] =\n\t\t\t\tcurrentLine.slice(0, deleteFrom) + currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = deleteFrom;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete character at cursor position (forward delete)\n\t\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\t\tconst after = currentLine.slice(this.state.cursorCol + 1);\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate moveCursor(deltaLine: number, deltaCol: number): void {\n\t\tif (deltaLine !== 0) {\n\t\t\tconst newLine = this.state.cursorLine + deltaLine;\n\t\t\tif (newLine >= 0 && newLine < this.state.lines.length) {\n\t\t\t\tthis.state.cursorLine = newLine;\n\t\t\t\t// Clamp cursor column to new line length\n\t\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tthis.state.cursorCol = Math.min(this.state.cursorCol, line.length);\n\t\t\t}\n\t\t}\n\n\t\tif (deltaCol !== 0) {\n\t\t\t// Move column\n\t\t\tconst newCol = this.state.cursorCol + deltaCol;\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst maxCol = currentLine.length;\n\t\t\tthis.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));\n\t\t}\n\t}\n\n\t// Helper method to check if cursor is at start of message (for slash command detection)\n\tprivate isAtStartOfMessage(): boolean {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// At start if line is empty, only contains whitespace, or is just \"/\"\n\t\treturn beforeCursor.trim() === \"\" || beforeCursor.trim() === \"/\";\n\t}\n\n\t// Autocomplete methods\n\tprivate tryTriggerAutocomplete(explicitTab: boolean = false): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if we should trigger file completion on Tab\n\t\tif (explicitTab) {\n\t\t\tconst provider = this.autocompleteProvider as CombinedAutocompleteProvider;\n\t\t\tconst shouldTrigger =\n\t\t\t\t!provider.shouldTriggerFileCompletion ||\n\t\t\t\tprovider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol);\n\t\t\tif (!shouldTrigger) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleTabCompletion(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// Check if we're in a slash command context\n\t\tif (beforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\tthis.handleSlashCommandCompletion();\n\t\t} else {\n\t\t\tthis.forceFileAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleSlashCommandCompletion(): void {\n\t\t// For now, fall back to regular autocomplete (slash commands)\n\t\t// This can be extended later to handle command-specific argument completion\n\t\tthis.tryTriggerAutocomplete(true);\n\t}\n\n\tprivate forceFileAutocomplete(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if provider has the force method\n\t\tconst provider = this.autocompleteProvider as any;\n\t\tif (!provider.getForceFileSuggestions) {\n\t\t\tthis.tryTriggerAutocomplete(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggestions = provider.getForceFileSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate cancelAutocomplete(): void {\n\t\tthis.isAutocompleting = false;\n\t\tthis.autocompleteList = undefined as any;\n\t\tthis.autocompletePrefix = \"\";\n\t}\n\n\tpublic isShowingAutocomplete(): boolean {\n\t\treturn this.isAutocompleting;\n\t}\n\n\tprivate updateAutocomplete(): void {\n\t\tif (!this.isAutocompleting || !this.autocompleteProvider) return;\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tif (this.autocompleteList) {\n\t\t\t\t// Update the existing list with new items\n\t\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\t}\n\t\t} else {\n\t\t\t// No more matches, cancel autocomplete\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n}\n"]}
@@ -146,8 +146,8 @@ export class Editor {
146
146
  if (data === "\x1b[A" || data === "\x1b[B") {
147
147
  this.autocompleteList.handleInput(data);
148
148
  }
149
- // If Tab or Enter was pressed, apply the selection
150
- if (data === "\t" || data === "\r") {
149
+ // If Tab was pressed, always apply the selection
150
+ if (data === "\t") {
151
151
  const selected = this.autocompleteList.getSelectedItem();
152
152
  if (selected && this.autocompleteProvider) {
153
153
  const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
@@ -161,8 +161,24 @@ export class Editor {
161
161
  }
162
162
  return;
163
163
  }
164
- else {
165
- // For other keys, handle normally within autocomplete
164
+ // If Enter was pressed on a slash command, cancel autocomplete and let it submit
165
+ if (data === "\r" && this.autocompletePrefix.startsWith("/")) {
166
+ this.cancelAutocomplete();
167
+ // Don't return - fall through to submission logic
168
+ }
169
+ // If Enter was pressed on a file path, apply completion
170
+ else if (data === "\r") {
171
+ const selected = this.autocompleteList.getSelectedItem();
172
+ if (selected && this.autocompleteProvider) {
173
+ const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
174
+ this.state.lines = result.lines;
175
+ this.state.cursorLine = result.cursorLine;
176
+ this.state.cursorCol = result.cursorCol;
177
+ this.cancelAutocomplete();
178
+ if (this.onChange) {
179
+ this.onChange(this.getText());
180
+ }
181
+ }
166
182
  return;
167
183
  }
168
184
  }
@@ -380,8 +396,8 @@ export class Editor {
380
396
  else if (/[a-zA-Z0-9]/.test(char)) {
381
397
  const currentLine = this.state.lines[this.state.cursorLine] || "";
382
398
  const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
383
- // Check if we're in a slash command with a space (i.e., typing arguments)
384
- if (textBeforeCursor.startsWith("/") && textBeforeCursor.includes(" ")) {
399
+ // Check if we're in a slash command (with or without space for arguments)
400
+ if (textBeforeCursor.trimStart().startsWith("/")) {
385
401
  this.tryTriggerAutocomplete();
386
402
  }
387
403
  }
@@ -493,10 +509,18 @@ export class Editor {
493
509
  if (this.onChange) {
494
510
  this.onChange(this.getText());
495
511
  }
496
- // Update autocomplete after backspace
512
+ // Update or re-trigger autocomplete after backspace
497
513
  if (this.isAutocompleting) {
498
514
  this.updateAutocomplete();
499
515
  }
516
+ else {
517
+ // If autocomplete was cancelled (no matches), re-trigger if we're in slash command context
518
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
519
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
520
+ if (textBeforeCursor.trimStart().startsWith("/")) {
521
+ this.tryTriggerAutocomplete();
522
+ }
523
+ }
500
524
  }
501
525
  moveToLineStart() {
502
526
  this.state.cursorCol = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/components/editor.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport type { AutocompleteProvider, CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { Component } from \"../tui.js\";\nimport { SelectList } from \"./select-list.js\";\n\ninterface EditorState {\n\tlines: string[];\n\tcursorLine: number;\n\tcursorCol: number;\n}\n\ninterface LayoutLine {\n\ttext: string;\n\thasCursor: boolean;\n\tcursorPos?: number;\n}\n\nexport interface TextEditorConfig {\n\t// Configuration options for text editor (none currently)\n}\n\nexport class Editor implements Component {\n\tprivate state: EditorState = {\n\t\tlines: [\"\"],\n\t\tcursorLine: 0,\n\t\tcursorCol: 0,\n\t};\n\n\tprivate config: TextEditorConfig = {};\n\n\t// Border color (can be changed dynamically)\n\tpublic borderColor: (str: string) => string = chalk.gray;\n\n\t// Autocomplete support\n\tprivate autocompleteProvider?: AutocompleteProvider;\n\tprivate autocompleteList?: SelectList;\n\tprivate isAutocompleting: boolean = false;\n\tprivate autocompletePrefix: string = \"\";\n\n\t// Paste tracking for large pastes\n\tprivate pastes: Map<number, string> = new Map();\n\tprivate pasteCounter: number = 0;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tpublic onSubmit?: (text: string) => void;\n\tpublic onChange?: (text: string) => void;\n\tpublic disableSubmit: boolean = false;\n\n\tconstructor(config?: TextEditorConfig) {\n\t\tif (config) {\n\t\t\tthis.config = { ...this.config, ...config };\n\t\t}\n\t}\n\n\tconfigure(config: Partial<TextEditorConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tsetAutocompleteProvider(provider: AutocompleteProvider): void {\n\t\tthis.autocompleteProvider = provider;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst horizontal = this.borderColor(\"─\");\n\n\t\t// Layout the text - use full width\n\t\tconst layoutLines = this.layoutText(width);\n\n\t\tconst result: string[] = [];\n\n\t\t// Render top border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Render each layout line\n\t\tfor (const layoutLine of layoutLines) {\n\t\t\tlet displayText = layoutLine.text;\n\t\t\tlet visibleLength = layoutLine.text.length;\n\n\t\t\t// Add cursor if this line has it\n\t\t\tif (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {\n\t\t\t\tconst before = displayText.slice(0, layoutLine.cursorPos);\n\t\t\t\tconst after = displayText.slice(layoutLine.cursorPos);\n\n\t\t\t\tif (after.length > 0) {\n\t\t\t\t\t// Cursor is on a character - replace it with highlighted version\n\t\t\t\t\tconst cursor = `\\x1b[7m${after[0]}\\x1b[0m`;\n\t\t\t\t\tconst restAfter = after.slice(1);\n\t\t\t\t\tdisplayText = before + cursor + restAfter;\n\t\t\t\t\t// visibleLength stays the same - we're replacing, not adding\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor is at the end - check if we have room for the space\n\t\t\t\t\tif (layoutLine.text.length < width) {\n\t\t\t\t\t\t// We have room - add highlighted space\n\t\t\t\t\t\tconst cursor = \"\\x1b[7m \\x1b[0m\";\n\t\t\t\t\t\tdisplayText = before + cursor;\n\t\t\t\t\t\t// visibleLength increases by 1 - we're adding a space\n\t\t\t\t\t\tvisibleLength = layoutLine.text.length + 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Line is at full width - use reverse video on last character if possible\n\t\t\t\t\t\t// or just show cursor at the end without adding space\n\t\t\t\t\t\tif (before.length > 0) {\n\t\t\t\t\t\t\tconst lastChar = before[before.length - 1];\n\t\t\t\t\t\t\tconst cursor = `\\x1b[7m${lastChar}\\x1b[0m`;\n\t\t\t\t\t\t\tdisplayText = before.slice(0, -1) + cursor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// visibleLength stays the same\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate padding based on actual visible length\n\t\t\tconst padding = \" \".repeat(Math.max(0, width - visibleLength));\n\n\t\t\t// Render the line (no side borders, just horizontal lines above and below)\n\t\t\tresult.push(displayText + padding);\n\t\t}\n\n\t\t// Render bottom border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Add autocomplete list if active\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\tconst autocompleteResult = this.autocompleteList.render(width);\n\t\t\tresult.push(...autocompleteResult);\n\t\t}\n\n\t\treturn result;\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\t// Remove the start marker and keep the rest\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// Append data to buffer first (end marker could be split across chunks)\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\t// Check if the accumulated buffer contains the end marker\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract content before the end marker\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// Process any remaining data after the end marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t// Still accumulating, wait for more data\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Handle special key combinations first\n\n\t\t// Ctrl+C - Exit (let parent handle this)\n\t\tif (data.charCodeAt(0) === 3) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle autocomplete special keys first (but don't block other input)\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\t// Escape - cancel autocomplete\n\t\t\tif (data === \"\\x1b\") {\n\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Let the autocomplete list handle navigation and selection\n\t\t\telse if (data === \"\\x1b[A\" || data === \"\\x1b[B\" || data === \"\\r\" || data === \"\\t\") {\n\t\t\t\t// Only pass arrow keys to the list, not Enter/Tab (we handle those directly)\n\t\t\t\tif (data === \"\\x1b[A\" || data === \"\\x1b[B\") {\n\t\t\t\t\tthis.autocompleteList.handleInput(data);\n\t\t\t\t}\n\n\t\t\t\t// If Tab or Enter was pressed, apply the selection\n\t\t\t\tif (data === \"\\t\" || data === \"\\r\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\t// For other keys, handle normally within autocomplete\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// For other keys (like regular typing), DON'T return here\n\t\t\t// Let them fall through to normal character handling\n\t\t}\n\n\t\t// Tab key - context-aware completion (but not when already autocompleting)\n\t\tif (data === \"\\t\" && !this.isAutocompleting) {\n\t\t\tthis.handleTabCompletion();\n\t\t\treturn;\n\t\t}\n\n\t\t// Continue with rest of input handling\n\t\t// Ctrl+K - Delete to end of line\n\t\tif (data.charCodeAt(0) === 11) {\n\t\t\tthis.deleteToEndOfLine();\n\t\t}\n\t\t// Ctrl+U - Delete to start of line\n\t\telse if (data.charCodeAt(0) === 21) {\n\t\t\tthis.deleteToStartOfLine();\n\t\t}\n\t\t// Ctrl+W - Delete word backwards\n\t\telse if (data.charCodeAt(0) === 23) {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)\n\t\telse if (data === \"\\x1b\\x7f\") {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Ctrl+A - Move to start of line\n\t\telse if (data.charCodeAt(0) === 1) {\n\t\t\tthis.moveToLineStart();\n\t\t}\n\t\t// Ctrl+E - Move to end of line\n\t\telse if (data.charCodeAt(0) === 5) {\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// New line shortcuts (but not plain LF/CR which should be submit)\n\t\telse if (\n\t\t\t(data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers\n\t\t\tdata === \"\\x1b\\r\" || // Option+Enter in some terminals\n\t\t\tdata === \"\\x1b[13;2~\" || // Shift+Enter in some terminals\n\t\t\t(data.length > 1 && data.includes(\"\\x1b\") && data.includes(\"\\r\")) ||\n\t\t\t(data === \"\\n\" && data.length === 1) || // Shift+Enter from iTerm2 mapping\n\t\t\tdata === \"\\\\\\r\" // Shift+Enter in VS Code terminal\n\t\t) {\n\t\t\t// Modifier + Enter = new line\n\t\t\tthis.addNewLine();\n\t\t}\n\t\t// Plain Enter (char code 13 for CR) - only CR submits, LF adds new line\n\t\telse if (data.charCodeAt(0) === 13 && data.length === 1) {\n\t\t\t// If submit is disabled, do nothing\n\t\t\tif (this.disableSubmit) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Get text and substitute paste markers with actual content\n\t\t\tlet result = this.state.lines.join(\"\\n\").trim();\n\n\t\t\t// Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content\n\t\t\tfor (const [pasteId, pasteContent] of this.pastes) {\n\t\t\t\t// Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]\n\t\t\t\tconst markerRegex = new RegExp(`\\\\[paste #${pasteId}( (\\\\+\\\\d+ lines|\\\\d+ chars))?\\\\]`, \"g\");\n\t\t\t\tresult = result.replace(markerRegex, pasteContent);\n\t\t\t}\n\n\t\t\t// Reset editor and clear pastes\n\t\t\tthis.state = {\n\t\t\t\tlines: [\"\"],\n\t\t\t\tcursorLine: 0,\n\t\t\t\tcursorCol: 0,\n\t\t\t};\n\t\t\tthis.pastes.clear();\n\t\t\tthis.pasteCounter = 0;\n\n\t\t\t// Notify that editor is now empty\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(\"\");\n\t\t\t}\n\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(result);\n\t\t\t}\n\t\t}\n\t\t// Backspace\n\t\telse if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {\n\t\t\tthis.handleBackspace();\n\t\t}\n\t\t// Line navigation shortcuts (Home/End keys)\n\t\telse if (data === \"\\x1b[H\" || data === \"\\x1b[1~\" || data === \"\\x1b[7~\") {\n\t\t\t// Home key\n\t\t\tthis.moveToLineStart();\n\t\t} else if (data === \"\\x1b[F\" || data === \"\\x1b[4~\" || data === \"\\x1b[8~\") {\n\t\t\t// End key\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// Forward delete (Fn+Backspace or Delete key)\n\t\telse if (data === \"\\x1b[3~\") {\n\t\t\t// Delete key\n\t\t\tthis.handleForwardDelete();\n\t\t}\n\t\t// Arrow keys\n\t\telse if (data === \"\\x1b[A\") {\n\t\t\t// Up\n\t\t\tthis.moveCursor(-1, 0);\n\t\t} else if (data === \"\\x1b[B\") {\n\t\t\t// Down\n\t\t\tthis.moveCursor(1, 0);\n\t\t} else if (data === \"\\x1b[C\") {\n\t\t\t// Right\n\t\t\tthis.moveCursor(0, 1);\n\t\t} else if (data === \"\\x1b[D\") {\n\t\t\t// Left\n\t\t\tthis.moveCursor(0, -1);\n\t\t}\n\t\t// Regular characters (printable characters and unicode, but not control characters)\n\t\telse if (data.charCodeAt(0) >= 32) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate layoutText(contentWidth: number): LayoutLine[] {\n\t\tconst layoutLines: LayoutLine[] = [];\n\n\t\tif (this.state.lines.length === 0 || (this.state.lines.length === 1 && this.state.lines[0] === \"\")) {\n\t\t\t// Empty editor\n\t\t\tlayoutLines.push({\n\t\t\t\ttext: \"\",\n\t\t\t\thasCursor: true,\n\t\t\t\tcursorPos: 0,\n\t\t\t});\n\t\t\treturn layoutLines;\n\t\t}\n\n\t\t// Process each logical line\n\t\tfor (let i = 0; i < this.state.lines.length; i++) {\n\t\t\tconst line = this.state.lines[i] || \"\";\n\t\t\tconst isCurrentLine = i === this.state.cursorLine;\n\t\t\tconst maxLineLength = contentWidth;\n\n\t\t\tif (line.length <= maxLineLength) {\n\t\t\t\t// Line fits in one layout line\n\t\t\t\tif (isCurrentLine) {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\tcursorPos: this.state.cursorCol,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Line needs wrapping\n\t\t\t\tconst chunks = [];\n\t\t\t\tfor (let pos = 0; pos < line.length; pos += maxLineLength) {\n\t\t\t\t\tchunks.push(line.slice(pos, pos + maxLineLength));\n\t\t\t\t}\n\n\t\t\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {\n\t\t\t\t\tconst chunk = chunks[chunkIndex];\n\t\t\t\t\tif (!chunk) continue;\n\n\t\t\t\t\tconst chunkStart = chunkIndex * maxLineLength;\n\t\t\t\t\tconst chunkEnd = chunkStart + chunk.length;\n\t\t\t\t\tconst cursorPos = this.state.cursorCol;\n\t\t\t\t\tconst hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;\n\n\t\t\t\t\tif (hasCursorInChunk) {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\t\tcursorPos: cursorPos - chunkStart,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn layoutLines;\n\t}\n\n\tgetText(): string {\n\t\treturn this.state.lines.join(\"\\n\");\n\t}\n\n\tsetText(text: string): void {\n\t\t// Split text into lines, handling different line endings\n\t\tconst lines = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n\t\t// Ensure at least one empty line\n\t\tthis.state.lines = lines.length === 0 ? [\"\"] : lines;\n\n\t\t// Reset cursor to end of text\n\t\tthis.state.cursorLine = this.state.lines.length - 1;\n\t\tthis.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\t// All the editor methods from before...\n\tprivate insertCharacter(char: string): void {\n\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = line.slice(0, this.state.cursorCol);\n\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\tthis.state.lines[this.state.cursorLine] = before + char + after;\n\t\tthis.state.cursorCol += char.length; // Fix: increment by the length of the inserted string\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Check if we should trigger or update autocomplete\n\t\tif (!this.isAutocompleting) {\n\t\t\t// Auto-trigger for \"/\" at the start of a line (slash commands)\n\t\t\tif (char === \"/\" && this.isAtStartOfMessage()) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t\t// Also auto-trigger when typing letters in a slash command context\n\t\t\telse if (/[a-zA-Z0-9]/.test(char)) {\n\t\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\t\t// Check if we're in a slash command with a space (i.e., typing arguments)\n\t\t\t\tif (textBeforeCursor.startsWith(\"/\") && textBeforeCursor.includes(\" \")) {\n\t\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Convert tabs to spaces (4 spaces per tab)\n\t\tconst tabExpandedText = cleanText.replace(/\\t/g, \" \");\n\n\t\t// Filter out non-printable characters except newlines\n\t\tconst filteredText = tabExpandedText\n\t\t\t.split(\"\")\n\t\t\t.filter((char) => char === \"\\n\" || char.charCodeAt(0) >= 32)\n\t\t\t.join(\"\");\n\n\t\t// Split into lines\n\t\tconst pastedLines = filteredText.split(\"\\n\");\n\n\t\t// Check if this is a large paste (> 10 lines or > 1000 characters)\n\t\tconst totalChars = filteredText.length;\n\t\tif (pastedLines.length > 10 || totalChars > 1000) {\n\t\t\t// Store the paste and insert a marker\n\t\t\tthis.pasteCounter++;\n\t\t\tconst pasteId = this.pasteCounter;\n\t\t\tthis.pastes.set(pasteId, filteredText);\n\n\t\t\t// Insert marker like \"[paste #1 +123 lines]\" or \"[paste #1 1234 chars]\"\n\t\t\tconst marker =\n\t\t\t\tpastedLines.length > 10\n\t\t\t\t\t? `[paste #${pasteId} +${pastedLines.length} lines]`\n\t\t\t\t\t: `[paste #${pasteId} ${totalChars} chars]`;\n\t\t\tfor (const char of marker) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (pastedLines.length === 1) {\n\t\t\t// Single line - just insert each character\n\t\t\tconst text = pastedLines[0] || \"\";\n\t\t\tfor (const char of text) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Multi-line paste - be very careful with array manipulation\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\tconst afterCursor = currentLine.slice(this.state.cursorCol);\n\n\t\t// Build the new lines array step by step\n\t\tconst newLines: string[] = [];\n\n\t\t// Add all lines before current line\n\t\tfor (let i = 0; i < this.state.cursorLine; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Add the first pasted line merged with before cursor text\n\t\tnewLines.push(beforeCursor + (pastedLines[0] || \"\"));\n\n\t\t// Add all middle pasted lines\n\t\tfor (let i = 1; i < pastedLines.length - 1; i++) {\n\t\t\tnewLines.push(pastedLines[i] || \"\");\n\t\t}\n\n\t\t// Add the last pasted line with after cursor text\n\t\tnewLines.push((pastedLines[pastedLines.length - 1] || \"\") + afterCursor);\n\n\t\t// Add all lines after current line\n\t\tfor (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Replace the entire lines array\n\t\tthis.state.lines = newLines;\n\n\t\t// Update cursor position to end of pasted content\n\t\tthis.state.cursorLine += pastedLines.length - 1;\n\t\tthis.state.cursorCol = (pastedLines[pastedLines.length - 1] || \"\").length;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate addNewLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\tconst after = currentLine.slice(this.state.cursorCol);\n\n\t\t// Split current line\n\t\tthis.state.lines[this.state.cursorLine] = before;\n\t\tthis.state.lines.splice(this.state.cursorLine + 1, 0, after);\n\n\t\t// Move cursor to start of new line\n\t\tthis.state.cursorLine++;\n\t\tthis.state.cursorCol = 0;\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete character in current line\n\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t\tconst before = line.slice(0, this.state.cursorCol - 1);\n\t\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t\tthis.state.cursorCol--;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// Merge with previous line\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Update autocomplete after backspace\n\t\tif (this.isAutocompleting) {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate moveToLineStart(): void {\n\t\tthis.state.cursorCol = 0;\n\t}\n\n\tprivate moveToLineEnd(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tthis.state.cursorCol = currentLine.length;\n\t}\n\n\tprivate deleteToStartOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete from start of line up to cursor\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = 0;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// At start of line - merge with previous line\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteToEndOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete from cursor to end of line\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t// If at start of line, behave like backspace at column 0 (merge with previous line)\n\t\tif (this.state.cursorCol === 0) {\n\t\t\tif (this.state.cursorLine > 0) {\n\t\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\t\tthis.state.cursorLine--;\n\t\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t\t}\n\t\t} else {\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t\tconst isWhitespace = (char: string): boolean => /\\s/.test(char);\n\t\t\tconst isPunctuation = (char: string): boolean => {\n\t\t\t\t// Treat obvious code punctuation as boundaries\n\t\t\t\treturn /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/.test(char);\n\t\t\t};\n\n\t\t\tlet deleteFrom = this.state.cursorCol;\n\t\t\tconst lastChar = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\n\t\t\t// If immediately on whitespace or punctuation, delete that single boundary char\n\t\t\tif (isWhitespace(lastChar) || isPunctuation(lastChar)) {\n\t\t\t\tdeleteFrom -= 1;\n\t\t\t} else {\n\t\t\t\t// Otherwise, delete a run of non-boundary characters (the \"word\")\n\t\t\t\twhile (deleteFrom > 0) {\n\t\t\t\t\tconst ch = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\t\t\t\t\tif (isWhitespace(ch) || isPunctuation(ch)) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdeleteFrom -= 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.state.lines[this.state.cursorLine] =\n\t\t\t\tcurrentLine.slice(0, deleteFrom) + currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = deleteFrom;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete character at cursor position (forward delete)\n\t\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\t\tconst after = currentLine.slice(this.state.cursorCol + 1);\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate moveCursor(deltaLine: number, deltaCol: number): void {\n\t\tif (deltaLine !== 0) {\n\t\t\tconst newLine = this.state.cursorLine + deltaLine;\n\t\t\tif (newLine >= 0 && newLine < this.state.lines.length) {\n\t\t\t\tthis.state.cursorLine = newLine;\n\t\t\t\t// Clamp cursor column to new line length\n\t\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tthis.state.cursorCol = Math.min(this.state.cursorCol, line.length);\n\t\t\t}\n\t\t}\n\n\t\tif (deltaCol !== 0) {\n\t\t\t// Move column\n\t\t\tconst newCol = this.state.cursorCol + deltaCol;\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst maxCol = currentLine.length;\n\t\t\tthis.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));\n\t\t}\n\t}\n\n\t// Helper method to check if cursor is at start of message (for slash command detection)\n\tprivate isAtStartOfMessage(): boolean {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// At start if line is empty, only contains whitespace, or is just \"/\"\n\t\treturn beforeCursor.trim() === \"\" || beforeCursor.trim() === \"/\";\n\t}\n\n\t// Autocomplete methods\n\tprivate tryTriggerAutocomplete(explicitTab: boolean = false): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if we should trigger file completion on Tab\n\t\tif (explicitTab) {\n\t\t\tconst provider = this.autocompleteProvider as CombinedAutocompleteProvider;\n\t\t\tconst shouldTrigger =\n\t\t\t\t!provider.shouldTriggerFileCompletion ||\n\t\t\t\tprovider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol);\n\t\t\tif (!shouldTrigger) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleTabCompletion(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// Check if we're in a slash command context\n\t\tif (beforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\tthis.handleSlashCommandCompletion();\n\t\t} else {\n\t\t\tthis.forceFileAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleSlashCommandCompletion(): void {\n\t\t// For now, fall back to regular autocomplete (slash commands)\n\t\t// This can be extended later to handle command-specific argument completion\n\t\tthis.tryTriggerAutocomplete(true);\n\t}\n\n\tprivate forceFileAutocomplete(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if provider has the force method\n\t\tconst provider = this.autocompleteProvider as any;\n\t\tif (!provider.getForceFileSuggestions) {\n\t\t\tthis.tryTriggerAutocomplete(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggestions = provider.getForceFileSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate cancelAutocomplete(): void {\n\t\tthis.isAutocompleting = false;\n\t\tthis.autocompleteList = undefined as any;\n\t\tthis.autocompletePrefix = \"\";\n\t}\n\n\tpublic isShowingAutocomplete(): boolean {\n\t\treturn this.isAutocompleting;\n\t}\n\n\tprivate updateAutocomplete(): void {\n\t\tif (!this.isAutocompleting || !this.autocompleteProvider) return;\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tif (this.autocompleteList) {\n\t\t\t\t// Update the existing list with new items\n\t\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\t}\n\t\t} else {\n\t\t\t// No more matches, cancel autocomplete\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/components/editor.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport type { AutocompleteProvider, CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { Component } from \"../tui.js\";\nimport { SelectList } from \"./select-list.js\";\n\ninterface EditorState {\n\tlines: string[];\n\tcursorLine: number;\n\tcursorCol: number;\n}\n\ninterface LayoutLine {\n\ttext: string;\n\thasCursor: boolean;\n\tcursorPos?: number;\n}\n\nexport interface TextEditorConfig {\n\t// Configuration options for text editor (none currently)\n}\n\nexport class Editor implements Component {\n\tprivate state: EditorState = {\n\t\tlines: [\"\"],\n\t\tcursorLine: 0,\n\t\tcursorCol: 0,\n\t};\n\n\tprivate config: TextEditorConfig = {};\n\n\t// Border color (can be changed dynamically)\n\tpublic borderColor: (str: string) => string = chalk.gray;\n\n\t// Autocomplete support\n\tprivate autocompleteProvider?: AutocompleteProvider;\n\tprivate autocompleteList?: SelectList;\n\tprivate isAutocompleting: boolean = false;\n\tprivate autocompletePrefix: string = \"\";\n\n\t// Paste tracking for large pastes\n\tprivate pastes: Map<number, string> = new Map();\n\tprivate pasteCounter: number = 0;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tpublic onSubmit?: (text: string) => void;\n\tpublic onChange?: (text: string) => void;\n\tpublic disableSubmit: boolean = false;\n\n\tconstructor(config?: TextEditorConfig) {\n\t\tif (config) {\n\t\t\tthis.config = { ...this.config, ...config };\n\t\t}\n\t}\n\n\tconfigure(config: Partial<TextEditorConfig>): void {\n\t\tthis.config = { ...this.config, ...config };\n\t}\n\n\tsetAutocompleteProvider(provider: AutocompleteProvider): void {\n\t\tthis.autocompleteProvider = provider;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst horizontal = this.borderColor(\"─\");\n\n\t\t// Layout the text - use full width\n\t\tconst layoutLines = this.layoutText(width);\n\n\t\tconst result: string[] = [];\n\n\t\t// Render top border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Render each layout line\n\t\tfor (const layoutLine of layoutLines) {\n\t\t\tlet displayText = layoutLine.text;\n\t\t\tlet visibleLength = layoutLine.text.length;\n\n\t\t\t// Add cursor if this line has it\n\t\t\tif (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {\n\t\t\t\tconst before = displayText.slice(0, layoutLine.cursorPos);\n\t\t\t\tconst after = displayText.slice(layoutLine.cursorPos);\n\n\t\t\t\tif (after.length > 0) {\n\t\t\t\t\t// Cursor is on a character - replace it with highlighted version\n\t\t\t\t\tconst cursor = `\\x1b[7m${after[0]}\\x1b[0m`;\n\t\t\t\t\tconst restAfter = after.slice(1);\n\t\t\t\t\tdisplayText = before + cursor + restAfter;\n\t\t\t\t\t// visibleLength stays the same - we're replacing, not adding\n\t\t\t\t} else {\n\t\t\t\t\t// Cursor is at the end - check if we have room for the space\n\t\t\t\t\tif (layoutLine.text.length < width) {\n\t\t\t\t\t\t// We have room - add highlighted space\n\t\t\t\t\t\tconst cursor = \"\\x1b[7m \\x1b[0m\";\n\t\t\t\t\t\tdisplayText = before + cursor;\n\t\t\t\t\t\t// visibleLength increases by 1 - we're adding a space\n\t\t\t\t\t\tvisibleLength = layoutLine.text.length + 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Line is at full width - use reverse video on last character if possible\n\t\t\t\t\t\t// or just show cursor at the end without adding space\n\t\t\t\t\t\tif (before.length > 0) {\n\t\t\t\t\t\t\tconst lastChar = before[before.length - 1];\n\t\t\t\t\t\t\tconst cursor = `\\x1b[7m${lastChar}\\x1b[0m`;\n\t\t\t\t\t\t\tdisplayText = before.slice(0, -1) + cursor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// visibleLength stays the same\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate padding based on actual visible length\n\t\t\tconst padding = \" \".repeat(Math.max(0, width - visibleLength));\n\n\t\t\t// Render the line (no side borders, just horizontal lines above and below)\n\t\t\tresult.push(displayText + padding);\n\t\t}\n\n\t\t// Render bottom border\n\t\tresult.push(horizontal.repeat(width));\n\n\t\t// Add autocomplete list if active\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\tconst autocompleteResult = this.autocompleteList.render(width);\n\t\t\tresult.push(...autocompleteResult);\n\t\t}\n\n\t\treturn result;\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\t// Remove the start marker and keep the rest\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// Append data to buffer first (end marker could be split across chunks)\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\t// Check if the accumulated buffer contains the end marker\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract content before the end marker\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// Process any remaining data after the end marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t// Still accumulating, wait for more data\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Handle special key combinations first\n\n\t\t// Ctrl+C - Exit (let parent handle this)\n\t\tif (data.charCodeAt(0) === 3) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle autocomplete special keys first (but don't block other input)\n\t\tif (this.isAutocompleting && this.autocompleteList) {\n\t\t\t// Escape - cancel autocomplete\n\t\t\tif (data === \"\\x1b\") {\n\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Let the autocomplete list handle navigation and selection\n\t\t\telse if (data === \"\\x1b[A\" || data === \"\\x1b[B\" || data === \"\\r\" || data === \"\\t\") {\n\t\t\t\t// Only pass arrow keys to the list, not Enter/Tab (we handle those directly)\n\t\t\t\tif (data === \"\\x1b[A\" || data === \"\\x1b[B\") {\n\t\t\t\t\tthis.autocompleteList.handleInput(data);\n\t\t\t\t}\n\n\t\t\t\t// If Tab was pressed, always apply the selection\n\t\t\t\tif (data === \"\\t\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// If Enter was pressed on a slash command, cancel autocomplete and let it submit\n\t\t\t\tif (data === \"\\r\" && this.autocompletePrefix.startsWith(\"/\")) {\n\t\t\t\t\tthis.cancelAutocomplete();\n\t\t\t\t\t// Don't return - fall through to submission logic\n\t\t\t\t}\n\t\t\t\t// If Enter was pressed on a file path, apply completion\n\t\t\t\telse if (data === \"\\r\") {\n\t\t\t\t\tconst selected = this.autocompleteList.getSelectedItem();\n\t\t\t\t\tif (selected && this.autocompleteProvider) {\n\t\t\t\t\t\tconst result = this.autocompleteProvider.applyCompletion(\n\t\t\t\t\t\t\tthis.state.lines,\n\t\t\t\t\t\t\tthis.state.cursorLine,\n\t\t\t\t\t\t\tthis.state.cursorCol,\n\t\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\tthis.autocompletePrefix,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.state.lines = result.lines;\n\t\t\t\t\t\tthis.state.cursorLine = result.cursorLine;\n\t\t\t\t\t\tthis.state.cursorCol = result.cursorCol;\n\n\t\t\t\t\t\tthis.cancelAutocomplete();\n\n\t\t\t\t\t\tif (this.onChange) {\n\t\t\t\t\t\t\tthis.onChange(this.getText());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// For other keys (like regular typing), DON'T return here\n\t\t\t// Let them fall through to normal character handling\n\t\t}\n\n\t\t// Tab key - context-aware completion (but not when already autocompleting)\n\t\tif (data === \"\\t\" && !this.isAutocompleting) {\n\t\t\tthis.handleTabCompletion();\n\t\t\treturn;\n\t\t}\n\n\t\t// Continue with rest of input handling\n\t\t// Ctrl+K - Delete to end of line\n\t\tif (data.charCodeAt(0) === 11) {\n\t\t\tthis.deleteToEndOfLine();\n\t\t}\n\t\t// Ctrl+U - Delete to start of line\n\t\telse if (data.charCodeAt(0) === 21) {\n\t\t\tthis.deleteToStartOfLine();\n\t\t}\n\t\t// Ctrl+W - Delete word backwards\n\t\telse if (data.charCodeAt(0) === 23) {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)\n\t\telse if (data === \"\\x1b\\x7f\") {\n\t\t\tthis.deleteWordBackwards();\n\t\t}\n\t\t// Ctrl+A - Move to start of line\n\t\telse if (data.charCodeAt(0) === 1) {\n\t\t\tthis.moveToLineStart();\n\t\t}\n\t\t// Ctrl+E - Move to end of line\n\t\telse if (data.charCodeAt(0) === 5) {\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// New line shortcuts (but not plain LF/CR which should be submit)\n\t\telse if (\n\t\t\t(data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers\n\t\t\tdata === \"\\x1b\\r\" || // Option+Enter in some terminals\n\t\t\tdata === \"\\x1b[13;2~\" || // Shift+Enter in some terminals\n\t\t\t(data.length > 1 && data.includes(\"\\x1b\") && data.includes(\"\\r\")) ||\n\t\t\t(data === \"\\n\" && data.length === 1) || // Shift+Enter from iTerm2 mapping\n\t\t\tdata === \"\\\\\\r\" // Shift+Enter in VS Code terminal\n\t\t) {\n\t\t\t// Modifier + Enter = new line\n\t\t\tthis.addNewLine();\n\t\t}\n\t\t// Plain Enter (char code 13 for CR) - only CR submits, LF adds new line\n\t\telse if (data.charCodeAt(0) === 13 && data.length === 1) {\n\t\t\t// If submit is disabled, do nothing\n\t\t\tif (this.disableSubmit) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Get text and substitute paste markers with actual content\n\t\t\tlet result = this.state.lines.join(\"\\n\").trim();\n\n\t\t\t// Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content\n\t\t\tfor (const [pasteId, pasteContent] of this.pastes) {\n\t\t\t\t// Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]\n\t\t\t\tconst markerRegex = new RegExp(`\\\\[paste #${pasteId}( (\\\\+\\\\d+ lines|\\\\d+ chars))?\\\\]`, \"g\");\n\t\t\t\tresult = result.replace(markerRegex, pasteContent);\n\t\t\t}\n\n\t\t\t// Reset editor and clear pastes\n\t\t\tthis.state = {\n\t\t\t\tlines: [\"\"],\n\t\t\t\tcursorLine: 0,\n\t\t\t\tcursorCol: 0,\n\t\t\t};\n\t\t\tthis.pastes.clear();\n\t\t\tthis.pasteCounter = 0;\n\n\t\t\t// Notify that editor is now empty\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(\"\");\n\t\t\t}\n\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(result);\n\t\t\t}\n\t\t}\n\t\t// Backspace\n\t\telse if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {\n\t\t\tthis.handleBackspace();\n\t\t}\n\t\t// Line navigation shortcuts (Home/End keys)\n\t\telse if (data === \"\\x1b[H\" || data === \"\\x1b[1~\" || data === \"\\x1b[7~\") {\n\t\t\t// Home key\n\t\t\tthis.moveToLineStart();\n\t\t} else if (data === \"\\x1b[F\" || data === \"\\x1b[4~\" || data === \"\\x1b[8~\") {\n\t\t\t// End key\n\t\t\tthis.moveToLineEnd();\n\t\t}\n\t\t// Forward delete (Fn+Backspace or Delete key)\n\t\telse if (data === \"\\x1b[3~\") {\n\t\t\t// Delete key\n\t\t\tthis.handleForwardDelete();\n\t\t}\n\t\t// Arrow keys\n\t\telse if (data === \"\\x1b[A\") {\n\t\t\t// Up\n\t\t\tthis.moveCursor(-1, 0);\n\t\t} else if (data === \"\\x1b[B\") {\n\t\t\t// Down\n\t\t\tthis.moveCursor(1, 0);\n\t\t} else if (data === \"\\x1b[C\") {\n\t\t\t// Right\n\t\t\tthis.moveCursor(0, 1);\n\t\t} else if (data === \"\\x1b[D\") {\n\t\t\t// Left\n\t\t\tthis.moveCursor(0, -1);\n\t\t}\n\t\t// Regular characters (printable characters and unicode, but not control characters)\n\t\telse if (data.charCodeAt(0) >= 32) {\n\t\t\tthis.insertCharacter(data);\n\t\t}\n\t}\n\n\tprivate layoutText(contentWidth: number): LayoutLine[] {\n\t\tconst layoutLines: LayoutLine[] = [];\n\n\t\tif (this.state.lines.length === 0 || (this.state.lines.length === 1 && this.state.lines[0] === \"\")) {\n\t\t\t// Empty editor\n\t\t\tlayoutLines.push({\n\t\t\t\ttext: \"\",\n\t\t\t\thasCursor: true,\n\t\t\t\tcursorPos: 0,\n\t\t\t});\n\t\t\treturn layoutLines;\n\t\t}\n\n\t\t// Process each logical line\n\t\tfor (let i = 0; i < this.state.lines.length; i++) {\n\t\t\tconst line = this.state.lines[i] || \"\";\n\t\t\tconst isCurrentLine = i === this.state.cursorLine;\n\t\t\tconst maxLineLength = contentWidth;\n\n\t\t\tif (line.length <= maxLineLength) {\n\t\t\t\t// Line fits in one layout line\n\t\t\t\tif (isCurrentLine) {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\tcursorPos: this.state.cursorCol,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\ttext: line,\n\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Line needs wrapping\n\t\t\t\tconst chunks = [];\n\t\t\t\tfor (let pos = 0; pos < line.length; pos += maxLineLength) {\n\t\t\t\t\tchunks.push(line.slice(pos, pos + maxLineLength));\n\t\t\t\t}\n\n\t\t\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {\n\t\t\t\t\tconst chunk = chunks[chunkIndex];\n\t\t\t\t\tif (!chunk) continue;\n\n\t\t\t\t\tconst chunkStart = chunkIndex * maxLineLength;\n\t\t\t\t\tconst chunkEnd = chunkStart + chunk.length;\n\t\t\t\t\tconst cursorPos = this.state.cursorCol;\n\t\t\t\t\tconst hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;\n\n\t\t\t\t\tif (hasCursorInChunk) {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: true,\n\t\t\t\t\t\t\tcursorPos: cursorPos - chunkStart,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlayoutLines.push({\n\t\t\t\t\t\t\ttext: chunk,\n\t\t\t\t\t\t\thasCursor: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn layoutLines;\n\t}\n\n\tgetText(): string {\n\t\treturn this.state.lines.join(\"\\n\");\n\t}\n\n\tsetText(text: string): void {\n\t\t// Split text into lines, handling different line endings\n\t\tconst lines = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n\t\t// Ensure at least one empty line\n\t\tthis.state.lines = lines.length === 0 ? [\"\"] : lines;\n\n\t\t// Reset cursor to end of text\n\t\tthis.state.cursorLine = this.state.lines.length - 1;\n\t\tthis.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\t// All the editor methods from before...\n\tprivate insertCharacter(char: string): void {\n\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = line.slice(0, this.state.cursorCol);\n\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\tthis.state.lines[this.state.cursorLine] = before + char + after;\n\t\tthis.state.cursorCol += char.length; // Fix: increment by the length of the inserted string\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Check if we should trigger or update autocomplete\n\t\tif (!this.isAutocompleting) {\n\t\t\t// Auto-trigger for \"/\" at the start of a line (slash commands)\n\t\t\tif (char === \"/\" && this.isAtStartOfMessage()) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t\t// Also auto-trigger when typing letters in a slash command context\n\t\t\telse if (/[a-zA-Z0-9]/.test(char)) {\n\t\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\t\t// Check if we're in a slash command (with or without space for arguments)\n\t\t\t\tif (textBeforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.updateAutocomplete();\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Convert tabs to spaces (4 spaces per tab)\n\t\tconst tabExpandedText = cleanText.replace(/\\t/g, \" \");\n\n\t\t// Filter out non-printable characters except newlines\n\t\tconst filteredText = tabExpandedText\n\t\t\t.split(\"\")\n\t\t\t.filter((char) => char === \"\\n\" || char.charCodeAt(0) >= 32)\n\t\t\t.join(\"\");\n\n\t\t// Split into lines\n\t\tconst pastedLines = filteredText.split(\"\\n\");\n\n\t\t// Check if this is a large paste (> 10 lines or > 1000 characters)\n\t\tconst totalChars = filteredText.length;\n\t\tif (pastedLines.length > 10 || totalChars > 1000) {\n\t\t\t// Store the paste and insert a marker\n\t\t\tthis.pasteCounter++;\n\t\t\tconst pasteId = this.pasteCounter;\n\t\t\tthis.pastes.set(pasteId, filteredText);\n\n\t\t\t// Insert marker like \"[paste #1 +123 lines]\" or \"[paste #1 1234 chars]\"\n\t\t\tconst marker =\n\t\t\t\tpastedLines.length > 10\n\t\t\t\t\t? `[paste #${pasteId} +${pastedLines.length} lines]`\n\t\t\t\t\t: `[paste #${pasteId} ${totalChars} chars]`;\n\t\t\tfor (const char of marker) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (pastedLines.length === 1) {\n\t\t\t// Single line - just insert each character\n\t\t\tconst text = pastedLines[0] || \"\";\n\t\t\tfor (const char of text) {\n\t\t\t\tthis.insertCharacter(char);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Multi-line paste - be very careful with array manipulation\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\tconst afterCursor = currentLine.slice(this.state.cursorCol);\n\n\t\t// Build the new lines array step by step\n\t\tconst newLines: string[] = [];\n\n\t\t// Add all lines before current line\n\t\tfor (let i = 0; i < this.state.cursorLine; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Add the first pasted line merged with before cursor text\n\t\tnewLines.push(beforeCursor + (pastedLines[0] || \"\"));\n\n\t\t// Add all middle pasted lines\n\t\tfor (let i = 1; i < pastedLines.length - 1; i++) {\n\t\t\tnewLines.push(pastedLines[i] || \"\");\n\t\t}\n\n\t\t// Add the last pasted line with after cursor text\n\t\tnewLines.push((pastedLines[pastedLines.length - 1] || \"\") + afterCursor);\n\n\t\t// Add all lines after current line\n\t\tfor (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {\n\t\t\tnewLines.push(this.state.lines[i] || \"\");\n\t\t}\n\n\t\t// Replace the entire lines array\n\t\tthis.state.lines = newLines;\n\n\t\t// Update cursor position to end of pasted content\n\t\tthis.state.cursorLine += pastedLines.length - 1;\n\t\tthis.state.cursorCol = (pastedLines[pastedLines.length - 1] || \"\").length;\n\n\t\t// Notify of change\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate addNewLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\tconst after = currentLine.slice(this.state.cursorCol);\n\n\t\t// Split current line\n\t\tthis.state.lines[this.state.cursorLine] = before;\n\t\tthis.state.lines.splice(this.state.cursorLine + 1, 0, after);\n\n\t\t// Move cursor to start of new line\n\t\tthis.state.cursorLine++;\n\t\tthis.state.cursorCol = 0;\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleBackspace(): void {\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete character in current line\n\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t\tconst before = line.slice(0, this.state.cursorCol - 1);\n\t\t\tconst after = line.slice(this.state.cursorCol);\n\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t\tthis.state.cursorCol--;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// Merge with previous line\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\n\t\t// Update or re-trigger autocomplete after backspace\n\t\tif (this.isAutocompleting) {\n\t\t\tthis.updateAutocomplete();\n\t\t} else {\n\t\t\t// If autocomplete was cancelled (no matches), re-trigger if we're in slash command context\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\t\t\tif (textBeforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\t\tthis.tryTriggerAutocomplete();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate moveToLineStart(): void {\n\t\tthis.state.cursorCol = 0;\n\t}\n\n\tprivate moveToLineEnd(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tthis.state.cursorCol = currentLine.length;\n\t}\n\n\tprivate deleteToStartOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol > 0) {\n\t\t\t// Delete from start of line up to cursor\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = 0;\n\t\t} else if (this.state.cursorLine > 0) {\n\t\t\t// At start of line - merge with previous line\n\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\tthis.state.cursorLine--;\n\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteToEndOfLine(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete from cursor to end of line\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate deleteWordBackwards(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\t// If at start of line, behave like backspace at column 0 (merge with previous line)\n\t\tif (this.state.cursorCol === 0) {\n\t\t\tif (this.state.cursorLine > 0) {\n\t\t\t\tconst previousLine = this.state.lines[this.state.cursorLine - 1] || \"\";\n\t\t\t\tthis.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;\n\t\t\t\tthis.state.lines.splice(this.state.cursorLine, 1);\n\t\t\t\tthis.state.cursorLine--;\n\t\t\t\tthis.state.cursorCol = previousLine.length;\n\t\t\t}\n\t\t} else {\n\t\t\tconst textBeforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t\tconst isWhitespace = (char: string): boolean => /\\s/.test(char);\n\t\t\tconst isPunctuation = (char: string): boolean => {\n\t\t\t\t// Treat obvious code punctuation as boundaries\n\t\t\t\treturn /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/.test(char);\n\t\t\t};\n\n\t\t\tlet deleteFrom = this.state.cursorCol;\n\t\t\tconst lastChar = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\n\t\t\t// If immediately on whitespace or punctuation, delete that single boundary char\n\t\t\tif (isWhitespace(lastChar) || isPunctuation(lastChar)) {\n\t\t\t\tdeleteFrom -= 1;\n\t\t\t} else {\n\t\t\t\t// Otherwise, delete a run of non-boundary characters (the \"word\")\n\t\t\t\twhile (deleteFrom > 0) {\n\t\t\t\t\tconst ch = textBeforeCursor[deleteFrom - 1] ?? \"\";\n\t\t\t\t\tif (isWhitespace(ch) || isPunctuation(ch)) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdeleteFrom -= 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.state.lines[this.state.cursorLine] =\n\t\t\t\tcurrentLine.slice(0, deleteFrom) + currentLine.slice(this.state.cursorCol);\n\t\t\tthis.state.cursorCol = deleteFrom;\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate handleForwardDelete(): void {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\n\t\tif (this.state.cursorCol < currentLine.length) {\n\t\t\t// Delete character at cursor position (forward delete)\n\t\t\tconst before = currentLine.slice(0, this.state.cursorCol);\n\t\t\tconst after = currentLine.slice(this.state.cursorCol + 1);\n\t\t\tthis.state.lines[this.state.cursorLine] = before + after;\n\t\t} else if (this.state.cursorLine < this.state.lines.length - 1) {\n\t\t\t// At end of line - merge with next line\n\t\t\tconst nextLine = this.state.lines[this.state.cursorLine + 1] || \"\";\n\t\t\tthis.state.lines[this.state.cursorLine] = currentLine + nextLine;\n\t\t\tthis.state.lines.splice(this.state.cursorLine + 1, 1);\n\t\t}\n\n\t\tif (this.onChange) {\n\t\t\tthis.onChange(this.getText());\n\t\t}\n\t}\n\n\tprivate moveCursor(deltaLine: number, deltaCol: number): void {\n\t\tif (deltaLine !== 0) {\n\t\t\tconst newLine = this.state.cursorLine + deltaLine;\n\t\t\tif (newLine >= 0 && newLine < this.state.lines.length) {\n\t\t\t\tthis.state.cursorLine = newLine;\n\t\t\t\t// Clamp cursor column to new line length\n\t\t\t\tconst line = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\t\tthis.state.cursorCol = Math.min(this.state.cursorCol, line.length);\n\t\t\t}\n\t\t}\n\n\t\tif (deltaCol !== 0) {\n\t\t\t// Move column\n\t\t\tconst newCol = this.state.cursorCol + deltaCol;\n\t\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\t\tconst maxCol = currentLine.length;\n\t\t\tthis.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));\n\t\t}\n\t}\n\n\t// Helper method to check if cursor is at start of message (for slash command detection)\n\tprivate isAtStartOfMessage(): boolean {\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// At start if line is empty, only contains whitespace, or is just \"/\"\n\t\treturn beforeCursor.trim() === \"\" || beforeCursor.trim() === \"/\";\n\t}\n\n\t// Autocomplete methods\n\tprivate tryTriggerAutocomplete(explicitTab: boolean = false): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if we should trigger file completion on Tab\n\t\tif (explicitTab) {\n\t\t\tconst provider = this.autocompleteProvider as CombinedAutocompleteProvider;\n\t\t\tconst shouldTrigger =\n\t\t\t\t!provider.shouldTriggerFileCompletion ||\n\t\t\t\tprovider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol);\n\t\t\tif (!shouldTrigger) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleTabCompletion(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\tconst currentLine = this.state.lines[this.state.cursorLine] || \"\";\n\t\tconst beforeCursor = currentLine.slice(0, this.state.cursorCol);\n\n\t\t// Check if we're in a slash command context\n\t\tif (beforeCursor.trimStart().startsWith(\"/\")) {\n\t\t\tthis.handleSlashCommandCompletion();\n\t\t} else {\n\t\t\tthis.forceFileAutocomplete();\n\t\t}\n\t}\n\n\tprivate handleSlashCommandCompletion(): void {\n\t\t// For now, fall back to regular autocomplete (slash commands)\n\t\t// This can be extended later to handle command-specific argument completion\n\t\tthis.tryTriggerAutocomplete(true);\n\t}\n\n\tprivate forceFileAutocomplete(): void {\n\t\tif (!this.autocompleteProvider) return;\n\n\t\t// Check if provider has the force method\n\t\tconst provider = this.autocompleteProvider as any;\n\t\tif (!provider.getForceFileSuggestions) {\n\t\t\tthis.tryTriggerAutocomplete(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggestions = provider.getForceFileSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\tthis.isAutocompleting = true;\n\t\t} else {\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n\n\tprivate cancelAutocomplete(): void {\n\t\tthis.isAutocompleting = false;\n\t\tthis.autocompleteList = undefined as any;\n\t\tthis.autocompletePrefix = \"\";\n\t}\n\n\tpublic isShowingAutocomplete(): boolean {\n\t\treturn this.isAutocompleting;\n\t}\n\n\tprivate updateAutocomplete(): void {\n\t\tif (!this.isAutocompleting || !this.autocompleteProvider) return;\n\n\t\tconst suggestions = this.autocompleteProvider.getSuggestions(\n\t\t\tthis.state.lines,\n\t\t\tthis.state.cursorLine,\n\t\t\tthis.state.cursorCol,\n\t\t);\n\n\t\tif (suggestions && suggestions.items.length > 0) {\n\t\t\tthis.autocompletePrefix = suggestions.prefix;\n\t\t\tif (this.autocompleteList) {\n\t\t\t\t// Update the existing list with new items\n\t\t\t\tthis.autocompleteList = new SelectList(suggestions.items, 5);\n\t\t\t}\n\t\t} else {\n\t\t\t// No more matches, cancel autocomplete\n\t\t\tthis.cancelAutocomplete();\n\t\t}\n\t}\n}\n"]}
package/dist/tui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IAC1C,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAEnC;IAED,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAKtC;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAM9B;CACD;AAED;;GAEG;AACH,qBAAa,GAAI,SAAQ,SAAS;IACjC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,QAAQ,EAAE,QAAQ,EAG7B;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAE1C;IAED,KAAK,IAAI,IAAI,CAOZ;IAED,IAAI,IAAI,IAAI,CAGX;IAED,aAAa,IAAI,IAAI,CAOpB;IAED,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;CAmIhB","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tlet lastChanged = -1;\n\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t\tlastChanged = i;\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\n\t\t// Render from first changed line to end, clearing each line before writing\n\t\t// This avoids the \\x1b[J clear-to-end which can cause flicker in xterm.js\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tbuffer += \"\\x1b[2K\"; // Clear current line\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\t// If we had more lines before, clear them and move cursor back\n\t\tif (this.previousLines.length > newLines.length) {\n\t\t\tconst extraLines = this.previousLines.length - newLines.length;\n\t\t\tfor (let i = newLines.length; i < this.previousLines.length; i++) {\n\t\t\t\tbuffer += \"\\r\\n\\x1b[2K\";\n\t\t\t}\n\t\t\t// Move cursor back to end of new content\n\t\t\tbuffer += `\\x1b[${extraLines}A`;\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IAC1C,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAEnC;IAED,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAKtC;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAM9B;CACD;AAED;;GAEG;AACH,qBAAa,GAAI,SAAQ,SAAS;IACjC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,QAAQ,EAAE,QAAQ,EAG7B;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAE1C;IAED,KAAK,IAAI,IAAI,CAOZ;IAED,IAAI,IAAI,IAAI,CAGX;IAED,aAAa,IAAI,IAAI,CAOpB;IAED,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;CAgIhB","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\n\t\t// Render from first changed line to end, clearing each line before writing\n\t\t// This avoids the \\x1b[J clear-to-end which can cause flicker in xterm.js\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tbuffer += \"\\x1b[2K\"; // Clear current line\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\t// If we had more lines before, clear them and move cursor back\n\t\tif (this.previousLines.length > newLines.length) {\n\t\t\tconst extraLines = this.previousLines.length - newLines.length;\n\t\t\tfor (let i = newLines.length; i < this.previousLines.length; i++) {\n\t\t\t\tbuffer += \"\\r\\n\\x1b[2K\";\n\t\t\t}\n\t\t\t// Move cursor back to end of new content\n\t\t\tbuffer += `\\x1b[${extraLines}A`;\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}
package/dist/tui.js CHANGED
@@ -112,7 +112,6 @@ export class TUI extends Container {
112
112
  }
113
113
  // Find first and last changed lines
114
114
  let firstChanged = -1;
115
- let lastChanged = -1;
116
115
  const maxLines = Math.max(newLines.length, this.previousLines.length);
117
116
  for (let i = 0; i < maxLines; i++) {
118
117
  const oldLine = i < this.previousLines.length ? this.previousLines[i] : "";
@@ -121,7 +120,6 @@ export class TUI extends Container {
121
120
  if (firstChanged === -1) {
122
121
  firstChanged = i;
123
122
  }
124
- lastChanged = i;
125
123
  }
126
124
  }
127
125
  // No changes
package/dist/tui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tlet lastChanged = -1;\n\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t\tlastChanged = i;\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\n\t\t// Render from first changed line to end, clearing each line before writing\n\t\t// This avoids the \\x1b[J clear-to-end which can cause flicker in xterm.js\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tbuffer += \"\\x1b[2K\"; // Clear current line\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\t// If we had more lines before, clear them and move cursor back\n\t\tif (this.previousLines.length > newLines.length) {\n\t\t\tconst extraLines = this.previousLines.length - newLines.length;\n\t\t\tfor (let i = newLines.length; i < this.previousLines.length; i++) {\n\t\t\t\tbuffer += \"\\r\\n\\x1b[2K\";\n\t\t\t}\n\t\t\t// Move cursor back to end of new content\n\t\t\tbuffer += `\\x1b[${extraLines}A`;\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\n\t\t// Render from first changed line to end, clearing each line before writing\n\t\t// This avoids the \\x1b[J clear-to-end which can cause flicker in xterm.js\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tbuffer += \"\\x1b[2K\"; // Clear current line\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\t// If we had more lines before, clear them and move cursor back\n\t\tif (this.previousLines.length > newLines.length) {\n\t\t\tconst extraLines = this.previousLines.length - newLines.length;\n\t\t\tfor (let i = newLines.length; i < this.previousLines.length; i++) {\n\t\t\t\tbuffer += \"\\r\\n\\x1b[2K\";\n\t\t\t}\n\t\t\t// Move cursor back to end of new content\n\t\t\tbuffer += `\\x1b[${extraLines}A`;\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-tui",
3
- "version": "0.7.26",
3
+ "version": "0.7.27",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",