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
38 changes: 37 additions & 1 deletion apps/server/src/open.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,19 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
command: "zed",
args: ["/tmp/workspace"],
});

const ideaLaunch = yield* resolveEditorLaunch(
{ cwd: "/tmp/workspace", editor: "idea" },
"darwin",
);
assert.deepEqual(ideaLaunch, {
command: "idea",
args: ["/tmp/workspace"],
});
}),
);

it.effect("uses --goto when editor supports line/column suffixes", () =>
it.effect("applies launch-style-specific navigation arguments", () =>
Effect.gen(function* () {
const lineOnly = yield* resolveEditorLaunch(
{ cwd: "/tmp/workspace/AGENTS.md:48", editor: "cursor" },
Expand Down Expand Up @@ -142,6 +151,33 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
command: "zed",
args: ["/tmp/workspace/src/open.ts:71:5"],
});

const zedLineOnly = yield* resolveEditorLaunch(
{ cwd: "/tmp/workspace/AGENTS.md:48", editor: "zed" },
"darwin",
);
assert.deepEqual(zedLineOnly, {
command: "zed",
args: ["/tmp/workspace/AGENTS.md:48"],
});

const ideaLineOnly = yield* resolveEditorLaunch(
{ cwd: "/tmp/workspace/AGENTS.md:48", editor: "idea" },
"darwin",
);
assert.deepEqual(ideaLineOnly, {
command: "idea",
args: ["--line", "48", "/tmp/workspace/AGENTS.md"],
});

const ideaLineAndColumn = yield* resolveEditorLaunch(
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "idea" },
"darwin",
);
assert.deepEqual(ideaLineAndColumn, {
command: "idea",
args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
});
}),
);

Expand Down
48 changes: 42 additions & 6 deletions apps/server/src/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,45 @@ interface CommandAvailabilityOptions {
readonly env?: NodeJS.ProcessEnv;
}

const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
const TARGET_WITH_POSITION_PATTERN = /^(.*?):(\d+)(?::(\d+))?$/;

function parseTargetPathAndPosition(target: string): {
path: string;
line: string | undefined;
column: string | undefined;
} | null {
const match = TARGET_WITH_POSITION_PATTERN.exec(target);
if (!match?.[1] || !match[2]) {
return null;
}

function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean {
return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);
return {
path: match[1],
line: match[2],
column: match[3],
};
}

function resolveCommandEditorArgs(
editor: (typeof EDITORS)[number],
target: string,
): ReadonlyArray<string> {
const parsedTarget = parseTargetPathAndPosition(target);

switch (editor.launchStyle) {
case "direct-path":
return [target];
case "goto":
return parsedTarget ? ["--goto", target] : [target];
case "line-column": {
if (!parsedTarget) {
return [target];
}

const { path, line, column } = parsedTarget;
return [...(line ? ["--line", line] : []), ...(column ? ["--column", column] : []), path];
}
}
}

