mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 01:51:44 +00:00
fix(tui): up-arrow inside a multi-line buffer moves cursor, not history
Reported during TUI v2 blitz retest: typing a multi-line message with shift-Enter and then pressing Up to edit an earlier line swapped the whole buffer for the previous history entry instead of moving the cursor up a line. Down then restored the draft → the buffer appeared to "flip" between the draft and a prior prompt. `useInputHandlers` cycles history on Up/Down, but textInput only checked `inputBuf.length` — that only counts lines committed with a trailing backslash, not shift-Enter newlines inside `input` itself. Fix: detect logical lines inside the input string and move the cursor one line up/down preserving column offset (clamp to line end when the destination is shorter, standard editor behavior). Only fall through to history cycling when the cursor is already on the first line (Up) or last line (Down). Adds unit coverage for the new `lineNav` helper.
This commit is contained in:
parent
35a4b093d8
commit
d30f6ac44e
2 changed files with 102 additions and 2 deletions
|
|
@ -134,6 +134,39 @@ function wordRight(s: string, p: number) {
|
|||
return i
|
||||
}
|
||||
|
||||
/**
|
||||
* Move cursor one logical line up or down inside `s` while preserving the
|
||||
* column offset from the current line's start. Returns `null` when the cursor
|
||||
* is already on the first line (up) or last line (down) — callers use that
|
||||
* signal to fall through to history cycling instead of eating the arrow key.
|
||||
*/
|
||||
export function lineNav(s: string, p: number, dir: -1 | 1): null | number {
|
||||
const pos = snapPos(s, p)
|
||||
const curStart = s.lastIndexOf('\n', pos - 1) + 1
|
||||
const col = pos - curStart
|
||||
|
||||
if (dir < 0) {
|
||||
if (curStart === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const prevStart = s.lastIndexOf('\n', curStart - 2) + 1
|
||||
|
||||
return snapPos(s, Math.min(prevStart + col, curStart - 1))
|
||||
}
|
||||
|
||||
const nextBreak = s.indexOf('\n', pos)
|
||||
|
||||
if (nextBreak < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nextEnd = s.indexOf('\n', nextBreak + 1)
|
||||
const lineEnd = nextEnd < 0 ? s.length : nextEnd
|
||||
|
||||
return snapPos(s, Math.min(nextBreak + 1 + col, lineEnd))
|
||||
}
|
||||
|
||||
function cursorLayout(value: string, cursor: number, cols: number) {
|
||||
const pos = Math.max(0, Math.min(cursor, value.length))
|
||||
const w = Math.max(1, cols - 1)
|
||||
|
|
@ -570,9 +603,21 @@ export function TextInput({
|
|||
return
|
||||
}
|
||||
|
||||
if (k.upArrow || k.downArrow) {
|
||||
const next = lineNav(vRef.current, curRef.current, k.upArrow ? -1 : 1)
|
||||
|
||||
if (next !== null) {
|
||||
clearSel()
|
||||
setCur(next)
|
||||
curRef.current = next
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
k.upArrow ||
|
||||
k.downArrow ||
|
||||
(k.ctrl && inp === 'c') ||
|
||||
k.tab ||
|
||||
(k.shift && k.tab) ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue