mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 01:21:43 +00:00
fix(tui): Ctrl+C with input selection actually preserves input (lift handler to app level)
Previous fix in 9dbf1ec6 handled Ctrl+C inside textInput but the APP-level useInputHandlers fires the same keypress in a separate React hook and ran clearIn() regardless. Net effect: the OSC 52 copy succeeded but the input wiped right after, so Brooklyn only noticed the wipe. Lift the selection-aware Ctrl+C to a single place by threading input selection state through a new nanostore (src/app/inputSelectionStore.ts). textInput syncs its derived `selected` range + a clear() callback to the store on every selection change, and the app-level Ctrl+C handler reads the store before its clear/interrupt/die chain: - terminal-level selection (scrollback) → copy, existing behavior - in-input selection present → copy + clear selection, preserve input - input has text, no selection → clearIn(), existing behavior - empty + busy → interrupt turn - empty + idle → die textInput no longer has its own Ctrl+C block; keypress falls through to app-level like it did before 9dbf1ec6.
This commit is contained in:
parent
bfac5d039d
commit
fb06bc67de
3 changed files with 59 additions and 15 deletions
14
ui-tui/src/app/inputSelectionStore.ts
Normal file
14
ui-tui/src/app/inputSelectionStore.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { atom } from 'nanostores'
|
||||
|
||||
export interface InputSelection {
|
||||
clear: () => void
|
||||
end: number
|
||||
start: number
|
||||
value: string
|
||||
}
|
||||
|
||||
export const $inputSelection = atom<InputSelection | null>(null)
|
||||
|
||||
export const setInputSelection = (next: InputSelection | null) => $inputSelection.set(next)
|
||||
|
||||
export const getInputSelection = () => $inputSelection.get()
|
||||
|
|
@ -8,6 +8,9 @@ import type {
|
|||
VoiceRecordResponse
|
||||
} from '../gatewayTypes.js'
|
||||
|
||||
import { writeOsc52Clipboard } from '../lib/osc52.js'
|
||||
|
||||
import { getInputSelection } from './inputSelectionStore.js'
|
||||
import type { InputHandlerContext, InputHandlerResult } from './interfaces.js'
|
||||
import { $isBlocked, $overlayState, patchOverlayState } from './overlayStore.js'
|
||||
import { turnController } from './turnController.js'
|
||||
|
|
@ -247,6 +250,15 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
|
|||
return copySelection()
|
||||
}
|
||||
|
||||
const inputSel = getInputSelection()
|
||||
|
||||
if (inputSel && inputSel.end > inputSel.start) {
|
||||
writeOsc52Clipboard(inputSel.value.slice(inputSel.start, inputSel.end))
|
||||
inputSel.clear()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (live.busy && live.sid) {
|
||||
return turnController.interruptTurn({
|
||||
appendMessage: actions.appendMessage,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { InputEvent, Key } from '@hermes/ink'
|
|||
import * as Ink from '@hermes/ink'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { writeOsc52Clipboard } from '../lib/osc52.js'
|
||||
import { setInputSelection } from '../app/inputSelectionStore.js'
|
||||
|
||||
type InkExt = typeof Ink & {
|
||||
stringWidth: (s: string) => number
|
||||
|
|
@ -353,6 +353,28 @@ export function TextInput({
|
|||
}
|
||||
}, [value])
|
||||
|
||||
useEffect(() => {
|
||||
if (!focus) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
setInputSelection({
|
||||
clear: () => {
|
||||
selRef.current = null
|
||||
setSel(null)
|
||||
},
|
||||
end: selected.end,
|
||||
start: selected.start,
|
||||
value: vRef.current
|
||||
})
|
||||
} else {
|
||||
setInputSelection(null)
|
||||
}
|
||||
|
||||
return () => setInputSelection(null)
|
||||
}, [focus, selected])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (pasteTimer.current) {
|
||||
|
|
@ -470,20 +492,16 @@ export function TextInput({
|
|||
return void emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current })
|
||||
}
|
||||
|
||||
if (k.ctrl && inp === 'c') {
|
||||
const range = selRange()
|
||||
|
||||
if (range) {
|
||||
writeOsc52Clipboard(vRef.current.slice(range.start, range.end))
|
||||
clearSel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (k.upArrow || k.downArrow || k.tab || (k.shift && k.tab) || k.pageUp || k.pageDown || k.escape) {
|
||||
if (
|
||||
k.upArrow ||
|
||||
k.downArrow ||
|
||||
(k.ctrl && inp === 'c') ||
|
||||
k.tab ||
|
||||
(k.shift && k.tab) ||
|
||||
k.pageUp ||
|
||||
k.pageDown ||
|
||||
k.escape
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue