authorgravatar for 35393046+lnieuwenhuis@users.noreply.github.comLars Nieuwenhuis <35393046+lnieuwenhuis@users.noreply.github.com> 2026-04-01 17:19:12+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-01 15:19:12+00:00
log0ac59d569d6df6568e5750f056a53ad32fd678b1
tree13b574bc0d6ca67154eaeccad0b9ebcc08e9dba6
parentd8aa2f85dfd76dde3698754f195c9b4bb9c613a3
signature Signed by PGP key B5690EEEBB952194

Feat: Added Trae IDE support for opening current working directory (#1656)


5 files changed, 87 insertions(+), 2 deletions(-)

apps/server/src/open.test.ts+20-1
......@@ -31,6 +31,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
3131 args: ["/tmp/workspace"],
3232 });
3333
34 const traeLaunch = yield* resolveEditorLaunch(
35 { cwd: "/tmp/workspace", editor: "trae" },
36 "darwin",
37 );
38 assert.deepEqual(traeLaunch, {
39 command: "trae",
40 args: ["/tmp/workspace"],
41 });
42
3443 const vscodeLaunch = yield* resolveEditorLaunch(
3544 { cwd: "/tmp/workspace", editor: "vscode" },
3645 "darwin",
......@@ -89,6 +98,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
8998 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
9099 });
91100
101 const traeLineAndColumn = yield* resolveEditorLaunch(
102 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "trae" },
103 "darwin",
104 );
105 assert.deepEqual(traeLineAndColumn, {
106 command: "trae",
107 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
108 });
109
92110 const vscodeLineAndColumn = yield* resolveEditorLaunch(
93111 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },
94112 "darwin",
......@@ -256,6 +274,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
256274 const path = yield* Path.Path;
257275 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
258276
277 yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n");
259278 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
260279 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
261280 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
......@@ -263,7 +282,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
263282 PATH: dir,
264283 PATHEXT: ".COM;.EXE;.BAT;.CMD",
265284 });
266 assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]);
285 assert.deepEqual(editors, ["trae", "vscode-insiders", "vscodium", "file-manager"]);
267286 }),
268287 );
269288});
apps/web/src/components/ChatView.browser.tsx+46
......@@ -1407,6 +1407,52 @@ describe("ChatView timeline estimator parity (full app)", () => {
14071407 }
14081408 });
14091409
1410 it("opens the project cwd with Trae when it is the only available editor", async () => {
1411 setDraftThreadWithoutWorktree();
1412
1413 const mounted = await mountChatView({
1414 viewport: DEFAULT_VIEWPORT,
1415 snapshot: createDraftOnlySnapshot(),
1416 configureFixture: (nextFixture) => {
1417 nextFixture.serverConfig = {
1418 ...nextFixture.serverConfig,
1419 availableEditors: ["trae"],
1420 };
1421 },
1422 });
1423
1424 try {
1425 await waitForServerConfigToApply();
1426 const openButton = await waitForElement(
1427 () =>
1428 Array.from(document.querySelectorAll("button")).find(
1429 (button) => button.textContent?.trim() === "Open",
1430 ) as HTMLButtonElement | null,
1431 "Unable to find Open button.",
1432 );
1433 await vi.waitFor(() => {
1434 expect(openButton.disabled).toBe(false);
1435 });
1436 openButton.click();
1437
1438 await vi.waitFor(
1439 () => {
1440 const openRequest = wsRequests.find(
1441 (request) => request._tag === WS_METHODS.shellOpenInEditor,
1442 );
1443 expect(openRequest).toMatchObject({
1444 _tag: WS_METHODS.shellOpenInEditor,
1445 cwd: "/repo/project",
1446 editor: "trae",
1447 });
1448 },
1449 { timeout: 8_000, interval: 16 },
1450 );
1451 } finally {
1452 await mounted.cleanup();
1453 }
1454 });
1455
14101456 it("filters the open picker menu and opens VSCodium from the menu", async () => {
14111457 setDraftThreadWithoutWorktree();
14121458
apps/web/src/components/Icons.tsx+14
......@@ -20,6 +20,20 @@ export const CursorIcon: Icon = (props) => (
2020 </svg>
2121);
2222
23export const TraeIcon: Icon = (props) => (
24 <svg {...props} viewBox="0 0 24 24" fill="currentColor">
25 {/* Back rectangle: left strip + bottom strip drawn separately — empty bottom-left corner is the gap between them */}
26 <rect x="1" y="4" width="3" height="14" />
27 <rect x="4" y="18" width="18" height="3" />
28 {/* Front frame: top bar + right bar only — left and bottom are replaced by the back strips above */}
29 <rect x="4" y="4" width="18" height="3" />
30 <rect x="19" y="7" width="3" height="11" />
31 {/* Two diamonds, offset slightly to the right within the open area */}
32 <path d="M11 10L13 12L11 14L9 12Z" />
33 <path d="M16 10L18 12L16 14L14 12Z" />
34 </svg>
35);
36
2337export const VisualStudioCode: Icon = (props) => {
2438 const id = useId();
2539 const maskId = `${id}-vscode-a`;
apps/web/src/components/chat/OpenInPicker.tsx+6-1
......@@ -6,7 +6,7 @@ import { ChevronDownIcon, FolderClosedIcon } from "lucide-react";
66import { Button } from "../ui/button";
77import { Group, GroupSeparator } from "../ui/group";
88import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu";
9import { AntigravityIcon, CursorIcon, Icon, VisualStudioCode, Zed } from "../Icons";
9import { AntigravityIcon, CursorIcon, Icon, TraeIcon, VisualStudioCode, Zed } from "../Icons";
1010import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";
1111import { readNativeApi } from "~/nativeApi";
1212
......@@ -17,6 +17,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
1717 Icon: CursorIcon,
1818 value: "cursor",
1919 },
20 {
21 label: "Trae",
22 Icon: TraeIcon,
23 value: "trae",
24 },
2025 {
2126 label: "VS Code",
2227 Icon: VisualStudioCode,
packages/contracts/src/editor.ts+1
......@@ -3,6 +3,7 @@ import { TrimmedNonEmptyString } from "./baseSchemas";
33
44export const EDITORS = [
55 { id: "cursor", label: "Cursor", command: "cursor", supportsGoto: true },
6 { id: "trae", label: "Trae", command: "trae", supportsGoto: true },
67 { id: "vscode", label: "VS Code", command: "code", supportsGoto: true },
78 {
89 id: "vscode-insiders",