* feat(tui): extract KillRing and UndoStack, add to Input
Extract kill ring and undo logic from Editor into reusable classes:
- KillRing: ring buffer with accumulation for consecutive kills
- UndoStack<S>: generic stack with clone-on-push semantics
Refactor Editor to use both classes. Add kill ring (kill/yank/
yank-pop), undo with coalescing, and deleteWordForward to Input.
* feat(tui): extract handleBackspace() and handleForwardDelete()
When moving up/down through lines of varying lengths, the editor now
remembers the original column position and restores it when reaching a
line long enough to accommodate it.
Example: cursor at column 10, move up to a shorter line (cursor clamps
to end), move up again to a longer line - cursor returns to column 10.
Implementation:
- Add preferredVisualCol instance property (nullable)
- Set it when moving to a shorter line during vertical navigation
- Clear it when arriving at a line that fits the preferred column
- Clear it on any horizontal movement or editing via setCursorCol()
- Detect line rewrap by checking if cursor is in middle of line
- When pressing right at end of prompt, set preferredVisualCol so
subsequent up/down navigation uses that column position
* fix(tui): blockquote multiline rendering and wrapping
Fix two bugs in blockquote rendering:
1. ANSI codes split incorrectly on newlines - when text tokens contain
newlines, applyDefaultStyle() wrapped the entire string including the
newline in ANSI codes. Any caller splitting by \n would break the codes.
Fixed by splitting text by newlines before styling in renderInlineTokens().
2. Wrapped blockquote lines lost the │ border - long blockquote lines
that wrapped to multiple lines only had the border on the first line.
Fixed by wrapping within the blockquote case and adding border to each line.
* test(tui): add test for inline formatting inside blockquotes
When cursor is on the first visual line and up is pressed, jump to
start of line instead of doing nothing. When cursor is on the last
visual line and down is pressed, jump to end of line instead of doing
nothing. This matches standard behavior in most native text inputs.
- Add isImageLine() to terminal-image.ts with single startsWith check based on detected terminal protocol
- Replace dual includes() checks in tui.ts with imported isImageLine()
- Add image line handling to markdown.ts to skip wrapping and margins for image escapes
- Consolidate Box cache into RenderCache type with childLines/width/bgSample/lines fields
- Use in-place mutation in applyLineResets() to avoid array allocation
Add Bash/Readline-style character search:
- Ctrl+] enters forward jump mode, awaits next character, jumps to it
- Ctrl+Alt+] does the same but searches backward
- Multi-line search
- Case-sensitive matching
- Pressing the hotkey again cancels; control chars cancel and fall
through
* fix(tui): keep file suggestions open when typing in Tab-triggered mode
Previously, pressing Tab on an empty prompt or after a space would show
file suggestions, but typing a letter would dismiss them because the
text didn't look like a path pattern. Now the editor tracks whether
autocomplete was triggered via Tab (force mode) or naturally (regular
mode), and uses the appropriate suggestion method when updating.
* fix(tui): hide autocomplete when backspacing slash command to empty
Previously, typing / showed slash command suggestions, but pressing
Backspace to delete it showed file suggestions instead of hiding all
suggestions.
Instead of buffering `\` and waiting for the next key, let it be
inserted immediately. On Enter, check if preceded by `\` and treat as
newline.
Removed backslash handling from the Input component entirely.
When Tab triggers file autocomplete and there's exactly one matching
suggestion, apply it immediately without showing the menu. This makes
path completion faster by eliminating the extra Tab press to confirm.
Add insertTextAtCursorInternal() to properly handle newlines by splitting
lines at the cursor position.
- Normalize line endings (CRLF/CR to LF)
- Handle single-line and multi-line insertion
- Position cursor at end of inserted text
- Call onChange only once at the end
Refactor handlePaste() to use insertTextAtCursorInternal() for
multi-line pastes, while retaining character-by-character insertion for
single-line pastes to preserve autocomplete triggering.
- allocateImageId() now returns random IDs instead of sequential
- Static images no longer auto-allocate IDs (transient display)
- Only explicit imageId usage (like DOSBox) gets tracked IDs
- Suppress emulators exit logging in DOSBox dispose
Fixes image replacement bug when extension and main app both
allocated sequential IDs starting at 1.
- Add allocateImageId() to generate unique image IDs
- Add deleteKittyImage() and deleteAllKittyImages() functions
- Image component now tracks its ID and has dispose() method
- renderImage() returns imageId for tracking
- DOSBox: reuse single image ID for all frames, delete on dispose
Fixes image accumulation hitting terminal quota and lingering
images after component close.
Autolinked emails like user@example.com were rendered as
'user@example.com (mailto:user@example.com)' because the comparison
token.text === token.href failed (text lacks mailto: prefix).
Now strips mailto: prefix before comparing, so autolinked emails
display without redundant URL in parentheses.
Add markdown.codeBlockIndent setting to customize indentation prefix for
rendered code blocks. Default remains 2 spaces for visual clarity, but
setting to empty string removes indentation for easier copy/paste of
code snippets to scripts, editors, or other tools.
Changes:
- tui: add optional codeBlockIndent to MarkdownTheme interface
- coding-agent: add MarkdownSettings with codeBlockIndent property
- coding-agent: compose theme with settings at call sites (no global state)
- coding-agent: update message components to accept optional MarkdownTheme
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
Undo snapshots are captured for all edit operations:
- Word insertion, backspace, forward delete
- Word/line deletion (Ctrl+W, Ctrl+U, Ctrl+K, Alt+D)
- Yank/yank-pop, paste, autocomplete completion
- Cursor movement starts a new undo unit
- setText() pushes snapshot when content changes
Additionally, history browsing captures the undo state on first entry.
Adds deleteWordForward action bound to Alt+D, which deletes from cursor
to the end of the current word and saves to kill ring for later yanking.
Consecutive forward kills append to the same kill ring entry.
Add kill ring functionality with:
- Ctrl+W/U/K save deleted text to kill ring
- Ctrl+Y yanks (pastes) most recent deletion
- Alt+Y cycles through kill ring (after Ctrl+Y)
- Consecutive deletions accumulate into single entry
- Add Focusable interface for components that need hardware cursor positioning
- Add CURSOR_MARKER (APC escape sequence) for marking cursor position in render output
- Editor and Input components implement Focusable and emit marker when focused
- TUI extracts cursor position from rendered output and positions hardware cursor
- Track hardwareCursorRow separately from cursorRow for differential rendering
- visibleWidth() and extractAnsiCode() now handle APC sequences
- Update overlay-test.ts example to demonstrate Focusable usage
- Add documentation for Focusable interface in docs/tui.md
Closes#719, closes#525
The Editor component now accepts TUI as the first constructor parameter,
enabling it to query terminal dimensions. When content exceeds available
height, the editor scrolls vertically keeping the cursor visible.
Features:
- Max editor height is 30% of terminal rows (minimum 5 lines)
- Page Up/Down keys scroll by page size
- Scroll indicators show lines above/below: ─── ↑ 5 more ───
Breaking change: Editor constructor signature changed from
new Editor(theme)
to
new Editor(tui, theme)
fixes#732
- Add LoginDialogComponent with proper borders (top/bottom DynamicBorder)
- Refactor all OAuth providers to use racing approach (browser callback vs manual paste)
- Add onEscape handler to Input component for cancellation
- Add abortable sleep for GitHub Copilot polling (instant cancel on Escape)
- Show OS-specific click hint (Cmd+click on macOS, Ctrl+click elsewhere)
- Clear content between login phases (fixes GitHub Copilot two-phase flow)
- Use InteractiveMode's showStatus/showError for result messages
- Reorder providers: Anthropic, ChatGPT, GitHub Copilot, Gemini CLI, Antigravity
* fix(tui): expand paste markers when opening external editor
Add getExpandedText() method to Editor that substitutes paste markers
with actual content. Use it in Ctrl-G external editor flow so users
see full pasted content instead of [paste #N ...] placeholders.
* docs: add changelog entries for #444