Skip to content

Commit b9d8c56

Browse files
committed
Handle external applications with app type distinction
1 parent 79933ea commit b9d8c56

File tree

3 files changed

+79
-42
lines changed

3 files changed

+79
-42
lines changed

apps/array/src/main/services/externalApps.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,37 @@ async function getFileIcon() {
2424
let cachedApps: DetectedApplication[] | null = null;
2525
let detectionPromise: Promise<DetectedApplication[]> | null = null;
2626

27-
const APP_PATHS: Record<string, string> = {
28-
vscode: "/Applications/Visual Studio Code.app",
29-
cursor: "/Applications/Cursor.app",
30-
sublime: "/Applications/Sublime Text.app",
31-
webstorm: "/Applications/WebStorm.app",
32-
intellij: "/Applications/IntelliJ IDEA.app",
33-
zed: "/Applications/Zed.app",
34-
pycharm: "/Applications/PyCharm.app",
35-
iterm: "/Applications/iTerm.app",
36-
warp: "/Applications/Warp.app",
37-
terminal: "/System/Applications/Utilities/Terminal.app",
38-
alacritty: "/Applications/Alacritty.app",
39-
kitty: "/Applications/kitty.app",
40-
ghostty: "/Applications/Ghostty.app",
41-
finder: "/System/Library/CoreServices/Finder.app",
27+
interface AppDefinition {
28+
path: string;
29+
type: ExternalAppType;
30+
}
31+
32+
const APP_DEFINITIONS: Record<string, AppDefinition> = {
33+
// Editors
34+
vscode: { path: "/Applications/Visual Studio Code.app", type: "editor" },
35+
cursor: { path: "/Applications/Cursor.app", type: "editor" },
36+
sublime: { path: "/Applications/Sublime Text.app", type: "editor" },
37+
webstorm: { path: "/Applications/WebStorm.app", type: "editor" },
38+
intellij: { path: "/Applications/IntelliJ IDEA.app", type: "editor" },
39+
zed: { path: "/Applications/Zed.app", type: "editor" },
40+
pycharm: { path: "/Applications/PyCharm.app", type: "editor" },
41+
42+
// Terminals
43+
iterm: { path: "/Applications/iTerm.app", type: "terminal" },
44+
warp: { path: "/Applications/Warp.app", type: "terminal" },
45+
terminal: {
46+
path: "/System/Applications/Utilities/Terminal.app",
47+
type: "terminal",
48+
},
49+
alacritty: { path: "/Applications/Alacritty.app", type: "terminal" },
50+
kitty: { path: "/Applications/kitty.app", type: "terminal" },
51+
ghostty: { path: "/Applications/Ghostty.app", type: "terminal" },
52+
53+
// File managers
54+
finder: {
55+
path: "/System/Library/CoreServices/Finder.app",
56+
type: "file-manager",
57+
},
4258
};
4359

4460
const DISPLAY_NAMES: Record<string, string> = {
@@ -129,8 +145,12 @@ async function checkApplication(
129145
async function detectExternalApps(): Promise<DetectedApplication[]> {
130146
const apps: DetectedApplication[] = [];
131147

132-
for (const [id, appPath] of Object.entries(APP_PATHS)) {
133-
const detected = await checkApplication(id, appPath, "editor");
148+
for (const [id, definition] of Object.entries(APP_DEFINITIONS)) {
149+
const detected = await checkApplication(
150+
id,
151+
definition.path,
152+
definition.type,
153+
);
134154
if (detected) {
135155
apps.push(detected);
136156
}
@@ -180,11 +200,21 @@ export function registerExternalAppsIpc(): void {
180200
return { success: false, error: "Application not found" };
181201
}
182202

203+
let isFile = false;
204+
try {
205+
const stat = await fs.stat(targetPath);
206+
isFile = stat.isFile();
207+
} catch {
208+
// if stat fails, assume it is a path that does not exist yet
209+
isFile = false;
210+
}
211+
183212
let command: string;
184-
if (appToOpen.command.includes("open -a")) {
185-
command = `${appToOpen.command} "${targetPath}"`;
213+
if (appToOpen.id === "finder" && isFile) {
214+
// for Finder with files, use -R to highlight the file in its parent folder
215+
command = `open -R "${targetPath}"`;
186216
} else {
187-
command = `${appToOpen.command} "${targetPath}"`;
217+
command = `open -a "${appToOpen.path}" "${targetPath}"`;
188218
}
189219

190220
await execAsync(command);

apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ function ChangedFileItem({
139139
fullPath,
140140
fileName,
141141
);
142+
143+
// blur active element to dismiss any open tooltip
144+
if (document.activeElement instanceof HTMLElement) {
145+
document.activeElement.blur();
146+
}
142147
};
143148

144149
const handleCopyPath = async () => {
@@ -288,27 +293,29 @@ function ChangedFileItem({
288293
</DropdownMenu.Trigger>
289294
</Tooltip>
290295
<DropdownMenu.Content size="1" align="end">
291-
{detectedApps.map((app) => (
292-
<DropdownMenu.Item
293-
key={app.id}
294-
onSelect={() => handleOpenWith(app.id)}
295-
>
296-
<Flex align="center" gap="2">
297-
{app.icon ? (
298-
<img
299-
src={app.icon}
300-
width={16}
301-
height={16}
302-
alt=""
303-
style={{ borderRadius: "2px" }}
304-
/>
305-
) : (
306-
<CodeIcon size={16} weight="regular" />
307-
)}
308-
<Text size="1">{app.name}</Text>
309-
</Flex>
310-
</DropdownMenu.Item>
311-
))}
296+
{detectedApps
297+
.filter((app) => app.type !== "terminal")
298+
.map((app) => (
299+
<DropdownMenu.Item
300+
key={app.id}
301+
onSelect={() => handleOpenWith(app.id)}
302+
>
303+
<Flex align="center" gap="2">
304+
{app.icon ? (
305+
<img
306+
src={app.icon}
307+
width={16}
308+
height={16}
309+
alt=""
310+
style={{ borderRadius: "2px" }}
311+
/>
312+
) : (
313+
<CodeIcon size={16} weight="regular" />
314+
)}
315+
<Text size="1">{app.name}</Text>
316+
</Flex>
317+
</DropdownMenu.Item>
318+
))}
312319
<DropdownMenu.Separator />
313320
<DropdownMenu.Item onSelect={handleCopyPath}>
314321
<Flex align="center" gap="2">

apps/array/src/shared/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export interface ChangedFile {
189189
}
190190

191191
// External apps detection types
192-
export type ExternalAppType = "editor" | "terminal";
192+
export type ExternalAppType = "editor" | "terminal" | "file-manager";
193193

194194
export interface DetectedApplication {
195195
id: string; // "vscode", "cursor", "iterm"

0 commit comments

Comments
 (0)