feat(tui): composer soft-wrap, alt+backspace, bracketed paste, borderless input

- editorLines now soft-wraps long logical lines at the box width (caret tracked
  across wrapped rows) instead of overflowing/truncating
- alt+backspace deletes the word before the cursor (deleteWordBack)
- handle tea.PasteMsg so bracketed paste (Ctrl+V / Ctrl+Shift+V / right-click)
  drops clipboard text into the focused composer; was silently dropped
- render the composer (in-session + launcher) borderless: top rule + flush text,
  no side borders or trailing padding, so a terminal drag-copy yields just the
  typed text instead of box chrome and whitespace
This commit is contained in:
2026-06-28 20:42:50 +04:00
parent 8abe7d9eb7
commit ccfa4b4740
4 changed files with 173 additions and 27 deletions
+47
View File
@@ -5,6 +5,53 @@ import (
"testing"
)
// A bracketed paste inserts at the cursor in the main composer (insert mode, no overlay).
func TestHandlePasteIntoComposer(t *testing.T) {
m := NewModel(nil)
m.editMode, m.overlay = ModeInsert, OverlayNone
m.inputBuffer, m.inputCursor = "ab", 1
m = m.handlePaste("XY").(Model)
if m.inputBuffer != "aXYb" || m.inputCursor != 3 {
t.Fatalf("composer paste = (%q,%d), want (%q,3)", m.inputBuffer, m.inputCursor, "aXYb")
}
}
// A paste while steering appends to the steer note, not the input buffer.
func TestHandlePasteWhileSteering(t *testing.T) {
m := NewModel(nil)
m.steering = true
m.steerBuffer = "go "
m = m.handlePaste("now").(Model)
if m.steerBuffer != "go now" || m.inputBuffer != "" {
t.Fatalf("steer paste = steer:%q input:%q", m.steerBuffer, m.inputBuffer)
}
}
// Alt+Backspace deletes the word (plus trailing spaces) left of the cursor.
func TestDeleteWordBack(t *testing.T) {
cases := []struct {
buf string
cur int
wantBuf string
wantCur int
}{
{"foo bar", 7, "foo ", 4},
{"foo bar ", 9, "foo ", 4},
{"foo bar baz", 7, "foo baz", 4}, // cursor mid-string
{"one", 3, "", 0},
{"", 0, "", 0},
}
for _, c := range cases {
m := NewModel(nil)
m.inputBuffer, m.inputCursor = c.buf, c.cur
m.deleteWordBack()
if m.inputBuffer != c.wantBuf || m.inputCursor != c.wantCur {
t.Errorf("deleteWordBack(%q,%d) = (%q,%d), want (%q,%d)",
c.buf, c.cur, m.inputBuffer, m.inputCursor, c.wantBuf, c.wantCur)
}
}
}
// F-006: a multi-line paste must collapse to a single display line so the fixed-height
// input box can't overflow and leave a smeared/stale region.
func TestFlattenForDisplay(t *testing.T) {