Content-Length: 409690 | pFad | http://github.com/coder/coder/commit/558e25d591a657473c3b49763ae8a087f601e8be

DA feat: support shift+enter in terminal (#19021) · coder/coder@558e25d · GitHub
Skip to content

Commit 558e25d

Browse files
authored
feat: support shift+enter in terminal (#19021)
It acts the same alt+enter, but is more familiar to users. Closes #18864
1 parent 71738f6 commit 558e25d

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

site/src/pages/TerminalPage/TerminalPage.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "jest-canvas-mock";
22
import { waitFor } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
34
import { API } from "api/api";
45
import WS from "jest-websocket-mock";
56
import { http, HttpResponse } from "msw";
@@ -148,4 +149,21 @@ describe("TerminalPage", () => {
148149
ws.send(text);
149150
await expectTerminalText(container, text);
150151
});
152+
153+
it("supports shift+enter", async () => {
154+
const ws = new WS(
155+
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
156+
);
157+
158+
const { container } = await renderTerminal();
159+
// Ideally we could use ws.connected but that seems to pause React updates.
160+
// For now, wait for the initial resize message instead.
161+
await ws.nextMessage;
162+
163+
const msg = ws.nextMessage;
164+
const terminal = container.getElementsByClassName("xterm");
165+
await userEvent.type(terminal[0], "{Shift>}{Enter}{/Shift}");
166+
const req = JSON.parse(new TextDecoder().decode((await msg) as Uint8Array));
167+
expect(req.data).toBe("\x1b\r");
168+
});
151169
});

site/src/pages/TerminalPage/TerminalPage.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ const TerminalPage: FC = () => {
148148
}),
149149
);
150150

151+
// Make shift+enter send ^[^M (escaped carriage return). Applications
152+
// typically take this to mean to insert a literal newline. There is no way
153+
// to remove this handler, so we must attach it once and rely on a ref to
154+
// send it to the current socket.
155+
const escapedCarriageReturn = "\x1b\r";
156+
terminal.attachCustomKeyEventHandler((ev) => {
157+
if (ev.shiftKey && ev.key === "Enter") {
158+
if (ev.type === "keydown") {
159+
websocketRef.current?.send(
160+
new TextEncoder().encode(
161+
JSON.stringify({ data: escapedCarriageReturn }),
162+
),
163+
);
164+
}
165+
return false;
166+
}
167+
return true;
168+
});
169+
151170
terminal.open(terminalWrapperRef.current);
152171

153172
// We have to fit twice here. It's unknown why, but the first fit will
@@ -190,6 +209,7 @@ const TerminalPage: FC = () => {
190209
}, [navigate, reconnectionToken, searchParams]);
191210

192211
// Hook up the terminal through a web socket.
212+
const websocketRef = useRef<Websocket>();
193213
useEffect(() => {
194214
if (!terminal) {
195215
return;
@@ -270,6 +290,7 @@ const TerminalPage: FC = () => {
270290
.withBackoff(new ExponentialBackoff(1000, 6))
271291
.build();
272292
websocket.binaryType = "arraybuffer";
293+
websocketRef.current = websocket;
273294
websocket.addEventListener(WebsocketEvent.open, () => {
274295
// Now that we are connected, allow user input.
275296
terminal.options = {
@@ -333,6 +354,7 @@ const TerminalPage: FC = () => {
333354
d.dispose();
334355
}
335356
websocket?.close(1000);
357+
websocketRef.current = undefined;
336358
};
337359
}, [
338360
command,

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/commit/558e25d591a657473c3b49763ae8a087f601e8be

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy