feat(terminal): Shift+Enter adds a new line in claude/codex prompts
xterm sends a plain CR for Shift+Enter, so the prompt was submitted. Send Ctrl+J (LF) instead: both prompts take it as a new line, a shell still runs the line.
This commit is contained in:
@@ -634,6 +634,15 @@ export const TerminalView = memo(function TerminalView({
|
||||
term.input(altArrow, true);
|
||||
return false;
|
||||
}
|
||||
// Shift+Enter: xterm sends a plain CR, so claude/codex SUBMIT the
|
||||
// prompt. Send Ctrl+J (LF) instead — both prompts take it as a new
|
||||
// line (Option+Enter is the other way, awkward on a Mac), while a
|
||||
// shell still runs the line, as Enter did.
|
||||
if (ev.key === "Enter" && ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
term.input("\n", true);
|
||||
return false;
|
||||
}
|
||||
if (ev.key === "Tab" && tabMovesRef.current) return false;
|
||||
if (ev.key === "Tab" && ev.shiftKey) ev.preventDefault();
|
||||
return true;
|
||||
|
||||
@@ -634,6 +634,21 @@ describe("TerminalView", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("sends Shift+Enter as Ctrl+J, a new line in claude's prompt, not a submit", () => {
|
||||
inputMock.mockClear();
|
||||
render(<TerminalView runId="r1" wsBaseUrl="ws://x" />);
|
||||
const handler = keyHandlerMock.mock.calls[0]![0]! as (e: KeyboardEvent) => boolean;
|
||||
const ev = new KeyboardEvent("keydown", { key: "Enter", shiftKey: true, cancelable: true });
|
||||
expect(handler(ev)).toBe(false);
|
||||
expect(ev.defaultPrevented).toBe(true);
|
||||
expect(inputMock).toHaveBeenCalledWith("\n", true);
|
||||
inputMock.mockClear();
|
||||
// Plain Enter and Alt+Enter stay xterm's own.
|
||||
expect(handler(new KeyboardEvent("keydown", { key: "Enter" }))).toBe(true);
|
||||
expect(handler(new KeyboardEvent("keydown", { key: "Enter", altKey: true }))).toBe(true);
|
||||
expect(inputMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("size reporting (tmux must never be wider than the terminal)", () => {
|
||||
// A tmux pane wider than xterm wraps each over-long row's tail onto the
|
||||
// next row's left margin, and tmux never repaints a row it believes
|
||||
|
||||
Reference in New Issue
Block a user