| author | |
| committer | |
| log | 0ac59d569d6df6568e5750f056a53ad32fd678b1 |
| tree | 13b574bc0d6ca67154eaeccad0b9ebcc08e9dba6 |
| parent | d8aa2f85dfd76dde3698754f195c9b4bb9c613a3 |
| signature | Signed by PGP key B5690EEEBB952194 |
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) => { |
| 31 | 31 | args: ["/tmp/workspace"], |
| 32 | 32 | }); |
| 33 | 33 | |
| 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 | ||
| 34 | 43 | const vscodeLaunch = yield* resolveEditorLaunch( |
| 35 | 44 | { cwd: "/tmp/workspace", editor: "vscode" }, |
| 36 | 45 | "darwin", |
| ... | ... | @@ -89,6 +98,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => { |
| 89 | 98 | args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], |
| 90 | 99 | }); |
| 91 | 100 | |
| 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 | ||
| 92 | 110 | const vscodeLineAndColumn = yield* resolveEditorLaunch( |
| 93 | 111 | { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" }, |
| 94 | 112 | "darwin", |
| ... | ... | @@ -256,6 +274,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => { |
| 256 | 274 | const path = yield* Path.Path; |
| 257 | 275 | const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" }); |
| 258 | 276 | |
| 277 | yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n"); | |
| 259 | 278 | yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n"); |
| 260 | 279 | yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n"); |
| 261 | 280 | yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ"); |
| ... | ... | @@ -263,7 +282,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => { |
| 263 | 282 | PATH: dir, |
| 264 | 283 | PATHEXT: ".COM;.EXE;.BAT;.CMD", |
| 265 | 284 | }); |
| 266 | assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]); | |
| 285 | assert.deepEqual(editors, ["trae", "vscode-insiders", "vscodium", "file-manager"]); | |
| 267 | 286 | }), |
| 268 | 287 | ); |
| 269 | 288 | }); |
apps/web/src/components/ChatView.browser.tsx+46| ... | ... | @@ -1407,6 +1407,52 @@ describe("ChatView timeline estimator parity (full app)", () => { |
| 1407 | 1407 | } |
| 1408 | 1408 | }); |
| 1409 | 1409 | |
| 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 | ||
| 1410 | 1456 | it("filters the open picker menu and opens VSCodium from the menu", async () => { |
| 1411 | 1457 | setDraftThreadWithoutWorktree(); |
| 1412 | 1458 |
apps/web/src/components/Icons.tsx+14| ... | ... | @@ -20,6 +20,20 @@ export const CursorIcon: Icon = (props) => ( |
| 20 | 20 | </svg> |
| 21 | 21 | ); |
| 22 | 22 | |
| 23 | export 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 | ||
| 23 | 37 | export const VisualStudioCode: Icon = (props) => { |
| 24 | 38 | const id = useId(); |
| 25 | 39 | const maskId = `${id}-vscode-a`; |
apps/web/src/components/chat/OpenInPicker.tsx+6-1| ... | ... | @@ -6,7 +6,7 @@ import { ChevronDownIcon, FolderClosedIcon } from "lucide-react"; |
| 6 | 6 | import { Button } from "../ui/button"; |
| 7 | 7 | import { Group, GroupSeparator } from "../ui/group"; |
| 8 | 8 | import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu"; |
| 9 | import { AntigravityIcon, CursorIcon, Icon, VisualStudioCode, Zed } from "../Icons"; | |
| 9 | import { AntigravityIcon, CursorIcon, Icon, TraeIcon, VisualStudioCode, Zed } from "../Icons"; | |
| 10 | 10 | import { isMacPlatform, isWindowsPlatform } from "~/lib/utils"; |
| 11 | 11 | import { readNativeApi } from "~/nativeApi"; |
| 12 | 12 | |
| ... | ... | @@ -17,6 +17,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor |
| 17 | 17 | Icon: CursorIcon, |
| 18 | 18 | value: "cursor", |
| 19 | 19 | }, |
| 20 | { | |
| 21 | label: "Trae", | |
| 22 | Icon: TraeIcon, | |
| 23 | value: "trae", | |
| 24 | }, | |
| 20 | 25 | { |
| 21 | 26 | label: "VS Code", |
| 22 | 27 | Icon: VisualStudioCode, |
packages/contracts/src/editor.ts+1| ... | ... | @@ -3,6 +3,7 @@ import { TrimmedNonEmptyString } from "./baseSchemas"; |
| 3 | 3 | |
| 4 | 4 | export const EDITORS = [ |
| 5 | 5 | { id: "cursor", label: "Cursor", command: "cursor", supportsGoto: true }, |
| 6 | { id: "trae", label: "Trae", command: "trae", supportsGoto: true }, | |
| 6 | 7 | { id: "vscode", label: "VS Code", command: "code", supportsGoto: true }, |
| 7 | 8 | { |
| 8 | 9 | id: "vscode-insiders", |