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
19 changes: 18 additions & 1 deletion packages/local-open-targets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,16 @@ function parseDesktopEntryValue(line: string): [string, string] | null {
];
}

const LINUX_WORKSPACE_APPLICATION_CATEGORIES = new Set([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — The allowlist excludes standard IDE entries.

The desktop menu standard defines IDE as a category. GNOME Builder uses Categories=GNOME;GTK;Development;IDE;, and this repository has no explicit GNOME Builder adapter. This change removes that useful workspace target. Add IDE to the allowlist and cover it with a fixture.

"FileManager",
"TerminalEmulator",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — Terminal entries do not receive terminal-specific path arguments.

A real GNOME Terminal entry uses Exec=gnome-terminal. The current invocation code appends the workspace path, which creates gnome-terminal /workspace. GNOME Terminal uses --working-directory=DIR for this operation. Route these entries through the existing terminal builder, or omit TerminalEmulator until the launcher supports them. Add a fixture with the real command shape.

"TextEditor",
]);

function parseDesktopEntryList(value: string | undefined): string[] {
return value?.split(";").filter(Boolean) ?? [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — The list parser treats an escaped semicolon as a separator.

The desktop-entry standard permits escaped semicolons in string(s) values. For example, Categories=X-Foo\;TextEditor; is one extension category. This code splits it into two values and accepts it as TextEditor. Parse the list escape rule before the allowlist check, and add a focused test.

}

function parseLinuxDesktopApplication(
desktopFilePath: string,
content: string,
Expand Down Expand Up @@ -403,7 +413,14 @@ function parseLinuxDesktopApplication(

const label = fields.get("Name");
const exec = fields.get("Exec");
if (!label || !exec) {
const categories = parseDesktopEntryList(fields.get("Categories"));
if (
!label ||
!exec ||
!categories.some((category) =>
LINUX_WORKSPACE_APPLICATION_CATEGORIES.has(category),
)
) {
return null;
}

Expand Down
49 changes: 43 additions & 6 deletions packages/local-open-targets/test/workspace-open-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ describe("workspace open targets", () => {
"[Desktop Entry]",
"Type=Application",
"Name=Mock Edit",
"Categories=Utility;TextEditor;",
"Exec=mockedit --open %f",
"",
].join("\n"),
Expand All @@ -245,10 +246,41 @@ describe("workspace open targets", () => {
"Type=Application",
"Name=Hidden App",
"NoDisplay=true",
"Categories=TextEditor;",
"Exec=hidden %f",
"",
].join("\n"),
);
await Promise.all(
[
["files", "Files", "FileManager"],
["terminal", "Terminal", "TerminalEmulator"],
].map(async ([id, name, category]) =>
writeFile(
path.join(desktopDirectory, `${id}.desktop`),
[
"[Desktop Entry]",
"Type=Application",
`Name=${name}`,
`Categories=System;${category};`,
`Exec=${id} %f`,
"",
].join("\n"),
),
),
);
await writeFile(
path.join(desktopDirectory, "unrelated.desktop"),
[
"[Desktop Entry]",
"Type=Application",
"Name=Music Player",
"Categories=AudioVideo;Player;",
"MimeType=audio/mpeg;inode/directory;",
"Exec=music-player %f",
"",
].join("\n"),
);

try {
const targets = await listWorkspaceOpenTargetsWithRuntime(
Expand All @@ -258,20 +290,24 @@ describe("workspace open targets", () => {
}),
);

expect(targets).toEqual([
{
expect(targets).toEqual(
[
["desktop-app:files", "Files"],
["desktop-app:mockedit", "Mock Edit"],
["desktop-app:terminal", "Terminal"],
].map(([id, label]) => ({
capabilities: {
openDirectory: true,
openFile: true,
openFileAtColumn: false,
openFileAtLine: false,
},
icon: { kind: "symbol", name: "app" },
id: "desktop-app:mockedit",
id,
kind: "native-app",
label: "Mock Edit",
},
]);
label,
})),
);
} finally {
await rm(root, { force: true, recursive: true });
}
Expand Down Expand Up @@ -472,6 +508,7 @@ describe("workspace open targets", () => {
"[Desktop Entry]",
"Type=Application",
"Name=Mock Edit",
"Categories=TextEditor;",
"Exec=mockedit --open %f",
"",
].join("\n"),
Expand Down
Loading