fix(agents): move the caret with Home and End in the agent composer on macOS (#2943)

WebKit maps Home / End in a text box to scrolling and leaves the caret where it is, so they did nothing in the agent composer on macOS. The composer now moves the caret to the start or end of the visual line (Shift extends the selection); with ⌘, Ctrl or Alt the keys keep their native behaviour, and other platforms are unchanged.

Co-authored-by: EjaYF <9842286+EjaYF@users.noreply.github.com>
This commit is contained in:
Ernstjan Freriks
2026-11-12 20:55:06 +08:00
committed by GitHub
co-authored by EjaYF
parent 068ce8e721
commit 5e4fa861bb
2 changed files with 38 additions and 1 deletions
+7
View File
@@ -62,6 +62,7 @@ import { useCompletionAnchor } from "./useCompletionAnchor";
import { useAgentSettings } from "./agentSettings";
import styles from "./Composer.module.css";
import { isImeKey } from "@/platform/ime";
import { isMacHost } from "@/platform/keymap";
const MAX_PROMPT_HEIGHT = 280;
const MAX_ITEMS = 12;
@@ -454,6 +455,12 @@ export function Composer(props: ComposerProps) {
recalled.current = { index: -1, stash: "" };
return;
}
// macOS text boxes scroll on Home / End and leave the caret where it is.
if ((e.key === "Home" || e.key === "End") && !e.altKey && !e.ctrlKey && !e.metaKey && isMacHost()) {
e.preventDefault();
getSelection()?.modify(e.shiftKey ? "extend" : "move", e.key === "Home" ? "backward" : "forward", "lineboundary");
return;
}
if (e.key === "Tab" && e.shiftKey) {
const mode = modeOption(props.config);
const value = mode && nextValue(mode);
+31 -1
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { sendKeyAction } from "./Composer";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@@ -43,6 +43,7 @@ vi.mock("@/platform/tauriCore", async (importOriginal) => ({
import { isTauri } from "@/platform/ipc";
import { useProject } from "@/platform/project";
import { usePlatform } from "@/platform/platformInfo";
import type { ProjectInfo } from "@/bindings";
import { defaultTaskProject } from "./project";
import { HomeEditor } from "./HomeEditor";
@@ -243,6 +244,35 @@ describe("New Task composer commands", () => {
});
});
describe("New Task composer Home / End", () => {
const os = usePlatform.getState().os;
afterEach(() => {
usePlatform.setState({ os });
vi.restoreAllMocks();
});
it("moves the caret to the line's start or end on macOS, extending the selection with Shift", () => {
usePlatform.setState({ os: "macos" });
const modify = vi.fn();
vi.spyOn(window, "getSelection").mockReturnValue({ modify } as unknown as Selection);
render(<HomeEditor />);
const box = screen.getByRole("textbox");
expect(fireEvent.keyDown(box, { key: "Home" })).toBe(false);
fireEvent.keyDown(box, { key: "End", shiftKey: true });
fireEvent.keyDown(box, { key: "End", metaKey: true });
expect(modify.mock.calls).toEqual([
["move", "backward", "lineboundary"],
["extend", "forward", "lineboundary"],
]);
});
it("leaves Home / End to the text box elsewhere", () => {
usePlatform.setState({ os: "windows" });
render(<HomeEditor />);
expect(fireEvent.keyDown(screen.getByRole("textbox"), { key: "Home" })).toBe(true);
});
});
describe("New Task composer paste", () => {
it("attaches the clipboard's image when WebKitGTK pastes an empty clipboardData", async () => {
render(<HomeEditor />);