mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(tui): fix Linux Ctrl+C regression, remove double clipboard write
- Fix critical regression: on Linux, Ctrl+C could not interrupt/clear/exit because isAction(key,'c') shadowed the isCtrl block (both resolve to k.ctrl on non-macOS). Restructured: isAction block now falls through to interrupt logic on non-macOS when no selection exists. - Remove double pbcopy: ink's copySelection() already calls setClipboard() which handles pbcopy+tmux+OSC52. The extra writeClipboardText call in useInputHandlers copySelection() was firing pbcopy a second time. - Remove allowClipboardHotkeys prop from TextInput — every caller passed isMac, and TextInput already imports isMac. Eliminated prop-drilling through appLayout, maskedPrompt, and prompts. - Remove dead code: the isCtrl copy paths (lines 277-288) were unreachable on any platform after the isAction block changes. - Simplify textInput Cmd+C: use writeClipboardText directly without the redundant OSC52 fallback (this path is macOS-only where pbcopy works).
This commit is contained in:
parent
e388910fe6
commit
b7e71fb727
5 changed files with 18 additions and 51 deletions
|
|
@ -8,8 +8,6 @@ import type {
|
|||
VoiceRecordResponse
|
||||
} from '../gatewayTypes.js'
|
||||
|
||||
import { writeClipboardText } from '../lib/clipboard.js'
|
||||
import { writeOsc52Clipboard } from '../lib/osc52.js'
|
||||
import { isAction, isMac } from '../lib/platform.js'
|
||||
|
||||
import { getInputSelection } from './inputSelectionStore.js'
|
||||
|
|
@ -30,19 +28,13 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
|
|||
const pagerPageSize = Math.max(5, (terminal.stdout?.rows ?? 24) - 6)
|
||||
|
||||
const copySelection = () => {
|
||||
// ink's copySelection() already calls setClipboard() which handles
|
||||
// pbcopy (macOS), wl-copy/xclip (Linux), tmux, and OSC 52 fallback.
|
||||
const text = terminal.selection.copySelection()
|
||||
|
||||
if (!text) {
|
||||
return
|
||||
if (text) {
|
||||
actions.sys(`copied ${text.length} chars`)
|
||||
}
|
||||
|
||||
void writeClipboardText(text).then(copied => {
|
||||
if (!copied) {
|
||||
writeOsc52Clipboard(text)
|
||||
}
|
||||
})
|
||||
|
||||
actions.sys(`copied ${text.length} chars`)
|
||||
}
|
||||
|
||||
const clearSelection = () => {
|
||||
|
|
@ -259,34 +251,19 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
|
|||
const inputSel = getInputSelection()
|
||||
|
||||
if (inputSel && inputSel.end > inputSel.start) {
|
||||
const text = inputSel.value.slice(inputSel.start, inputSel.end)
|
||||
|
||||
void writeClipboardText(text).then(copied => {
|
||||
if (!copied) {
|
||||
writeOsc52Clipboard(text)
|
||||
}
|
||||
})
|
||||
|
||||
inputSel.clear()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (isCtrl(key, ch, 'c')) {
|
||||
if (!isMac && terminal.hasSelection) {
|
||||
return copySelection()
|
||||
}
|
||||
|
||||
const inputSel = getInputSelection()
|
||||
|
||||
if (!isMac && inputSel && inputSel.end > inputSel.start) {
|
||||
writeOsc52Clipboard(inputSel.value.slice(inputSel.start, inputSel.end))
|
||||
inputSel.clear()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// On macOS, Cmd+C with no selection is a no-op (Ctrl+C below handles interrupt).
|
||||
// On non-macOS, isAction uses Ctrl, so fall through to interrupt/clear/exit.
|
||||
if (isMac) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (key.ctrl && ch.toLowerCase() === 'c') {
|
||||
if (live.busy && live.sid) {
|
||||
return turnController.interruptTurn({
|
||||
appendMessage: actions.appendMessage,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import type { AppLayoutProgressProps, AppLayoutProps } from '../app/interfaces.j
|
|||
import { $isBlocked } from '../app/overlayStore.js'
|
||||
import { $uiState } from '../app/uiStore.js'
|
||||
import { PLACEHOLDER } from '../content/placeholders.js'
|
||||
import { isMac } from '../lib/platform.js'
|
||||
import type { Theme } from '../theme.js'
|
||||
import type { DetailsMode } from '../types.js'
|
||||
|
||||
|
|
@ -236,7 +235,6 @@ const ComposerPane = memo(function ComposerPane({
|
|||
<Box flexGrow={1} position="relative">
|
||||
<TextInput
|
||||
columns={Math.max(20, composer.cols - pw)}
|
||||
allowClipboardHotkeys={isMac}
|
||||
onChange={composer.updateInput}
|
||||
onPaste={composer.handleTextPaste}
|
||||
onSubmit={composer.submit}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Box, Text } from '@hermes/ink'
|
|||
import { useState } from 'react'
|
||||
|
||||
import type { Theme } from '../theme.js'
|
||||
import { isMac } from '../lib/platform.js'
|
||||
|
||||
import { TextInput } from './textInput.js'
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ export function MaskedPrompt({ cols = 80, icon, label, onSubmit, sub, t }: Maske
|
|||
|
||||
<Box>
|
||||
<Text color={t.color.label}>{'> '}</Text>
|
||||
<TextInput columns={Math.max(20, cols - 6)} allowClipboardHotkeys={isMac} mask="*" onChange={setValue} onSubmit={onSubmit} value={value} />
|
||||
<TextInput columns={Math.max(20, cols - 6)} mask="*" onChange={setValue} onSubmit={onSubmit} value={value} />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }: Clarify
|
|||
|
||||
<Box>
|
||||
<Text color={t.color.label}>{'> '}</Text>
|
||||
<TextInput columns={Math.max(20, cols - 6)} allowClipboardHotkeys={isMac} onChange={setCustom} onSubmit={onAnswer} value={custom} />
|
||||
<TextInput columns={Math.max(20, cols - 6)} onChange={setCustom} onSubmit={onAnswer} value={custom} />
|
||||
</Box>
|
||||
|
||||
<Text color={t.color.dim}>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { setInputSelection } from '../app/inputSelectionStore.js'
|
||||
import { readClipboardText, writeClipboardText } from '../lib/clipboard.js'
|
||||
import { isActionMod, isMac } from '../lib/platform.js'
|
||||
import { writeOsc52Clipboard } from '../lib/osc52.js'
|
||||
|
||||
type InkExt = typeof Ink & {
|
||||
stringWidth: (s: string) => number
|
||||
|
|
@ -282,7 +281,6 @@ export function TextInput({
|
|||
onChange,
|
||||
onPaste,
|
||||
onSubmit,
|
||||
allowClipboardHotkeys = false,
|
||||
mask,
|
||||
placeholder = '',
|
||||
focus = true
|
||||
|
|
@ -508,12 +506,12 @@ export function TextInput({
|
|||
(inp: string, k: Key, event: InputEvent) => {
|
||||
const eventRaw = event.keypress.raw
|
||||
|
||||
if (eventRaw === '\x1bv' || eventRaw === '\x1bV' || eventRaw === '\x16' || (allowClipboardHotkeys && isMac && k.meta && inp.toLowerCase() === 'v')) {
|
||||
if (eventRaw === '\x1bv' || eventRaw === '\x1bV' || eventRaw === '\x16' || (isMac && k.meta && inp.toLowerCase() === 'v')) {
|
||||
if (cbPaste.current) {
|
||||
return void emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current })
|
||||
}
|
||||
|
||||
if (allowClipboardHotkeys) {
|
||||
if (isMac) {
|
||||
void readClipboardText().then(text => {
|
||||
if (text) {
|
||||
pastePlainText(text)
|
||||
|
|
@ -524,17 +522,13 @@ export function TextInput({
|
|||
return
|
||||
}
|
||||
|
||||
if (allowClipboardHotkeys && isMac && k.meta && inp.toLowerCase() === 'c') {
|
||||
if (isMac && k.meta && inp.toLowerCase() === 'c') {
|
||||
const range = selRange()
|
||||
|
||||
if (range) {
|
||||
const text = vRef.current.slice(range.start, range.end)
|
||||
|
||||
void writeClipboardText(text).then(copied => {
|
||||
if (!copied) {
|
||||
writeOsc52Clipboard(text)
|
||||
}
|
||||
})
|
||||
void writeClipboardText(text)
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -735,7 +729,6 @@ export interface PasteEvent {
|
|||
}
|
||||
|
||||
interface TextInputProps {
|
||||
allowClipboardHotkeys?: boolean
|
||||
columns?: number
|
||||
focus?: boolean
|
||||
mask?: string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue