feat(tui-go): multi-line input — Ctrl+J / Alt+Enter newline, growing box (Phase 2)

Enter still submits; Ctrl+J (always) and Alt+Enter (most terminals) insert a
newline at the cursor. True Shift+Enter isn't distinguishable from Enter in
bubbletea v1.3.10 (terminals send the same byte, no kitty-protocol parsing), so
these are the working stand-ins — noted in the input hint.

- editorLines() renders the buffer as styled rows with the caret at the cursor,
  splitting on \n and capping at maxInputLines (last rows kept). Both the idle
  launcher input and the in-session bottom bar use it and grow to fit; View()
  sizes the bottom bar via inputBarHeight() instead of the fixed inputH.
- Footer gains a "^J newline" hint in insert mode (non-filter); the launcher
  sub-line shows "⌥↵/^J newline". New "compose" preview kind + editor_lines_test
  (split + height cap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 12:03:06 +00:00
parent f2be46a743
commit cde0c33031
4 changed files with 133 additions and 23 deletions
+13
View File
@@ -440,7 +440,20 @@ func (m Model) handleInsertKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
m.inputMode = ModeRouter
}
case tea.KeyEnter:
// Alt+Enter inserts a newline; a bare Enter submits. (Shift+Enter is not
// distinguishable from Enter in this terminal stack — see Ctrl+J below.)
if k.Alt && m.inputMode != ModeFilter {
m.appendRunes("\n")
return m, nil
}
return m.submit()
case tea.KeyCtrlJ:
// Reliable "newline without sending" (Ctrl+J is the literal LF key) for the
// single-line filter excluded.
if m.inputMode != ModeFilter {
m.appendRunes("\n")
}
return m, nil
case tea.KeyBackspace:
m.backspace()
m.syncFilter()