Skip to content

Commit 804958d

Browse files
committed
Fix: for terminals that don't send enter, submit with 'linefeed'
1 parent cb070f6 commit 804958d

File tree

5 files changed

+39
-6
lines changed

5 files changed

+39
-6
lines changed

cli/src/components/chat-input-bar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useAskUserBridge } from '../hooks/use-ask-user-bridge'
1212
import { useEvent } from '../hooks/use-event'
1313
import { useChatStore } from '../state/chat-store'
1414
import { getInputModeConfig } from '../utils/input-modes'
15+
import { isLinefeedActingAsEnter } from '../utils/terminal-enter-detection'
1516
import { BORDER_CHARS } from '../utils/ui-constants'
1617

1718
import type { useTheme } from '../hooks/use-theme'
@@ -131,7 +132,8 @@ export const ChatInputBar = ({
131132
option?: boolean
132133
}) => {
133134
const isPlainEnter =
134-
(key.name === 'return' || key.name === 'enter') &&
135+
(key.name === 'return' || key.name === 'enter' ||
136+
(key.name === 'linefeed' && isLinefeedActingAsEnter())) &&
135137
!key.shift &&
136138
!key.ctrl &&
137139
!key.meta &&

cli/src/components/multiline-input.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { InputCursor } from './input-cursor'
1313
import { useTheme } from '../hooks/use-theme'
1414
import { useChatStore } from '../state/chat-store'
1515
import { clamp } from '../utils/math'
16+
import { isLinefeedActingAsEnter, markReturnKeySeen } from '../utils/terminal-enter-detection'
1617
import { supportsTruecolor } from '../utils/theme-system'
1718
import { calculateNewCursorPosition } from '../utils/word-wrap-utils'
1819

@@ -523,11 +524,17 @@ export const MultilineInput = forwardRef<
523524
const handleEnterKeys = useCallback(
524525
(key: KeyEvent): boolean => {
525526
const lowerKeyName = (key.name ?? '').toLowerCase()
526-
const isEnterKey = key.name === 'return' || key.name === 'enter'
527-
// Ctrl+J is translated by the terminal to a linefeed character (0x0a)
528-
// So we detect it by checking for name === 'linefeed' rather than ctrl + j
527+
const isReturnOrEnter = key.name === 'return' || key.name === 'enter'
528+
529+
if (isReturnOrEnter) {
530+
markReturnKeySeen()
531+
}
532+
533+
const linefeedIsEnter = lowerKeyName === 'linefeed' && isLinefeedActingAsEnter()
534+
const isEnterKey = isReturnOrEnter || linefeedIsEnter
535+
529536
const isCtrlJ =
530-
lowerKeyName === 'linefeed' ||
537+
(lowerKeyName === 'linefeed' && !linefeedIsEnter) ||
531538
(key.ctrl &&
532539
!key.meta &&
533540
!key.option &&

cli/src/hooks/use-chat-keyboard.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type ChatKeyboardState,
1313
type ChatKeyboardAction,
1414
} from '../utils/keyboard-actions'
15+
import { markReturnKeySeen } from '../utils/terminal-enter-detection'
1516

1617
import type { KeyEvent } from '@opentui/core'
1718

@@ -304,6 +305,10 @@ export function useChatKeyboard({
304305
reportActivity()
305306
}
306307

308+
if (key.name === 'return' || key.name === 'enter') {
309+
markReturnKeySeen()
310+
}
311+
307312
const action = resolveChatKeyboardAction(key, state)
308313
const handled = dispatchAction(action, handlers)
309314

cli/src/utils/keyboard-actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getInputModeConfig, type InputMode } from './input-modes'
2+
import { isLinefeedActingAsEnter } from './terminal-enter-detection'
23
import type { KeyEvent } from '@opentui/core'
34

45

@@ -131,7 +132,8 @@ export function resolveChatKeyboardAction(
131132
const isShiftTab =
132133
key.name === 'tab' && key.shift && !key.ctrl && !key.meta && !key.option
133134
const isEnter =
134-
(key.name === 'return' || key.name === 'enter') &&
135+
(key.name === 'return' || key.name === 'enter' ||
136+
(key.name === 'linefeed' && isLinefeedActingAsEnter())) &&
135137
!key.shift &&
136138
!hasModifier(key)
137139
const isPageUp = key.name === 'pageup' && !hasModifier(key)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Most terminals send \r for Enter and \n for Ctrl+J. A few niche Linux
3+
* terminal emulators send \n for Enter instead, making the two
4+
* indistinguishable. We detect this at runtime by tracking whether we've
5+
* ever seen a \r ("return") key event. On macOS, Enter always sends \r.
6+
*/
7+
8+
let hasSeenReturnKey = process.platform === 'darwin'
9+
10+
export function markReturnKeySeen(): void {
11+
hasSeenReturnKey = true
12+
}
13+
14+
/** True when a "linefeed" (\n) key event should be treated as Enter. */
15+
export function isLinefeedActingAsEnter(): boolean {
16+
return !hasSeenReturnKey
17+
}

0 commit comments

Comments
 (0)