authorgravatar for 51682780+Minigamer42@users.noreply.github.comMinigamer42 <51682780+Minigamer42@users.noreply.github.com> 2026-05-06 06:12:52+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-06 04:12:52+00:00
log166bce0389e2068ae48c5936692eb315b17a269d
tree70820d04717b06816403e37a0b7a159118f379df
parent1498335e398f2cd23b8fd1133ca3ba020d9b8905
signature Signed by PGP key B5690EEEBB952194

Feature/intellij editors (#2475)


9 files changed, 977 insertions(+), 113 deletions(-)

apps/server/src/open.test.ts+227-1
......@@ -98,6 +98,105 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
9898 command: "idea",
9999 args: ["/tmp/workspace"],
100100 });
101
102 const aquaLaunch = yield* resolveEditorLaunch(
103 { cwd: "/tmp/workspace", editor: "aqua" },
104 "darwin",
105 );
106 assert.deepEqual(aquaLaunch, {
107 command: "aqua",
108 args: ["/tmp/workspace"],
109 });
110
111 const clionLaunch = yield* resolveEditorLaunch(
112 { cwd: "/tmp/workspace", editor: "clion" },
113 "darwin",
114 );
115 assert.deepEqual(clionLaunch, {
116 command: "clion",
117 args: ["/tmp/workspace"],
118 });
119
120 const datagripLaunch = yield* resolveEditorLaunch(
121 { cwd: "/tmp/workspace", editor: "datagrip" },
122 "darwin",
123 );
124 assert.deepEqual(datagripLaunch, {
125 command: "datagrip",
126 args: ["/tmp/workspace"],
127 });
128
129 const dataspellLaunch = yield* resolveEditorLaunch(
130 { cwd: "/tmp/workspace", editor: "dataspell" },
131 "darwin",
132 );
133 assert.deepEqual(dataspellLaunch, {
134 command: "dataspell",
135 args: ["/tmp/workspace"],
136 });
137
138 const golandLaunch = yield* resolveEditorLaunch(
139 { cwd: "/tmp/workspace", editor: "goland" },
140 "darwin",
141 );
142 assert.deepEqual(golandLaunch, {
143 command: "goland",
144 args: ["/tmp/workspace"],
145 });
146
147 const phpstormLaunch = yield* resolveEditorLaunch(
148 { cwd: "/tmp/workspace", editor: "phpstorm" },
149 "darwin",
150 );
151 assert.deepEqual(phpstormLaunch, {
152 command: "phpstorm",
153 args: ["/tmp/workspace"],
154 });
155
156 const pycharmLaunch = yield* resolveEditorLaunch(
157 { cwd: "/tmp/workspace", editor: "pycharm" },
158 "darwin",
159 );
160 assert.deepEqual(pycharmLaunch, {
161 command: "pycharm",
162 args: ["/tmp/workspace"],
163 });
164
165 const riderLaunch = yield* resolveEditorLaunch(
166 { cwd: "/tmp/workspace", editor: "rider" },
167 "darwin",
168 );
169 assert.deepEqual(riderLaunch, {
170 command: "rider",
171 args: ["/tmp/workspace"],
172 });
173
174 const rubymineLaunch = yield* resolveEditorLaunch(
175 { cwd: "/tmp/workspace", editor: "rubymine" },
176 "darwin",
177 );
178 assert.deepEqual(rubymineLaunch, {
179 command: "rubymine",
180 args: ["/tmp/workspace"],
181 });
182
183 const rustroverLaunch = yield* resolveEditorLaunch(
184 { cwd: "/tmp/workspace", editor: "rustrover" },
185 "darwin",
186 );
187 assert.deepEqual(rustroverLaunch, {
188 command: "rustrover",
189 args: ["/tmp/workspace"],
190 });
191
192 const webstormLaunch = yield* resolveEditorLaunch(
193 { cwd: "/tmp/workspace", editor: "webstorm" },
194 "darwin",
195 );
196 assert.deepEqual(webstormLaunch, {
197 command: "webstorm",
198 args: ["/tmp/workspace"],
199 });
101200 }),
102201 );
103202
......@@ -207,6 +306,105 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
207306 command: "idea",
208307 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
209308 });
309
310 const aquaLineAndColumn = yield* resolveEditorLaunch(
311 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "aqua" },
312 "darwin",
313 );
314 assert.deepEqual(aquaLineAndColumn, {
315 command: "aqua",
316 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
317 });
318
319 const clionLineAndColumn = yield* resolveEditorLaunch(
320 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "clion" },
321 "darwin",
322 );
323 assert.deepEqual(clionLineAndColumn, {
324 command: "clion",
325 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
326 });
327
328 const datagripLineAndColumn = yield* resolveEditorLaunch(
329 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "datagrip" },
330 "darwin",
331 );
332 assert.deepEqual(datagripLineAndColumn, {
333 command: "datagrip",
334 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
335 });
336
337 const dataspellLineAndColumn = yield* resolveEditorLaunch(
338 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "dataspell" },
339 "darwin",
340 );
341 assert.deepEqual(dataspellLineAndColumn, {
342 command: "dataspell",
343 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
344 });
345
346 const golandLineAndColumn = yield* resolveEditorLaunch(
347 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "goland" },
348 "darwin",
349 );
350 assert.deepEqual(golandLineAndColumn, {
351 command: "goland",
352 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
353 });
354
355 const phpstormLineAndColumn = yield* resolveEditorLaunch(
356 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "phpstorm" },
357 "darwin",
358 );
359 assert.deepEqual(phpstormLineAndColumn, {
360 command: "phpstorm",
361 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
362 });
363
364 const pycharmLineAndColumn = yield* resolveEditorLaunch(
365 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "pycharm" },
366 "darwin",
367 );
368 assert.deepEqual(pycharmLineAndColumn, {
369 command: "pycharm",
370 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
371 });
372
373 const riderLineAndColumn = yield* resolveEditorLaunch(
374 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "rider" },
375 "darwin",
376 );
377 assert.deepEqual(riderLineAndColumn, {
378 command: "rider",
379 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
380 });
381
382 const rubymineLineAndColumn = yield* resolveEditorLaunch(
383 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "rubymine" },
384 "darwin",
385 );
386 assert.deepEqual(rubymineLineAndColumn, {
387 command: "rubymine",
388 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
389 });
390
391 const rustroverLineAndColumn = yield* resolveEditorLaunch(
392 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "rustrover" },
393 "darwin",
394 );
395 assert.deepEqual(rustroverLineAndColumn, {
396 command: "rustrover",
397 args: ["--line", "71", "--column", "5", "/tmp/workspace/src/open.ts"],
398 });
399
400 const webstormLineOnly = yield* resolveEditorLaunch(
401 { cwd: "/tmp/workspace/AGENTS.md:48", editor: "webstorm" },
402 "darwin",
403 );
404 assert.deepEqual(webstormLineOnly, {
405 command: "webstorm",
406 args: ["--line", "48", "/tmp/workspace/AGENTS.md"],
407 });
210408 }),
211409 );
212410
......@@ -377,12 +575,40 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
377575 yield* fs.writeFileString(path.join(dir, "kiro.CMD"), "@echo off\r\n");
378576 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
379577 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
578 yield* fs.writeFileString(path.join(dir, "aqua.CMD"), "@echo off\r\n");
579 yield* fs.writeFileString(path.join(dir, "clion.CMD"), "@echo off\r\n");
580 yield* fs.writeFileString(path.join(dir, "datagrip.CMD"), "@echo off\r\n");
581 yield* fs.writeFileString(path.join(dir, "dataspell.CMD"), "@echo off\r\n");
582 yield* fs.writeFileString(path.join(dir, "goland.CMD"), "@echo off\r\n");
583 yield* fs.writeFileString(path.join(dir, "phpstorm.CMD"), "@echo off\r\n");
584 yield* fs.writeFileString(path.join(dir, "pycharm.CMD"), "@echo off\r\n");
585 yield* fs.writeFileString(path.join(dir, "rider.CMD"), "@echo off\r\n");
586 yield* fs.writeFileString(path.join(dir, "rubymine.CMD"), "@echo off\r\n");
587 yield* fs.writeFileString(path.join(dir, "rustrover.CMD"), "@echo off\r\n");
588 yield* fs.writeFileString(path.join(dir, "webstorm.CMD"), "@echo off\r\n");
380589 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
381590 const editors = resolveAvailableEditors("win32", {
382591 PATH: dir,
383592 PATHEXT: ".COM;.EXE;.BAT;.CMD",
384593 });
385 assert.deepEqual(editors, ["trae", "kiro", "vscode-insiders", "vscodium", "file-manager"]);
594 assert.deepEqual(editors, [
595 "trae",
596 "kiro",
597 "vscode-insiders",
598 "vscodium",
599 "aqua",
600 "clion",
601 "datagrip",
602 "dataspell",
603 "goland",
604 "phpstorm",
605 "pycharm",
606 "rider",
607 "rubymine",
608 "rustrover",
609 "webstorm",
610 "file-manager",
611 ]);
386612 }),
387613 );
388614
apps/web/src/components/ChatMarkdown.browser.tsx+2-2
......@@ -65,7 +65,7 @@ describe("ChatMarkdown", () => {
6565 try {
6666 const link = page.getByRole("link", { name: "PermissionRule.ts · L1" });
6767 await expect.element(link).toBeInTheDocument();
68 await expect.element(link).toHaveAttribute("href", `${filePath}#L1`);
68 await expect.element(link).toHaveAttribute("href", `${filePath}:1`);
6969
7070 await link.click();
7171
......@@ -87,7 +87,7 @@ describe("ChatMarkdown", () => {
8787 try {
8888 const link = page.getByRole("link", { name: "PermissionRule.ts · L1:C7" });
8989 await expect.element(link).toBeInTheDocument();
90 await expect.element(link).toHaveAttribute("href", `${filePath}#L1C7`);
90 await expect.element(link).toHaveAttribute("href", `${filePath}:1:7`);
9191
9292 await link.click();
9393
apps/web/src/components/ChatMarkdown.tsx+8-3
......@@ -26,7 +26,11 @@ import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering";
2626import { fnv1a32 } from "../lib/diffRendering";
2727import { LRUCache } from "../lib/lruCache";
2828import { useTheme } from "../hooks/useTheme";
29import { resolveMarkdownFileLinkMeta, rewriteMarkdownFileUriHref } from "../markdown-links";
29import {
30 normalizeMarkdownLinkDestination,
31 resolveMarkdownFileLinkMeta,
32 rewriteMarkdownFileUriHref,
33} from "../markdown-links";
3034import { readLocalApi } from "../localApi";
3135import { cn } from "../lib/utils";
3236
......@@ -354,7 +358,8 @@ function extractMarkdownLinkHrefs(text: string): string[] {
354358}
355359
356360function normalizeMarkdownLinkHrefKey(href: string): string {
357 return rewriteMarkdownFileUriHref(href.trim()) ?? href.trim();
361 const normalizedHref = normalizeMarkdownLinkDestination(href);
362 return rewriteMarkdownFileUriHref(normalizedHref) ?? normalizedHref;
358363}
359364
360365const MarkdownFileLink = memo(function MarkdownFileLink({
......@@ -549,7 +554,7 @@ function ChatMarkdown({ text, cwd, isStreaming = false }: ChatMarkdownProps) {
549554
550555 return (
551556 <MarkdownFileLink
552 href={href ?? fileLinkMeta.targetPath}
557 href={fileLinkMeta.targetPath}
553558 targetPath={fileLinkMeta.targetPath}
554559 displayPath={fileLinkMeta.displayPath}
555560 filePath={fileLinkMeta.filePath}
apps/web/src/components/Icons.tsx+1-102
......@@ -1,4 +1,4 @@
1import { type SVGProps, useId } from "react";
1import React, { type SVGProps, useId } from "react";
22import { cn } from "~/lib/utils";
33
44export type Icon = React.FC<SVGProps<SVGSVGElement>>;
......@@ -635,107 +635,6 @@ export const AntigravityIcon: Icon = (props) => (
635635 </svg>
636636);
637637
638export const IntelliJIdeaIcon: Icon = (props) => {
639 const id = useId();
640 const gradientAId = `${id}-idea-a`;
641 const gradientBId = `${id}-idea-b`;
642 const gradientCId = `${id}-idea-c`;
643 const gradientDId = `${id}-idea-d`;
644
645 return (
646 <svg {...props} viewBox="0 0 70 70" fill="none">
647 <defs>
648 <linearGradient
649 id={gradientAId}
650 x1="0.7898"
651 y1="40.0893"
652 x2="33.3172"
653 y2="40.0893"
654 gradientUnits="userSpaceOnUse"
655 >
656 <stop offset="0.2581" stopColor="#F97A12" />
657 <stop offset="0.4591" stopColor="#B07B58" />
658 <stop offset="0.7241" stopColor="#577BAE" />
659 <stop offset="0.9105" stopColor="#1E7CE5" />
660 <stop offset="1" stopColor="#087CFA" />
661 </linearGradient>
662 <linearGradient
663 id={gradientBId}
664 x1="25.7674"
665 y1="24.88"
666 x2="79.424"
667 y2="54.57"
668 gradientUnits="userSpaceOnUse"
669 >
670 <stop stopColor="#F97A12" />
671 <stop offset="0.07179946" stopColor="#CB7A3E" />
672 <stop offset="0.1541" stopColor="#9E7B6A" />
673 <stop offset="0.242" stopColor="#757B91" />
674 <stop offset="0.3344" stopColor="#537BB1" />
675 <stop offset="0.4324" stopColor="#387CCC" />
676 <stop offset="0.5381" stopColor="#237CE0" />
677 <stop offset="0.6552" stopColor="#147CEF" />
678 <stop offset="0.7925" stopColor="#0B7CF7" />
679 <stop offset="1" stopColor="#087CFA" />
680 </linearGradient>
681 <linearGradient
682 id={gradientCId}
683 x1="63.2277"
684 y1="42.9153"
685 x2="48.2903"
686 y2="-1.7191"
687 gradientUnits="userSpaceOnUse"
688 >
689 <stop stopColor="#FE315D" />
690 <stop offset="0.07840246" stopColor="#CB417E" />
691 <stop offset="0.1601" stopColor="#9E4E9B" />
692 <stop offset="0.2474" stopColor="#755BB4" />
693 <stop offset="0.3392" stopColor="#5365CA" />
694 <stop offset="0.4365" stopColor="#386DDB" />
695 <stop offset="0.5414" stopColor="#2374E9" />
696 <stop offset="0.6576" stopColor="#1478F3" />
697 <stop offset="0.794" stopColor="#0B7BF8" />
698 <stop offset="1" stopColor="#087CFA" />
699 </linearGradient>
700 <linearGradient
701 id={gradientDId}
702 x1="10.7204"
703 y1="16.473"
704 x2="55.5237"
705 y2="90.58"
706 gradientUnits="userSpaceOnUse"
707 >
708 <stop stopColor="#FE315D" />
709 <stop offset="0.04023279" stopColor="#F63462" />
710 <stop offset="0.1037" stopColor="#DF3A71" />
711 <stop offset="0.1667" stopColor="#C24383" />
712 <stop offset="0.2912" stopColor="#AD4A91" />
713 <stop offset="0.5498" stopColor="#755BB4" />
714 <stop offset="0.9175" stopColor="#1D76ED" />
715 <stop offset="1" stopColor="#087CFA" />
716 </linearGradient>
717 </defs>
718 <polygon points="17.7,54.6 0.8,41.2 9.2,25.6 33.3,35" fill={`url(#${gradientAId})`} />
719 <path
720 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"
721 fill={`url(#${gradientBId})`}
722 />
723 <polygon points="70,18.7 48.7,43.9 38.9,12.3 48.2,1.1" fill={`url(#${gradientCId})`} />
724 <path
725 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"
726 fill={`url(#${gradientDId})`}
727 />
728 <rect x="13.7" y="13.5" width="43.2" height="43.2" fill="#000" />
729 <rect x="17.7" y="48.6" width="16.2" height="2.7" fill="#fff" />
730 <path d="M29.4 22.4v-3.3h-9v3.3h2.6v11.3h-2.6V37h9v-3.3h-2.5V22.4h2.5Z" fill="#fff" />
731 <path
732 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"
733 fill="#fff"
734 />
735 </svg>
736 );
737};
738
739638export const OpenCodeIcon: Icon = (props) => (
740639 <svg {...props} viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
741640 <g clipPath="url(#opencode__clip0_1311_94969)">
apps/web/src/components/JetBrainsIcons.tsx created+610
......@@ -0,0 +1,610 @@
1import { useId } from "react";
2import type { Icon } from "./Icons";
3
4const useSvgGradientIds = (prefix: string, count: number) => {
5 const id = useId();
6 return Array.from(
7 { length: count },
8 (_, index) => `${id}-${prefix}-${String.fromCharCode(97 + index)}`,
9 );
10};
11
12export const AquaIcon: Icon = (props) => {
13 const [gradientAId, gradientBId] = useSvgGradientIds("aqua", 2);
14
15 return (
16 <svg {...props} viewBox="0 0 64 64" fill="none">
17 <defs>
18 <linearGradient
19 id={gradientAId}
20 x1="59.932"
21 x2="1.336"
22 y1="59.676"
23 y2="1.079"
24 gradientUnits="userSpaceOnUse"
25 >
26 <stop offset=".25" stopColor="#7256FF" />
27 <stop offset=".73" stopColor="#007DFE" />
28 </linearGradient>
29 <linearGradient
30 id={gradientBId}
31 x1="7.671"
32 x2="61.125"
33 y1="64.392"
34 y2="39.609"
35 gradientUnits="userSpaceOnUse"
36 >
37 <stop offset=".3" stopColor="#00D886" />
38 <stop offset=".54" stopColor="#7256FF" />
39 </linearGradient>
40 </defs>
41 <path
42 fill={`url(#${gradientAId})`}
43 d="M0 4.125v34.128c0 1.658.993 3.154 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.13 4.13 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125"
44 />
45 <path
46 fill={`url(#${gradientBId})`}
47 d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566q.3 0 .598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.13 4.13 0 0 0 6 49.015z"
48 />
49 <path
50 fill="#00D886"
51 d="M6 47.55v12.259a4.125 4.125 0 0 0 4.19 4.124L21 64c1.181-.019 2.531-.786 3.3-1.683l32.707-38.158c.64-.748.993-1.7.993-2.685V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.834A4.12 4.12 0 0 0 6 47.55"
52 />
53 <path fill="#000" d="M52 12H12v40h40z" />
54 <path
55 fill="#fff"
56 d="M33 44H17v3h16zM20.746 31.243a7.3 7.3 0 0 1-2.755-2.787q-1.002-1.774-1.003-3.961c0-2.187.334-2.778 1.003-3.96a7.3 7.3 0 0 1 2.755-2.787q1.752-1.013 3.917-1.014c1.443 0 2.739.338 3.907 1.013a7.3 7.3 0 0 1 2.75 2.787q.996 1.774.996 3.961c0 2.187-.332 2.778-.997 3.96a7.3 7.3 0 0 1-2.749 2.788q-1.752 1.013-3.907 1.013c-1.436 0-2.75-.338-3.917-1.013m6.308-2.23q1.06-.67 1.661-1.854.6-1.185.6-2.664t-.6-2.664a4.6 4.6 0 0 0-1.661-1.854q-1.061-.67-2.39-.67t-2.396.67a4.6 4.6 0 0 0-1.672 1.854q-.605 1.185-.605 2.664t.605 2.664 1.672 1.854q1.067.67 2.396.67 1.33 0 2.39-.67m-2.16 6.034a2.67 2.67 0 0 1-1.126-1.077q-.391-.696-.391-1.64l-.01-1.94h2.743v1.811q0 .332.129.573.128.242.37.365.24.123.574.123h1.833v2.165h-2.412q-.975 0-1.71-.38M38.233 16.992h3.173l5.563 15.006h-2.947l-1.212-3.483h-5.842l-1.136 3.483H32.82zm3.805 9.261-2.025-5.723-.225-.954-.215.954-1.908 5.723z"
57 />
58 </svg>
59 );
60};
61
62export const CLionIcon: Icon = (props) => {
63 const [gradientAId, gradientBId] = useSvgGradientIds("clion", 2);
64
65 return (
66 <svg {...props} viewBox="0 0 64 64" fill="none">
67 <defs>
68 <linearGradient
69 id={gradientAId}
70 x1="4.067"
71 x2="62.664"
72 y1="4.327"
73 y2="62.923"
74 gradientUnits="userSpaceOnUse"
75 >
76 <stop offset=".29" stopColor="#009AE5" />
77 <stop offset=".7" stopColor="#00D980" />
78 </linearGradient>
79 <linearGradient
80 id={gradientBId}
81 x1="56.329"
82 x2="2.874"
83 y1="-.391"
84 y2="24.393"
85 gradientUnits="userSpaceOnUse"
86 >
87 <stop offset=".3" stopColor="#FF2D90" />
88 <stop offset=".54" stopColor="#009AE5" />
89 </linearGradient>
90 </defs>
91 <path
92 fill={`url(#${gradientAId})`}
93 d="M64 59.878V25.75a4.13 4.13 0 0 0-2.52-3.8L24.057 6.153a4.1 4.1 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.12 4.12 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878"
94 />
95 <path
96 fill={`url(#${gradientBId})`}
97 d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309q-.3 0-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.13 4.13 0 0 0 58 14.988"
98 />
99 <path
100 fill="#FF2D90"
101 d="M58 16.453V4.194A4.125 4.125 0 0 0 53.81.07L43.003.008c-1.18.019-2.535.781-3.304 1.678L6.993 39.844c-.64.748-.993 1.7-.993 2.684v11.35a4.125 4.125 0 0 0 4.125 4.125h11.003c1.19 0 2.321-.514 3.105-1.409L56.979 19.17A4.12 4.12 0 0 0 58 16.453"
102 />
103 <path fill="#000" d="M52 12H12v40h40z" />
104 <path
105 fill="#fff"
106 d="M33 44H17v3h16zM20.747 31.243a7.3 7.3 0 0 1-2.744-2.787q-.997-1.774-.997-3.961c0-2.187.332-2.778.997-3.96a7.3 7.3 0 0 1 2.744-2.787q1.747-1.013 3.901-1.014 1.823 0 3.345.675a6.86 6.86 0 0 1 2.535 1.892 6.44 6.44 0 0 1 1.355 2.793h-3.065a4.05 4.05 0 0 0-.895-1.431 4 4 0 0 0-1.431-.95 4.9 4.9 0 0 0-1.822-.332q-1.33 0-2.402.665a4.6 4.6 0 0 0-1.677 1.827q-.606 1.164-.606 2.62 0 1.459.606 2.621a4.6 4.6 0 0 0 1.677 1.828q1.072.664 2.402.664a4.9 4.9 0 0 0 1.822-.332 4.02 4.02 0 0 0 2.326-2.38h3.065a6.44 6.44 0 0 1-1.355 2.792 6.86 6.86 0 0 1-2.535 1.892q-1.522.675-3.345.675-2.154 0-3.901-1.013zM37.33 16.992v12.37h7.117v2.636H34.414V16.992z"
107 />
108 </svg>
109 );
110};
111
112export const DataGripIcon: Icon = (props) => {
113 const [gradientAId, gradientBId] = useSvgGradientIds("datagrip", 2);
114
115 return (
116 <svg {...props} viewBox="0 0 64 64" fill="none">
117 <defs>
118 <linearGradient
119 id={gradientAId}
120 x1="59.676"
121 x2="1.08"
122 y1="4.067"
123 y2="62.663"
124 gradientUnits="userSpaceOnUse"
125 >
126 <stop offset=".28" stopColor="#7256FF" />
127 <stop offset=".66" stopColor="#00D980" />
128 </linearGradient>
129 <linearGradient
130 id={gradientBId}
131 x1="64.391"
132 x2="39.607"
133 y1="56.329"
134 y2="2.874"
135 gradientUnits="userSpaceOnUse"
136 >
137 <stop offset=".3" stopColor="#FF43F2" />
138 <stop offset=".54" stopColor="#7256FF" />
139 </linearGradient>
140 </defs>
141 <path
142 fill={`url(#${gradientAId})`}
143 d="M4.125 64h34.128a4.13 4.13 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.125 4.125 0 0 0 53.9 0H36.234c-.806 0-1.593.236-2.267.678L1.86 21.787A4.13 4.13 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64"
144 />
145 <path
146 fill={`url(#${gradientBId})`}
147 d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309q0-.3-.044-.598L58.508 3.527A4.125 4.125 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.13 4.13 0 0 0 3.93 2.87"
148 />
149 <path
150 fill="#FF43F2"
151 d="M47.55 58h12.259a4.125 4.125 0 0 0 4.124-4.19L64 43c-.018-1.181-.785-2.531-1.682-3.3L24.159 6.993A4.13 4.13 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.105l37.425 32.746A4.12 4.12 0 0 0 47.55 58"
152 />
153 <path fill="#000" d="M52 12H12v40h40z" />
154 <path
155 fill="#fff"
156 d="M33 44H17v3h16zM17.012 16.992h5.713q2.133 0 3.821.965a6.9 6.9 0 0 1 2.642 2.674q.954 1.71.954 3.864t-.954 3.865a6.9 6.9 0 0 1-2.642 2.674q-1.688.964-3.821.964h-5.713zm8.028 11.876q1.008-.6 1.554-1.72.547-1.12.547-2.653 0-1.534-.547-2.653-.546-1.12-1.554-1.721-1.008-.6-2.348-.6h-2.755v9.947h2.755q1.34 0 2.348-.6M35.426 31.243a7.3 7.3 0 0 1-2.744-2.787q-.997-1.774-.997-3.961c0-2.187.333-2.778.997-3.96s1.58-2.112 2.744-2.787q1.747-1.013 3.902-1.014 1.746 0 3.221.622a6.9 6.9 0 0 1 2.487 1.747 6.36 6.36 0 0 1 1.42 2.594h-3.13a3.9 3.9 0 0 0-.927-1.228q-.585-.52-1.367-.803c-.782-.283-1.082-.284-1.683-.284q-1.329 0-2.401.664a4.6 4.6 0 0 0-1.678 1.828q-.605 1.163-.605 2.62c0 1.457.202 1.846.605 2.621a4.6 4.6 0 0 0 1.678 1.828q1.072.664 2.401.664 1.233 0 2.235-.461t1.592-1.276a3.3 3.3 0 0 0 .633-1.833l.01.31h-3.526v-2.304h6.356v1.18q0 1.982-.96 3.585a6.9 6.9 0 0 1-2.625 2.525q-1.668.921-3.736.921c-1.38 0-2.737-.337-3.902-1.013z"
157 />
158 </svg>
159 );
160};
161
162export const DataSpellIcon: Icon = (props) => {
163 const [gradientAId, gradientBId] = useSvgGradientIds("dataspell", 2);
164
165 return (
166 <svg {...props} viewBox="0 0 64 64" fill="none">
167 <defs>
168 <linearGradient
169 id={gradientAId}
170 x1="4.067"
171 x2="62.664"
172 y1="4.327"
173 y2="62.923"
174 gradientUnits="userSpaceOnUse"
175 >
176 <stop offset=".28" stopColor="#007DFE" />
177 <stop offset=".73" stopColor="#00D980" />
178 </linearGradient>
179 <linearGradient
180 id={gradientBId}
181 x1="56.329"
182 x2="2.875"
183 y1="-.391"
184 y2="24.392"
185 gradientUnits="userSpaceOnUse"
186 >
187 <stop offset=".32" stopColor="#F0EB18" />
188 <stop offset=".55" stopColor="#007DFE" />
189 </linearGradient>
190 </defs>
191 <path
192 fill={`url(#${gradientAId})`}
193 d="M64 59.878V25.75a4.13 4.13 0 0 0-2.52-3.8L24.057 6.153a4.1 4.1 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.12 4.12 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878"
194 />
195 <path
196 fill={`url(#${gradientBId})`}
197 d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309q-.3 0-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.13 4.13 0 0 0 58 14.988"
198 />
199 <path
200 fill="#F0EB18"
201 d="M58 16.45V4.191A4.125 4.125 0 0 0 53.81.067L43 0c-1.181.019-2.531.786-3.3 1.683L6.993 39.84c-.64.748-.993 1.7-.993 2.685v11.349A4.125 4.125 0 0 0 10.125 58h11.003c1.19 0 2.321-.514 3.105-1.409l32.746-37.425A4.12 4.12 0 0 0 58 16.45"
202 />
203 <path fill="#000" d="M52 12H12v40h40z" />
204 <path
205 fill="#fff"
206 d="M33 44H17v3h16zM17.012 16.992h5.713q2.133 0 3.821.965a6.9 6.9 0 0 1 2.642 2.674q.954 1.71.954 3.864t-.954 3.865a6.9 6.9 0 0 1-2.642 2.674q-1.688.964-3.821.964h-5.713zm8.028 11.876q1.008-.6 1.554-1.72.547-1.12.547-2.653 0-1.534-.547-2.653-.546-1.12-1.554-1.721-1.008-.6-2.348-.6h-2.755v9.947h2.755q1.34 0 2.348-.6M34.713 31.664q-1.26-.59-1.973-1.65t-.734-2.444h2.937q0 .654.343 1.147t.954.772q.61.278 1.404.278c.53 0 .952-.083 1.334-.251q.574-.252.89-.702t.316-1.029q0-.718-.434-1.19-.435-.471-1.195-.654l-2.648-.59q-1.071-.234-1.865-.809a4 4 0 0 1-1.232-1.42q-.44-.846-.44-1.908 0-1.286.665-2.31.664-1.023 1.854-1.597 1.19-.573 2.701-.573c1.511 0 1.933.186 2.733.558s1.424.888 1.871 1.549q.67.99.68 2.277h-2.926q0-.546-.289-.98c-.289-.435-.466-.515-.82-.676a2.9 2.9 0 0 0-1.217-.24q-.686 0-1.205.23a1.9 1.9 0 0 0-.81.643q-.29.412-.29.96 0 .621.403 1.028.402.407 1.098.579l2.552.557q1.103.226 1.967.852a4.45 4.45 0 0 1 1.345 1.544q.481.917.482 1.999 0 1.329-.702 2.385-.702 1.054-1.967 1.656-1.265.6-2.894.6t-2.889-.59"
207 />
208 </svg>
209 );
210};
211
212export const GoLandIcon: Icon = (props) => {
213 const [gradientAId, gradientBId] = useSvgGradientIds("goland", 2);
214
215 return (
216 <svg {...props} viewBox="0 0 64 64" fill="none">
217 <defs>
218 <linearGradient
219 id={gradientAId}
220 x1="64.391"
221 x2="39.607"
222 y1="56.329"
223 y2="2.874"
224 gradientUnits="userSpaceOnUse"
225 >
226 <stop offset=".24" stopColor="#00D886" />
227 <stop offset=".51" stopColor="#007DFE" />
228 </linearGradient>
229 <linearGradient
230 id={gradientBId}
231 x1="59.676"
232 x2="1.08"
233 y1="4.067"
234 y2="62.663"
235 gradientUnits="userSpaceOnUse"
236 >
237 <stop offset=".27" stopColor="#007DFE" />
238 <stop offset=".7" stopColor="#D249FC" />
239 </linearGradient>
240 </defs>
241 <path
242 fill="#00D886"
243 d="M47.55 58h12.259a4.125 4.125 0 0 0 4.124-4.19l-.176-11.044a4.13 4.13 0 0 0-1.44-3.066L24.159 6.993A4.13 4.13 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.105l37.425 32.746A4.12 4.12 0 0 0 47.55 58"
244 />
245 <path
246 fill={`url(#${gradientAId})`}
247 d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309q0-.3-.044-.598L58.508 3.527A4.124 4.124 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.13 4.13 0 0 0 3.93 2.87"
248 />
249 <path
250 fill={`url(#${gradientBId})`}
251 d="M4.125 64h34.128a4.13 4.13 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.124 4.124 0 0 0 53.9 0H36.234c-.805 0-1.593.236-2.266.678L1.86 21.787A4.13 4.13 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64"
252 />
253 <path fill="#000" d="M52 12H12v40h40z" />
254 <path
255 fill="#fff"
256 d="M19.748 31.243a7.3 7.3 0 0 1-2.743-2.787q-.997-1.774-.997-3.961c0-2.187.332-2.778.997-3.96s1.58-2.112 2.743-2.787q1.747-1.013 3.902-1.014 1.747 0 3.222.622a6.9 6.9 0 0 1 2.486 1.747 6.4 6.4 0 0 1 1.42 2.594h-3.13a3.9 3.9 0 0 0-.926-1.228q-.584-.52-1.367-.803c-.783-.283-1.083-.284-1.683-.284q-1.33 0-2.402.664a4.6 4.6 0 0 0-1.677 1.828q-.606 1.163-.606 2.62c0 1.457.202 1.846.606 2.621a4.6 4.6 0 0 0 1.677 1.828q1.072.664 2.402.664 1.232 0 2.235-.461t1.591-1.276a3.3 3.3 0 0 0 .633-1.833l.01.31h-3.526v-2.304h6.357v1.18q0 1.982-.96 3.585a6.9 6.9 0 0 1-2.626 2.525q-1.666.921-3.736.921c-2.07 0-2.737-.337-3.902-1.013zM36.271 31.243a7.3 7.3 0 0 1-2.755-2.787q-1.002-1.774-1.002-3.961c0-2.187.333-2.778 1.002-3.96a7.3 7.3 0 0 1 2.755-2.787q1.752-1.013 3.918-1.014c1.443 0 2.738.338 3.907 1.013a7.3 7.3 0 0 1 2.749 2.787q.996 1.774.997 3.961c0 2.187-.333 2.778-.997 3.96s-1.581 2.113-2.75 2.788q-1.752 1.013-3.906 1.013c-1.437 0-2.75-.338-3.918-1.013m6.308-2.23q1.062-.67 1.662-1.854t.6-2.664-.6-2.664-1.662-1.854-2.39-.67-2.395.67a4.6 4.6 0 0 0-1.672 1.854q-.606 1.185-.606 2.664t.606 2.664 1.672 1.854 2.395.67q1.33 0 2.39-.67M33 44H17v3h16z"
257 />
258 </svg>
259 );
260};
261
262export const IntelliJIdeaIcon: Icon = (props) => {
263 const [gradientAId, gradientBId] = useSvgGradientIds("intellij-idea", 2);
264
265 return (
266 <svg {...props} viewBox="0 0 64 64" fill="none">
267 <defs>
268 <linearGradient
269 id={gradientAId}
270 x1="-.391"
271 x2="24.392"
272 y1="7.671"
273 y2="61.126"
274 gradientUnits="userSpaceOnUse"
275 >
276 <stop offset=".1" stopColor="#FC801D" />
277 <stop offset=".59" stopColor="#FE2857" />
278 </linearGradient>
279 <linearGradient
280 id={gradientBId}
281 x1="4.325"
282 x2="62.921"
283 y1="59.932"
284 y2="1.336"
285 gradientUnits="userSpaceOnUse"
286 >
287 <stop offset=".21" stopColor="#FE2857" />
288 <stop offset=".7" stopColor="#007EFF" />
289 </linearGradient>
290 </defs>
291 <path
292 fill="#FF8100"
293 d="M16.45 6H4.191a4.125 4.125 0 0 0-4.124 4.19l.176 11.044a4.13 4.13 0 0 0 1.44 3.066l38.159 32.707c.747.64 1.7.993 2.684.993h11.35A4.125 4.125 0 0 0 58 53.875V42.872c0-1.19-.514-2.321-1.41-3.105L19.167 7.021A4.12 4.12 0 0 0 16.45 6"
294 />
295 <path
296 fill={`url(#${gradientAId})`}
297 d="M14.988 6H4.125A4.125 4.125 0 0 0 0 10.125v12.566q0 .3.044.598l5.448 37.185A4.125 4.125 0 0 0 9.573 64h15.398a4.125 4.125 0 0 0 4.125-4.127L29.09 41.37c0-.426-.066-.849-.195-1.254l-9.98-31.245A4.13 4.13 0 0 0 14.988 6z"
298 />
299 <path
300 fill={`url(#${gradientBId})`}
301 d="M59.876 0H25.748a4.13 4.13 0 0 0-3.8 2.52L6.151 39.943a4.1 4.1 0 0 0-.325 1.638l.15 18.329A4.125 4.125 0 0 0 10.101 64h17.666c.806 0 1.593-.236 2.266-.678l32.11-21.109A4.12 4.12 0 0 0 64 38.766V4.125A4.125 4.125 0 0 0 59.876 0"
302 />
303 <path fill="#000" d="M52 12H12v40h40z" />
304 <path
305 fill="#fff"
306 d="M33 44H17v3h16zM17 29.383h2.98v-9.775H17v-2.616h8.843v2.616h-2.98v9.775h2.98V32H17zM27.643 29.298h2.154a2.4 2.4 0 0 0 1.163-.279q.51-.279.788-.788.279-.51.279-1.163V16.992h2.926v10.28q0 1.35-.622 2.427a4.45 4.45 0 0 1-1.715 1.688q-1.092.612-2.454.611h-2.519z"
307 />
308 </svg>
309 );
310};
311
312export const PhpStormIcon: Icon = (props) => {
313 const [gradientAId, gradientBId] = useSvgGradientIds("phpstorm", 2);
314
315 return (
316 <svg {...props} viewBox="0 0 64 64" fill="none">
317 <defs>
318 <linearGradient
319 id={gradientAId}
320 x1="56.329"
321 x2="2.874"
322 y1="-.391"
323 y2="24.392"
324 gradientUnits="userSpaceOnUse"
325 >
326 <stop offset=".16" stopColor="#D249FC" />
327 <stop offset=".55" stopColor="#FF2D90" />
328 </linearGradient>
329 <linearGradient
330 id={gradientBId}
331 x1="4.067"
332 x2="62.664"
333 y1="4.326"
334 y2="62.923"
335 gradientUnits="userSpaceOnUse"
336 >
337 <stop offset=".3" stopColor="#FF2D90" />
338 <stop offset=".7" stopColor="#7256FF" />
339 </linearGradient>
340 </defs>
341 <path
342 fill="#D249FC"
343 d="M58 16.446V4.187A4.125 4.125 0 0 0 53.81.063L42.765.239A4.13 4.13 0 0 0 39.7 1.68L6.993 39.837c-.64.748-.993 1.7-.993 2.685v11.35a4.125 4.125 0 0 0 4.125 4.124h11.003c1.19 0 2.321-.514 3.105-1.409l32.746-37.425A4.12 4.12 0 0 0 58 16.446"
344 />
345 <path
346 fill={`url(#${gradientAId})`}
347 d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309q-.3 0-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.13 4.13 0 0 0 58 14.988"
348 />
349 <path
350 fill={`url(#${gradientBId})`}
351 d="M64 59.878V25.75a4.13 4.13 0 0 0-2.52-3.8L24.057 6.153a4.1 4.1 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.12 4.12 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878"
352 />
353 <path fill="#000" d="M52 12H12v40h40z" />
354 <path
355 fill="#fff"
356 d="M33 44H17v3h16zM16.993 16.992h6.442q1.586 0 2.78.579 1.196.579 1.845 1.63.648 1.05.648 2.432c0 .922-.22 1.759-.659 2.466q-.66 1.062-1.87 1.646-1.212.584-2.83.584h-3.43V32h-2.927zm7.54 6.63q.553-.273.852-.782.3-.51.3-1.195c0-.685-.1-.842-.3-1.174q-.3-.5-.852-.772-.552-.273-1.291-.273h-3.323v4.47h3.323q.74 0 1.291-.273M32.572 31.664q-1.26-.59-1.972-1.65t-.735-2.444h2.937q0 .654.343 1.147t.954.772q.611.278 1.404.278c.53 0 .952-.083 1.335-.251q.572-.252.89-.702c.317-.45.315-.643.315-1.029q0-.718-.434-1.19-.435-.471-1.195-.654l-2.647-.59q-1.073-.234-1.866-.809a4 4 0 0 1-1.232-1.42q-.44-.846-.44-1.908 0-1.286.665-2.31.665-1.023 1.854-1.597 1.19-.573 2.702-.573c1.512 0 1.933.186 2.733.558q1.2.558 1.87 1.549t.681 2.277h-2.926q0-.546-.29-.98a1.9 1.9 0 0 0-.82-.676 2.9 2.9 0 0 0-1.216-.24q-.687 0-1.206.23a1.9 1.9 0 0 0-.81.643q-.29.412-.289.96 0 .621.402 1.028t1.099.579l2.551.557q1.104.226 1.967.852a4.45 4.45 0 0 1 1.345 1.544q.482.917.482 1.999 0 1.329-.701 2.385-.703 1.054-1.967 1.656-1.264.6-2.895.6-1.63 0-2.888-.59"
357 />
358 </svg>
359 );
360};
361
362export const PyCharmIcon: Icon = (props) => {
363 const [gradientAId, gradientBId] = useSvgGradientIds("pycharm", 2);
364
365 return (
366 <svg {...props} viewBox="0 0 64 64" fill="none">
367 <defs>
368 <linearGradient
369 id={gradientAId}
370 x1="7.671"
371 x2="61.126"
372 y1="64.393"
373 y2="39.609"
374 gradientUnits="userSpaceOnUse"
375 >
376 <stop offset=".1" stopColor="#00D886" />
377 <stop offset=".59" stopColor="#F0EB18" />
378 </linearGradient>
379 <linearGradient
380 id={gradientBId}
381 x1="59.933"
382 x2="1.337"
383 y1="59.676"
384 y2="1.08"
385 gradientUnits="userSpaceOnUse"
386 >
387 <stop offset=".3" stopColor="#F0EB18" />
388 <stop offset=".7" stopColor="#00C4F4" />
389 </linearGradient>
390 </defs>
391 <path
392 fill="#00D886"
393 d="M6 47.55v12.259a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.13 4.13 0 0 0 3.066-1.44l32.707-38.158c.64-.748.993-1.7.993-2.685V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.834A4.12 4.12 0 0 0 6 47.55"
394 />
395 <path
396 fill={`url(#${gradientAId})`}
397 d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566q.3 0 .598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.13 4.13 0 0 0 6 49.015z"
398 />
399 <path
400 fill={`url(#${gradientBId})`}
401 d="M0 4.125v34.128c0 1.658.993 3.154 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.13 4.13 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125"
402 />
403 <path fill="#000" d="M52 12H12v40h40z" />
404 <path
405 fill="#fff"
406 d="M33 44H17v3h16zM16.993 16.992h6.442q1.586 0 2.78.579 1.196.579 1.845 1.63.648 1.05.648 2.432c0 .922-.22 1.759-.659 2.466q-.66 1.062-1.87 1.646-1.212.584-2.83.584h-3.43V32h-2.927zm7.54 6.63q.553-.273.852-.782.3-.51.3-1.195c0-.685-.1-.842-.3-1.174q-.3-.5-.852-.772-.552-.273-1.291-.273h-3.324v4.47h3.324q.74 0 1.291-.273M33.713 31.243a7.3 7.3 0 0 1-2.744-2.787q-.996-1.774-.996-3.961c0-2.187.332-2.778.996-3.96a7.3 7.3 0 0 1 2.744-2.787q1.748-1.013 3.902-1.014 1.823 0 3.344.675a6.86 6.86 0 0 1 2.535 1.892 6.44 6.44 0 0 1 1.356 2.793h-3.066a4.05 4.05 0 0 0-.895-1.431 4 4 0 0 0-1.43-.95 4.9 4.9 0 0 0-1.823-.332q-1.33 0-2.402.665a4.6 4.6 0 0 0-1.677 1.827q-.606 1.164-.606 2.62 0 1.459.606 2.621a4.6 4.6 0 0 0 1.677 1.828q1.072.664 2.402.664.986 0 1.822-.332a4.02 4.02 0 0 0 2.326-2.38h3.066a6.44 6.44 0 0 1-1.356 2.792 6.86 6.86 0 0 1-2.535 1.892q-1.522.675-3.344.675-2.154 0-3.902-1.013z"
407 />
408 </svg>
409 );
410};
411
412export const RiderIcon: Icon = (props) => {
413 const [gradientAId, gradientBId] = useSvgGradientIds("rider", 2);
414
415 return (
416 <svg {...props} viewBox="0 0 64 64" fill="none">
417 <defs>
418 <linearGradient
419 id={gradientAId}
420 x1="64.391"
421 x2="39.607"
422 y1="56.329"
423 y2="2.874"
424 gradientUnits="userSpaceOnUse"
425 >
426 <stop offset=".21" stopColor="#007DFE" />
427 <stop offset=".55" stopColor="#FFB700" />
428 </linearGradient>
429 <linearGradient
430 id={gradientBId}
431 x1="59.676"
432 x2="1.08"
433 y1="4.067"
434 y2="62.663"
435 gradientUnits="userSpaceOnUse"
436 >
437 <stop offset=".23" stopColor="#FFB700" />
438 <stop offset=".73" stopColor="#FF0A67" />
439 </linearGradient>
440 </defs>
441 <path
442 fill="#007DFE"
443 d="M47.55 58h12.258a4.125 4.125 0 0 0 4.124-4.19l-.176-11.044a4.12 4.12 0 0 0-1.44-3.066L24.158 6.993A4.13 4.13 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.105l37.425 32.746A4.12 4.12 0 0 0 47.55 58"
444 />
445 <path
446 fill={`url(#${gradientAId})`}
447 d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309q0-.3-.044-.598L58.508 3.527A4.125 4.125 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.13 4.13 0 0 0 3.93 2.87"
448 />
449 <path
450 fill={`url(#${gradientBId})`}
451 d="M4.125 64h34.128a4.13 4.13 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.124 4.124 0 0 0 53.9 0H36.234c-.805 0-1.593.236-2.266.678L1.86 21.787A4.13 4.13 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64"
452 />
453 <path fill="#000" d="M52 12H12v40h40z" />
454 <path
455 fill="#fff"
456 d="M33 44H17v3h16zM16.992 16.992h6.442q1.576 0 2.776.579t1.85 1.63.648 2.432c0 .922-.22 1.75-.66 2.46q-.66 1.068-1.875 1.651-1.217.584-2.825.584h-3.43v5.67h-2.926zm7.54 6.63q.553-.274.853-.783.3-.51.3-1.184c0-.45-.1-.852-.3-1.185q-.3-.498-.852-.772-.552-.273-1.292-.273h-3.323v4.47h3.323q.74 0 1.292-.273m-2.63 1.763h3.194L29.03 32h-3.355zM31.613 16.992h5.713q2.133 0 3.822.965a6.9 6.9 0 0 1 2.641 2.674q.954 1.71.954 3.864t-.953 3.865a6.9 6.9 0 0 1-2.642 2.674q-1.688.964-3.822.964h-5.713zm8.028 11.876q1.008-.6 1.555-1.72t.547-2.653q0-1.534-.547-2.653-.547-1.12-1.555-1.721-1.008-.6-2.347-.6h-2.755v9.947h2.755q1.34 0 2.347-.6"
457 />
458 </svg>
459 );
460};
461
462export const RubyMineIcon: Icon = (props) => {
463 const [gradientAId, gradientBId] = useSvgGradientIds("rubymine", 2);
464
465 return (
466 <svg {...props} viewBox="0 0 64 64" fill="none">
467 <defs>
468 <linearGradient
469 id={gradientAId}
470 x1="4.325"
471 x2="62.921"
472 y1="59.932"
473 y2="1.337"
474 gradientUnits="userSpaceOnUse"
475 >
476 <stop offset=".29" stopColor="#FF2358" />
477 <stop offset=".75" stopColor="#7256FF" />
478 </linearGradient>
479 <linearGradient
480 id={gradientBId}
481 x1="-.391"
482 x2="24.393"
483 y1="7.671"
484 y2="61.125"
485 gradientUnits="userSpaceOnUse"
486 >
487 <stop offset=".29" stopColor="#FF8100" />
488 <stop offset=".56" stopColor="#FF2358" />
489 </linearGradient>
490 </defs>
491 <path
492 fill={`url(#${gradientAId})`}
493 d="M59.875 0H25.748a4.13 4.13 0 0 0-3.8 2.52L6.151 39.942a4.1 4.1 0 0 0-.325 1.639l.15 18.328A4.125 4.125 0 0 0 10.101 64h17.666c.805 0 1.593-.235 2.266-.678l32.109-21.108A4.12 4.12 0 0 0 64 38.766V4.125A4.125 4.125 0 0 0 59.875 0"
494 />
495 <path
496 fill={`url(#${gradientBId})`}
497 d="M14.987 6H4.126A4.125 4.125 0 0 0 0 10.125v12.566q0 .3.044.598l5.448 37.184A4.125 4.125 0 0 0 9.572 64H24.97a4.125 4.125 0 0 0 4.125-4.126l-.004-18.504c0-.426-.067-.85-.196-1.254L18.917 8.87A4.13 4.13 0 0 0 14.987 6z"
498 />
499 <path
500 fill="#FF8100"
501 d="M16.45 6H4.19a4.125 4.125 0 0 0-4.124 4.19L0 21c.019 1.181.786 2.531 1.683 3.3l38.158 32.706c.748.641 1.7.993 2.684.993h11.35A4.125 4.125 0 0 0 58 53.875V42.872c0-1.189-.514-2.32-1.41-3.104L19.167 7.021A4.12 4.12 0 0 0 16.45 6"
502 />
503 <path fill="#000" d="M52 12H12v40h40z" />
504 <path
505 fill="#fff"
506 d="M33 44H17v3h16zM17.012 16.992h6.442q1.575 0 2.776.579t1.849 1.63.648 2.432c0 .922-.22 1.75-.66 2.46q-.658 1.068-1.875 1.651-1.216.584-2.824.584h-3.43v5.67h-2.926zm7.54 6.63q.552-.274.852-.783.3-.51.3-1.184c0-.45-.1-.852-.3-1.185q-.3-.498-.852-.772-.552-.273-1.292-.273h-3.323v4.47h3.323q.74 0 1.292-.273m-2.631 1.763h3.194L29.05 32h-3.355zM31.633 16.992h4.073l3.087 9.85.257 1.287.225-1.286 2.98-9.85h4.138v15.005h-2.894V21.29l.043-.782-3.494 11.49h-2.123l-3.451-11.415.043.707v10.708h-2.883V16.992z"
507 />
508 </svg>
509 );
510};
511
512export const RustRoverIcon: Icon = (props) => {
513 const [gradientAId, gradientBId] = useSvgGradientIds("rustrover", 2);
514
515 return (
516 <svg {...props} viewBox="0 0 64 64" fill="none">
517 <defs>
518 <linearGradient
519 id={gradientAId}
520 x1="7.671"
521 x2="61.125"
522 y1="64.393"
523 y2="39.609"
524 gradientUnits="userSpaceOnUse"
525 >
526 <stop offset=".08" stopColor="#00D886" />
527 <stop offset=".46" stopColor="#FFAB00" />
528 </linearGradient>
529 <linearGradient
530 id={gradientBId}
531 x1="59.932"
532 x2="1.336"
533 y1="59.676"
534 y2="1.08"
535 gradientUnits="userSpaceOnUse"
536 >
537 <stop offset=".19" stopColor="#FFAB00" />
538 <stop offset=".83" stopColor="#FF004C" />
539 </linearGradient>
540 </defs>
541 <path
542 fill="#00D886"
543 d="M6 47.55v12.258a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.12 4.12 0 0 0 3.066-1.44l32.707-38.158c.64-.747.993-1.7.993-2.684V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.833A4.12 4.12 0 0 0 6 47.55"
544 />
545 <path
546 fill={`url(#${gradientAId})`}
547 d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566q.3 0 .598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.13 4.13 0 0 0 6 49.015z"
548 />
549 <path
550 fill={`url(#${gradientBId})`}
551 d="M0 4.125v34.128c0 1.658.993 3.154 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.13 4.13 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125"
552 />
553 <path fill="#000" d="M52 12H12v40h40z" />
554 <path
555 fill="#fff"
556 d="M33 44H17v3h16zM16.992 16.992h6.442q1.576 0 2.776.579t1.85 1.63.648 2.432c0 .922-.22 1.75-.66 2.46q-.66 1.068-1.875 1.651-1.217.584-2.825.584h-3.43v5.67h-2.926zm7.54 6.63q.553-.274.853-.783.3-.51.3-1.184c0-.45-.1-.852-.3-1.185q-.3-.498-.852-.772-.552-.273-1.292-.273h-3.323v4.47h3.323q.74 0 1.292-.273m-2.63 1.763h3.194L29.03 32h-3.355zM31.613 16.992h6.442q1.575 0 2.776.579t1.85 1.63.648 2.432c0 .922-.22 1.75-.66 2.46q-.659 1.068-1.875 1.651-1.217.584-2.824.584h-3.43v5.67h-2.927zm7.54 6.63q.553-.274.853-.783.3-.51.3-1.184c0-.45-.1-.852-.3-1.185q-.3-.498-.852-.772-.552-.273-1.292-.273h-3.323v4.47h3.323q.74 0 1.292-.273m-2.63 1.763h3.194L43.651 32h-3.355z"
557 />
558 </svg>
559 );
560};
561
562export const WebStormIcon: Icon = (props) => {
563 const [gradientAId, gradientBId] = useSvgGradientIds("webstorm", 2);
564
565 return (
566 <svg {...props} viewBox="0 0 64 64" fill="none">
567 <defs>
568 <linearGradient
569 id={gradientAId}
570 x1="7.671"
571 x2="61.126"
572 y1="64.392"
573 y2="39.609"
574 gradientUnits="userSpaceOnUse"
575 >
576 <stop offset=".22" stopColor="#F0EB18" />
577 <stop offset=".59" stopColor="#00C4F4" />
578 </linearGradient>
579 <linearGradient
580 id={gradientBId}
581 x1="59.932"
582 x2="1.337"
583 y1="59.676"
584 y2="1.079"
585 gradientUnits="userSpaceOnUse"
586 >
587 <stop offset=".19" stopColor="#00C4F4" />
588 <stop offset=".83" stopColor="#007DFE" />
589 </linearGradient>
590 </defs>
591 <path
592 fill="#F0EB18"
593 d="M6 47.55v12.258a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.12 4.12 0 0 0 3.066-1.44l32.707-38.158c.64-.747.993-1.7.993-2.684V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.833A4.12 4.12 0 0 0 6 47.55"
594 />
595 <path
596 fill={`url(#${gradientAId})`}
597 d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566q.3 0 .598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.13 4.13 0 0 0 6 49.015z"
598 />
599 <path
600 fill={`url(#${gradientBId})`}
601 d="M0 4.125v34.128c0 1.658.993 3.154 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.13 4.13 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125"
602 />
603 <path fill="#000" d="M52 12H12v40h40z" />
604 <path
605 fill="#fff"
606 d="M33 44H17v3h16zM19.051 16.992l2.423 10.955 2.583-10.955h2.958l2.701 10.955 2.348-10.955h2.97l-3.645 15.006h-3.334l-2.53-10.9-2.561 10.9H19.64l-3.623-15.006zM38.662 31.664q-1.26-.59-1.972-1.65t-.735-2.444h2.937q0 .654.343 1.147t.954.772q.61.278 1.404.278c.53 0 .952-.083 1.335-.251q.573-.252.889-.702t.316-1.029q0-.718-.434-1.19-.435-.471-1.195-.654l-2.648-.59q-1.071-.234-1.865-.809a4 4 0 0 1-1.232-1.42q-.44-.846-.44-1.908 0-1.286.665-2.31.664-1.023 1.854-1.597 1.19-.573 2.701-.573c1.008 0 1.934.186 2.734.558q1.2.558 1.87 1.549t.68 2.277h-2.925q0-.546-.29-.98c-.29-.435-.466-.515-.82-.676a2.9 2.9 0 0 0-1.217-.24q-.686 0-1.205.23a1.9 1.9 0 0 0-.81.643q-.29.412-.289.96 0 .621.402 1.028t1.099.579l2.55.557q1.105.226 1.968.852a4.45 4.45 0 0 1 1.345 1.544 4.2 4.2 0 0 1 .482 1.999q0 1.329-.702 2.385-.702 1.054-1.966 1.656-1.266.6-2.894.6-1.63 0-2.889-.59"
607 />
608 </svg>
609 );
610};
apps/web/src/components/chat/OpenInPicker.tsx+69-1
......@@ -12,12 +12,25 @@ import {
1212 Icon,
1313 KiroIcon,
1414 TraeIcon,
15 IntelliJIdeaIcon,
1615 VisualStudioCode,
1716 VisualStudioCodeInsiders,
1817 VSCodium,
1918 Zed,
2019} from "../Icons";
20import {
21 AquaIcon,
22 CLionIcon,
23 DataGripIcon,
24 DataSpellIcon,
25 GoLandIcon,
26 IntelliJIdeaIcon,
27 PhpStormIcon,
28 PyCharmIcon,
29 RiderIcon,
30 RubyMineIcon,
31 RustRoverIcon,
32 WebStormIcon,
33} from "../JetBrainsIcons";
2134import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";
2235import { readLocalApi } from "~/localApi";
2336
......@@ -68,6 +81,61 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
6881 Icon: IntelliJIdeaIcon,
6982 value: "idea",
7083 },
84 {
85 label: "Aqua",
86 Icon: AquaIcon,
87 value: "aqua",
88 },
89 {
90 label: "CLion",
91 Icon: CLionIcon,
92 value: "clion",
93 },
94 {
95 label: "DataGrip",
96 Icon: DataGripIcon,
97 value: "datagrip",
98 },
99 {
100 label: "DataSpell",
101 Icon: DataSpellIcon,
102 value: "dataspell",
103 },
104 {
105 label: "GoLand",
106 Icon: GoLandIcon,
107 value: "goland",
108 },
109 {
110 label: "PhpStorm",
111 Icon: PhpStormIcon,
112 value: "phpstorm",
113 },
114 {
115 label: "PyCharm",
116 Icon: PyCharmIcon,
117 value: "pycharm",
118 },
119 {
120 label: "Rider",
121 Icon: RiderIcon,
122 value: "rider",
123 },
124 {
125 label: "RubyMine",
126 Icon: RubyMineIcon,
127 value: "rubymine",
128 },
129 {
130 label: "RustRover",
131 Icon: RustRoverIcon,
132 value: "rustrover",
133 },
134 {
135 label: "WebStorm",
136 Icon: WebStormIcon,
137 value: "webstorm",
138 },
71139 {
72140 label: isMacPlatform(platform)
73141 ? "Finder"
apps/web/src/markdown-links.test.ts+30
......@@ -18,6 +18,20 @@ describe("rewriteMarkdownFileUriHref", () => {
1818 "/Users/julius/project/file%2520name.md",
1919 );
2020 });
21
22 it("normalizes file uri hrefs for windows drive paths", () => {
23 expect(
24 rewriteMarkdownFileUriHref(
25 "file:///D:/Programme/t3code/apps/web/src/components/chat/OpenInPicker.tsx#L69",
26 ),
27 ).toBe("D:/Programme/t3code/apps/web/src/components/chat/OpenInPicker.tsx#L69");
28 });
29
30 it("unwraps angle-bracketed file uri hrefs", () => {
31 expect(
32 rewriteMarkdownFileUriHref(" <file:///D:/Programme/t3code/apps/web/src/markdown-links.ts> "),
33 ).toBe("D:/Programme/t3code/apps/web/src/markdown-links.ts");
34 });
2135});
2236
2337describe("resolveMarkdownFileLinkTarget", () => {
......@@ -84,6 +98,22 @@ describe("resolveMarkdownFileLinkTarget", () => {
8498 });
8599 });
86100
101 it("normalizes slash-prefixed windows drive paths before resolving", () => {
102 expect(
103 resolveMarkdownFileLinkTarget(
104 "/D:/Programme/t3code/apps/web/src/components/chat/OpenInPicker.tsx#L69",
105 ),
106 ).toBe("D:/Programme/t3code/apps/web/src/components/chat/OpenInPicker.tsx:69");
107 });
108
109 it("resolves angle-bracketed windows drive paths", () => {
110 expect(
111 resolveMarkdownFileLinkTarget(
112 "</D:/Programme/t3code/apps/web/src/components/ChatMarkdown.tsx:1>",
113 ),
114 ).toBe("D:/Programme/t3code/apps/web/src/components/ChatMarkdown.tsx:1");
115 });
116
87117 it("does not treat app routes as file links", () => {
88118 expect(resolveMarkdownFileLinkTarget("/chat/settings")).toBeNull();
89119 });
apps/web/src/markdown-links.ts+19-4
......@@ -39,6 +39,14 @@ function safeDecode(value: string): string {
3939 }
4040}
4141
42function unwrapMarkdownLinkDestination(value: string): string {
43 return value.startsWith("<") && value.endsWith(">") ? value.slice(1, -1) : value;
44}
45
46export function normalizeMarkdownLinkDestination(value: string): string {
47 return unwrapMarkdownLinkDestination(value.trim());
48}
49
4250function stripSearchAndHash(value: string): { path: string; hash: string } {
4351 const hashIndex = value.indexOf("#");
4452 const pathWithSearch = hashIndex >= 0 ? value.slice(0, hashIndex) : value;
......@@ -48,6 +56,10 @@ function stripSearchAndHash(value: string): { path: string; hash: string } {
4856 return { path, hash: rawHash };
4957}
5058
59function normalizeWindowsDrivePath(path: string): string {
60 return /^\/[A-Za-z]:[\\/]/.test(path) ? path.slice(1) : path;
61}
62
5163function parseFileUrlHref(
5264 href: string,
5365 options?: { readonly decodePath?: boolean },
......@@ -60,7 +72,7 @@ function parseFileUrlHref(
6072 if (rawPath.length === 0) return null;
6173
6274 // Browser URL parser encodes "C:/foo" as "/C:/foo" for file URLs.
63 const normalizedPath = /^\/[A-Za-z]:[\\/]/.test(rawPath) ? rawPath.slice(1) : rawPath;
75 const normalizedPath = normalizeWindowsDrivePath(rawPath);
6476
6577 return {
6678 path: options?.decodePath === false ? normalizedPath : safeDecode(normalizedPath),
......@@ -73,7 +85,8 @@ function parseFileUrlHref(
7385
7486export function rewriteMarkdownFileUriHref(href: string | undefined): string | null {
7587 if (!href) return null;
76 const target = parseFileUrlHref(href.trim(), { decodePath: false });
88 const normalizedHref = normalizeMarkdownLinkDestination(href);
89 const target = parseFileUrlHref(normalizedHref, { decodePath: false });
7790 if (!target) return null;
7891 return `${target.path}${target.hash}`;
7992}
......@@ -124,14 +137,16 @@ export function resolveMarkdownFileLinkTarget(
124137 cwd?: string,
125138): string | null {
126139 if (!href) return null;
127 const rawHref = href.trim();
140 const rawHref = normalizeMarkdownLinkDestination(href);
128141 if (rawHref.length === 0 || rawHref.startsWith("#")) return null;
129142
130143 const fileUrlTarget = rawHref.toLowerCase().startsWith("file:")
131144 ? parseFileUrlHref(rawHref)
132145 : null;
133146 const source = fileUrlTarget ?? stripSearchAndHash(rawHref);
134 const decodedPath = fileUrlTarget ? source.path.trim() : safeDecode(source.path.trim());
147 const decodedPath = normalizeWindowsDrivePath(
148 fileUrlTarget ? source.path.trim() : safeDecode(source.path.trim()),
149 );
135150 const decodedHash = safeDecode(source.hash.trim());
136151
137152 if (decodedPath.length === 0) return null;
packages/contracts/src/editor.ts+11
......@@ -27,6 +27,17 @@ export const EDITORS = [
2727 { id: "zed", label: "Zed", commands: ["zed", "zeditor"], launchStyle: "direct-path" },
2828 { id: "antigravity", label: "Antigravity", commands: ["agy"], launchStyle: "goto" },
2929 { id: "idea", label: "IntelliJ IDEA", commands: ["idea"], launchStyle: "line-column" },
30 { id: "aqua", label: "Aqua", commands: ["aqua"], launchStyle: "line-column" },
31 { id: "clion", label: "CLion", commands: ["clion"], launchStyle: "line-column" },
32 { id: "datagrip", label: "DataGrip", commands: ["datagrip"], launchStyle: "line-column" },
33 { id: "dataspell", label: "DataSpell", commands: ["dataspell"], launchStyle: "line-column" },
34 { id: "goland", label: "GoLand", commands: ["goland"], launchStyle: "line-column" },
35 { id: "phpstorm", label: "PhpStorm", commands: ["phpstorm"], launchStyle: "line-column" },
36 { id: "pycharm", label: "PyCharm", commands: ["pycharm"], launchStyle: "line-column" },
37 { id: "rider", label: "Rider", commands: ["rider"], launchStyle: "line-column" },
38 { id: "rubymine", label: "RubyMine", commands: ["rubymine"], launchStyle: "line-column" },
39 { id: "rustrover", label: "RustRover", commands: ["rustrover"], launchStyle: "line-column" },
40 { id: "webstorm", label: "WebStorm", commands: ["webstorm"], launchStyle: "line-column" },
3041 { id: "file-manager", label: "File Manager", commands: null, launchStyle: "direct-path" },
3142] as const satisfies ReadonlyArray<EditorDefinition>;
3243