function fileManagerCommandForPlatform(platform: NodeJS.Platform): string {
Expand Down Expand Up @@ -208,9 +243,10 @@ export const resolveEditorLaunch = Effect.fnUntraced(function* (
}

if (editorDef.command) {
return shouldUseGotoFlag(editorDef, input.cwd)
? { command: editorDef.command, args: ["--goto", input.cwd] }
: { command: editorDef.command, args: [input.cwd] };
return {
command: editorDef.command,
args: resolveCommandEditorArgs(editorDef, input.cwd),
};
}

if (editorDef.id !== "file-manager") {
Expand Down
101 changes: 101 additions & 0 deletions apps/web/src/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,107 @@ export const AntigravityIcon: Icon = (props) => (
</svg>
);

export const IntelliJIdeaIcon: Icon = (props) => {
const id = useId();
const gradientAId = `${id}-idea-a`;
const gradientBId = `${id}-idea-b`;
const gradientCId = `${id}-idea-c`;
const gradientDId = `${id}-idea-d`;

return (
<svg {...props} viewBox="0 0 70 70" fill="none">
<defs>
<linearGradient
id={gradientAId}
x1="0.7898"
y1="40.0893"
x2="33.3172"
y2="40.0893"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.2581" stopColor="#F97A12" />
<stop offset="0.4591" stopColor="#B07B58" />
<stop offset="0.7241" stopColor="#577BAE" />
<stop offset="0.9105" stopColor="#1E7CE5" />
<stop offset="1" stopColor="#087CFA" />
</linearGradient>
<linearGradient
id={gradientBId}
x1="25.7674"
y1="24.88"
x2="79.424"
y2="54.57"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#F97A12" />
<stop offset="0.07179946" stopColor="#CB7A3E" />
<stop offset="0.1541" stopColor="#9E7B6A" />
<stop offset="0.242" stopColor="#757B91" />
<stop offset="0.3344" stopColor="#537BB1" />
<stop offset="0.4324" stopColor="#387CCC" />
<stop offset="0.5381" stopColor="#237CE0" />
<stop offset="0.6552" stopColor="#147CEF" />
<stop offset="0.7925" stopColor="#0B7CF7" />
<stop offset="1" stopColor="#087CFA" />
</linearGradient>
<linearGradient
id={gradientCId}
x1="63.2277"
y1="42.9153"
x2="48.2903"
y2="-1.7191"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FE315D" />
<stop offset="0.07840246" stopColor="#CB417E" />
<stop offset="0.1601" stopColor="#9E4E9B" />
<stop offset="0.2474" stopColor="#755BB4" />
<stop offset="0.3392" stopColor="#5365CA" />
<stop offset="0.4365" stopColor="#386DDB" />
<stop offset="0.5414" stopColor="#2374E9" />
<stop offset="0.6576" stopColor="#1478F3" />
<stop offset="0.794" stopColor="#0B7BF8" />
<stop offset="1" stopColor="#087CFA" />
</linearGradient>
<linearGradient
id={gradientDId}
x1="10.7204"
y1="16.473"
x2="55.5237"
y2="90.58"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FE315D" />
<stop offset="0.04023279" stopColor="#F63462" />
<stop offset="0.1037" stopColor="#DF3A71" />
<stop offset="0.1667" stopColor="#C24383" />
<stop offset="0.2912" stopColor="#AD4A91" />
<stop offset="0.5498" stopColor="#755BB4" />
<stop offset="0.9175" stopColor="#1D76ED" />
<stop offset="1" stopColor="#087CFA" />
</linearGradient>
</defs>
<polygon points="17.7,54.6 0.8,41.2 9.2,25.6 33.3,35" fill={`url(#${gradientAId})`} />
<path
d="M70 18.7 68.7 59.2 41.8 70 25.6 59.6 49.3 35 38.9 12.3 48.2 1.1Z"
fill={`url(#${gradientBId})`}
/>
<polygon points="70,18.7 48.7,43.9 38.9,12.3 48.2,1.1" fill={`url(#${gradientCId})`} />
<path
d="M33.7 58.1 5.6 68.3 10.1 52.5 16 33.1 0 27.7 10.1 0 32.1 2.7 53.7 27.4Z"
fill={`url(#${gradientDId})`}
/>
<rect x="13.7" y="13.5" width="43.2" height="43.2" fill="#000" />
<rect x="17.7" y="48.6" width="16.2" height="2.7" fill="#fff" />
<path d="M29.4 22.4v-3.3h-9v3.3h2.6v11.3h-2.6V37h9v-3.3h-2.5V22.4h2.5Z" fill="#fff" />
<path
d="M38 37.3c-1.4 0-2.6-.3-3.5-.8-.9-.5-1.7-1.2-2.3-1.9l2.5-2.8c.5.6 1 1 1.5 1.3.5.3 1.1.5 1.7.5.7 0 1.3-.2 1.8-.7.4-.5.6-1.2.6-2.3V19.1h4v11.7c0 1.1-.1 2-.4 2.8-.3.8-.7 1.4-1.3 2-.5.5-1.2 1-2 1.2-.8.3-1.6.5-2.6.5Z"
fill="#fff"
/>
</svg>
);
};

export const OpenCodeIcon: Icon = (props) => (
<svg {...props} viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#opencode__clip0_1311_94969)">
Expand Down
15 changes: 14 additions & 1 deletion apps/web/src/components/chat/OpenInPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ import { ChevronDownIcon, FolderClosedIcon } from "lucide-react";
import { Button } from "../ui/button";
import { Group, GroupSeparator } from "../ui/group";
import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu";
import { AntigravityIcon, CursorIcon, Icon, TraeIcon, VisualStudioCode, Zed } from "../Icons";
import {
AntigravityIcon,
CursorIcon,
Icon,
TraeIcon,
IntelliJIdeaIcon,
VisualStudioCode,
Zed,
} from "../Icons";
import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";
import { readNativeApi } from "~/nativeApi";

Expand Down Expand Up @@ -47,6 +55,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
Icon: AntigravityIcon,
value: "antigravity",
},
{
label: "IntelliJ IDEA",
Icon: IntelliJIdeaIcon,
value: "idea",
},
{
label: isMacPlatform(platform)
? "Finder"
Expand Down
29 changes: 20 additions & 9 deletions packages/contracts/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Schema } from "effect";
import { TrimmedNonEmptyString } from "./baseSchemas";

export const EditorLaunchStyle = Schema.Literals(["direct-path", "goto", "line-column"]);
export type EditorLaunchStyle = typeof EditorLaunchStyle.Type;

type EditorDefinition = {
readonly id: string;
readonly label: string;
readonly command: string | null;
readonly launchStyle: EditorLaunchStyle;
};

export const EDITORS = [
{ id: "cursor", label: "Cursor", command: "cursor", supportsGoto: true },
{ id: "trae", label: "Trae", command: "trae", supportsGoto: true },
{ id: "vscode", label: "VS Code", command: "code", supportsGoto: true },
{ id: "cursor", label: "Cursor", command: "cursor", launchStyle: "goto" },
{ id: "trae", label: "Trae", command: "trae", launchStyle: "goto" },
{ id: "vscode", label: "VS Code", command: "code", launchStyle: "goto" },
{
id: "vscode-insiders",
label: "VS Code Insiders",
command: "code-insiders",
supportsGoto: true,
launchStyle: "goto",
},
{ id: "vscodium", label: "VSCodium", command: "codium", supportsGoto: true },
{ id: "zed", label: "Zed", command: "zed", supportsGoto: false },
{ id: "antigravity", label: "Antigravity", command: "agy", supportsGoto: false },
{ id: "file-manager", label: "File Manager", command: null, supportsGoto: false },
] as const;
{ id: "vscodium", label: "VSCodium", command: "codium", launchStyle: "goto" },
{ id: "zed", label: "Zed", command: "zed", launchStyle: "direct-path" },
{ id: "antigravity", label: "Antigravity", command: "agy", launchStyle: "goto" },
{ id: "idea", label: "IntelliJ IDEA", command: "idea", launchStyle: "line-column" },
{ id: "file-manager", label: "File Manager", command: null, launchStyle: "direct-path" },
] as const satisfies ReadonlyArray<EditorDefinition>;

export const EditorId = Schema.Literals(EDITORS.map((e) => e.id));
export type EditorId = typeof EditorId.Type;
Expand Down
Loading