From 97a6deaa39cc3bf261a9806e793b3f4c81def819 Mon Sep 17 00:00:00 2001 From: Caique Maurano Date: Wed, 19 Aug 2026 09:14:13 -0300 Subject: [PATCH] feat(desktop): add Android Studio editor target (#3531) --- .../features/editor-targets/registry.test.ts | 35 ++++++++++++++++ .../src/features/editor-targets/registry.ts | 2 + .../editor-targets/targets/android-studio.ts | 41 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 packages/desktop/src/features/editor-targets/targets/android-studio.ts diff --git a/packages/desktop/src/features/editor-targets/registry.test.ts b/packages/desktop/src/features/editor-targets/registry.test.ts index d0d93e0ba..cdb4eb1af 100644 --- a/packages/desktop/src/features/editor-targets/registry.test.ts +++ b/packages/desktop/src/features/editor-targets/registry.test.ts @@ -233,6 +233,41 @@ describe("editor target registry", () => { ]); }); + it("lists and opens Android Studio through its bundled macOS launcher", async () => { + const runtime = new FakeEditorTargets("darwin", { HOME: "/Users/me" }); + const bundledCommand = "/Users/me/Applications/Android Studio.app/Contents/MacOS/studio"; + runtime.installCommand(bundledCommand, bundledCommand); + runtime.addPath("/repo"); + runtime.addPath("/repo/app/src/main/MainActivity.kt"); + + const targets = await listAvailableEditorTargets(runtime); + + expect(targets).toContainEqual({ + id: "android-studio", + label: "Android Studio", + kind: "editor", + icon: { kind: "symbol", name: "terminal" }, + }); + + await openEditorTarget( + { + editorId: "android-studio", + workspacePath: "/repo", + filePath: "/repo/app/src/main/MainActivity.kt", + line: 18, + column: 3, + }, + runtime, + ); + + expect(runtime.launches).toEqual([ + { + command: bundledCommand, + args: ["/repo", "--line", "18", "--column", "3", "/repo/app/src/main/MainActivity.kt"], + }, + ]); + }); + it("detects Cursor's installed Windows command when it is absent from PATH", async () => { const runtime = new FakeEditorTargets("win32", { LOCALAPPDATA: "C:/Users/me/AppData/Local", diff --git a/packages/desktop/src/features/editor-targets/registry.ts b/packages/desktop/src/features/editor-targets/registry.ts index 8de2a55a8..060302c72 100644 --- a/packages/desktop/src/features/editor-targets/registry.ts +++ b/packages/desktop/src/features/editor-targets/registry.ts @@ -4,6 +4,7 @@ import type { EditorTargetLaunchInput, EditorTargetRuntime, } from "./target.js"; +import { androidStudioTarget } from "./targets/android-studio.js"; import { antigravityTarget } from "./targets/antigravity.js"; import { aquaTarget } from "./targets/aqua.js"; import { clionTarget } from "./targets/clion.js"; @@ -35,6 +36,7 @@ export const EDITOR_TARGETS: readonly EditorTarget[] = [ vscodiumTarget, zedTarget, antigravityTarget, + androidStudioTarget, intellijIdeaTarget, aquaTarget, clionTarget, diff --git a/packages/desktop/src/features/editor-targets/targets/android-studio.ts b/packages/desktop/src/features/editor-targets/targets/android-studio.ts new file mode 100644 index 000000000..7907b772e --- /dev/null +++ b/packages/desktop/src/features/editor-targets/targets/android-studio.ts @@ -0,0 +1,41 @@ +import type { EditorTarget, EditorTargetLaunchInput, EditorTargetRuntime } from "../target.js"; + +function commands(runtime: EditorTargetRuntime): string[] { + const candidates = ["studio", "studio64"]; + if (runtime.platform === "darwin") { + candidates.push("/Applications/Android Studio.app/Contents/MacOS/studio"); + if (runtime.env.HOME) { + candidates.push(`${runtime.env.HOME}/Applications/Android Studio.app/Contents/MacOS/studio`); + } + } + return candidates; +} + +function launchArgs(input: EditorTargetLaunchInput): string[] { + if (!input.filePath) return [input.workspacePath]; + const args = [input.workspacePath]; + if (input.line) args.push("--line", String(input.line)); + if (input.column) args.push("--column", String(input.column)); + args.push(input.filePath); + return args; +} + +export const androidStudioTarget: EditorTarget = { + id: "android-studio", + async describe() { + return { + id: this.id, + label: "Android Studio", + kind: "editor", + icon: { kind: "symbol", name: "terminal" }, + }; + }, + async isInstalled(runtime) { + return runtime.resolveCommand(commands(runtime)) !== null; + }, + async launch(input, runtime) { + const command = runtime.resolveCommand(commands(runtime)); + if (!command) throw new Error("Android Studio is not installed"); + await runtime.spawnDetached({ command, args: launchArgs(input) }); + }, +};