fix(tui): support modified enter for multiline input

This commit is contained in:
Brooklyn Nicholson
2026-04-26 13:52:54 -05:00
parent bb59d3bac2
commit 015f6c825d
4 changed files with 36 additions and 9 deletions

View File

@@ -11,7 +11,25 @@ function parseOne(sequence: string) {
return keys[0]!
}
describe('InputEvent macOS command modifiers', () => {
describe('enhanced keyboard modifier parsing', () => {
it('detects modified Enter sequences for multiline composer shortcuts', () => {
const shiftEnter = new InputEvent(parseOne('\u001b[13;2u'))
const ctrlEnter = new InputEvent(parseOne('\u001b[13;5u'))
const modifyOtherShiftEnter = new InputEvent(parseOne('\u001b[27;2;13~'))
expect(shiftEnter.key.return).toBe(true)
expect(shiftEnter.key.shift).toBe(true)
expect(shiftEnter.input).toBe('')
expect(ctrlEnter.key.return).toBe(true)
expect(ctrlEnter.key.ctrl).toBe(true)
expect(ctrlEnter.input).toBe('')
expect(modifyOtherShiftEnter.key.return).toBe(true)
expect(modifyOtherShiftEnter.key.shift).toBe(true)
expect(modifyOtherShiftEnter.input).toBe('')
})
it('preserves Cmd as super for kitty keyboard CSI-u sequences', () => {
const parsed = parseOne('\u001b[99;9u')
const event = new InputEvent(parsed)

View File

@@ -116,11 +116,15 @@ function parseKey(keypress: ParsedKey): [Key, string] {
// so the raw "[57358u" doesn't leak into the prompt. See #38781.
input = ''
} else {
// 'space' → ' '; 'escape' → '' (key.escape carries it;
// processedAsSpecialSequence bypasses the nonAlphanumericKeys
// clear below, so we must handle it explicitly here);
// otherwise use key name.
input = keypress.name === 'space' ? ' ' : keypress.name === 'escape' ? '' : keypress.name
// 'space' → ' '; functional keys like Enter/Escape carry their state
// through key.return/key.escape, and processedAsSpecialSequence bypasses
// the nonAlphanumericKeys clear below, so clear them explicitly here.
input =
keypress.name === 'space'
? ' '
: keypress.name === 'return' || keypress.name === 'escape'
? ''
: keypress.name
}
processedAsSpecialSequence = true
@@ -138,7 +142,12 @@ function parseKey(keypress: ParsedKey): [Key, string] {
// guards against future terminal behavior.
input = ''
} else {
input = keypress.name === 'space' ? ' ' : keypress.name === 'escape' ? '' : keypress.name
input =
keypress.name === 'space'
? ' '
: keypress.name === 'return' || keypress.name === 'escape'
? ''
: keypress.name
}
processedAsSpecialSequence = true

View File

@@ -176,7 +176,7 @@ export function isXtermJs(): boolean {
// in xterm.js-based terminals like VS Code). tmux is allowlisted because it
// accepts modifyOtherKeys and doesn't forward the kitty sequence to the outer
// terminal.
const EXTENDED_KEYS_TERMINALS = ['iTerm.app', 'kitty', 'WezTerm', 'ghostty', 'tmux', 'windows-terminal']
const EXTENDED_KEYS_TERMINALS = ['iTerm.app', 'kitty', 'WezTerm', 'ghostty', 'tmux', 'windows-terminal', 'vscode']
/** True if this terminal correctly handles extended key reporting
* (Kitty keyboard protocol + xterm modifyOtherKeys). */

View File

@@ -700,7 +700,7 @@ export function TextInput({
}
if (k.return) {
if (k.shift || (isMac ? isActionMod(k) : k.meta)) {
if (k.shift || k.ctrl || (isMac ? isActionMod(k) : k.meta)) {
flushParentChange()
commit(ins(vRef.current, curRef.current, '\n'), curRef.current + 1)
} else {