Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Terminal as XTerm } from "@xterm/xterm";
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";

// Mock localStorage for Node.js test environment
Expand Down Expand Up @@ -30,9 +31,11 @@ mock.module("renderer/lib/trpc-client", () => ({
}));

// Import after mocks are set up
const { getDefaultTerminalBg, getDefaultTerminalTheme } = await import(
"./helpers"
);
const {
getDefaultTerminalBg,
getDefaultTerminalTheme,
setupKeyboardHandler,
} = await import("./helpers");
Comment thread
coderabbitai[bot] marked this conversation as resolved.

describe("getDefaultTerminalTheme", () => {
beforeEach(() => {
Expand Down Expand Up @@ -108,3 +111,83 @@ describe("getDefaultTerminalBg", () => {
expect(getDefaultTerminalBg()).toBe("#1a1a1a");
});
});

describe("setupKeyboardHandler", () => {
const originalNavigator = globalThis.navigator;

afterEach(() => {
// Restore navigator between tests
// @ts-expect-error - resetting global navigator for tests
globalThis.navigator = originalNavigator;
});

it("maps Option+Left/Right to Meta+B/F on macOS", () => {
// @ts-expect-error - mocking navigator for tests
globalThis.navigator = { platform: "MacIntel" };

let handler: ((event: KeyboardEvent) => boolean) | null = null;
const xterm = {
attachCustomKeyEventHandler: (next: (event: KeyboardEvent) => boolean) => {
handler = next;
},
};

const onWrite = mock(() => {});
setupKeyboardHandler(xterm as unknown as XTerm, { onWrite });

handler?.({
type: "keydown",
key: "ArrowLeft",
altKey: true,
metaKey: false,
ctrlKey: false,
shiftKey: false,
} as KeyboardEvent);
handler?.({
type: "keydown",
key: "ArrowRight",
altKey: true,
metaKey: false,
ctrlKey: false,
shiftKey: false,
} as KeyboardEvent);

expect(onWrite).toHaveBeenCalledWith("\x1bb");
expect(onWrite).toHaveBeenCalledWith("\x1bf");
});

it("maps Ctrl+Left/Right to Meta+B/F on Windows", () => {
// @ts-expect-error - mocking navigator for tests
globalThis.navigator = { platform: "Win32" };

let handler: ((event: KeyboardEvent) => boolean) | null = null;
const xterm = {
attachCustomKeyEventHandler: (next: (event: KeyboardEvent) => boolean) => {
handler = next;
},
};

const onWrite = mock(() => {});
setupKeyboardHandler(xterm as unknown as XTerm, { onWrite });

handler?.({
type: "keydown",
key: "ArrowLeft",
altKey: false,
metaKey: false,
ctrlKey: true,
shiftKey: false,
} as KeyboardEvent);
handler?.({
type: "keydown",
key: "ArrowRight",
altKey: false,
metaKey: false,
ctrlKey: true,
shiftKey: false,
} as KeyboardEvent);

expect(onWrite).toHaveBeenCalledWith("\x1bb");
expect(onWrite).toHaveBeenCalledWith("\x1bf");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,11 @@ export function setupKeyboardHandler(
xterm: XTerm,
options: KeyboardHandlerOptions = {},
): () => void {
const platform =
typeof navigator !== "undefined" ? navigator.platform.toLowerCase() : "";
const isMac = platform.includes("mac");
const isWindows = platform.includes("win");

const handler = (event: KeyboardEvent): boolean => {
const isShiftEnter =
event.key === "Enter" &&
Expand Down Expand Up @@ -543,6 +548,69 @@ export function setupKeyboardHandler(
return false;
}

// Option+Left/Right (macOS): word navigation (Meta+B / Meta+F)
const isOptionLeft =
event.key === "ArrowLeft" &&
event.altKey &&
isMac &&
!event.metaKey &&
!event.ctrlKey &&
!event.shiftKey;

if (isOptionLeft) {
if (event.type === "keydown" && options.onWrite) {
options.onWrite("\x1bb"); // Meta+B - backward word
}
return false;
}

// Option+Right: Move cursor forward by word (Meta+F)
const isOptionRight =
event.key === "ArrowRight" &&
event.altKey &&
isMac &&
!event.metaKey &&
!event.ctrlKey &&
!event.shiftKey;

if (isOptionRight) {
if (event.type === "keydown" && options.onWrite) {
options.onWrite("\x1bf"); // Meta+F - forward word
}
return false;
}

// Ctrl+Left/Right (Windows): word navigation (Meta+B / Meta+F)
const isCtrlLeft =
event.key === "ArrowLeft" &&
event.ctrlKey &&
isWindows &&
!event.metaKey &&
!event.altKey &&
!event.shiftKey;

if (isCtrlLeft) {
if (event.type === "keydown" && options.onWrite) {
options.onWrite("\x1bb"); // Meta+B - backward word
}
return false;
}

const isCtrlRight =
event.key === "ArrowRight" &&
event.ctrlKey &&
isWindows &&
!event.metaKey &&
!event.altKey &&
!event.shiftKey;

if (isCtrlRight) {
if (event.type === "keydown" && options.onWrite) {
options.onWrite("\x1bf"); // Meta+F - forward word
}
return false;
}

if (isTerminalReservedEvent(event)) return true;

const clearKeys = getHotkeyKeys("CLEAR_TERMINAL");
Expand Down
Loading