feat: open active files in editors and file managers (#1285)
* feat: open active files in editors and file managers * fix: open the workspace folder alongside the active file in editors Editor targets received only the file path, so opening from the workspace file explorer launched the bare file without the surrounding project. Thread an optional workspace `cwd` through the open_in_editor request so editors launch as `<workspace> <file>`, loading the project explorer plus the file. File-manager targets keep their single-path behavior. * fix: address PR review feedback on file-open path resolution - Narrow the tilde guard so only genuine home-relative paths (~ or ~/…) are rejected; filenames like ~env.ts are now treated as workspace-relative. - Make the workspace-containment check case-insensitive for Windows drive paths so case-only differences still resolve a relative path. - Centralize the file-manager target id set in the protocol (isFileManagerTargetId) so the server and app no longer duplicate it. * feat(desktop): open workspace files from Electron * test(desktop): avoid mocked Electron editor IPC * fix(desktop): avoid Windows shell for editor executables * test(app): mock desktop editor bridge in e2e * fix(app): reuse resolved workspace open target path * fix(app): stabilize active file memo deps * fix(protocol): tag legacy editor RPC shims * fix(desktop): allow custom editor target ids * fix(desktop): open Windows editor paths with metacharacters Quote metacharacter values for .cmd editor wrappers so cmd.exe preserves them across wrapper execution instead of passing literal carets through. * fix(app): preserve reviewed file path handling --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Mohamed Boudra
parent
c0a0e3a1c6
commit
e9a746d4ea
@@ -140,6 +140,37 @@ describe("resolveWorkspaceFilePaths", () => {
|
||||
expect(
|
||||
resolveWorkspaceFilePaths({ path: "~/notes.md", workspaceRoot: "/Users/me/repo" }),
|
||||
).toBeNull();
|
||||
expect(resolveWorkspaceFilePaths({ path: "~", workspaceRoot: "/Users/me/repo" })).toBeNull();
|
||||
});
|
||||
|
||||
it("treats filenames that merely start with ~ as workspace-relative", () => {
|
||||
expect(resolveWorkspaceFilePaths({ path: "~env.ts", workspaceRoot: "/Users/me/repo" })).toEqual(
|
||||
{
|
||||
absolutePath: "/Users/me/repo/~env.ts",
|
||||
relativePath: "~env.ts",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("matches Windows workspace paths case-insensitively", () => {
|
||||
expect(
|
||||
resolveWorkspaceFilePaths({
|
||||
path: "C:\\Users\\Me\\Repo\\src\\app.ts",
|
||||
workspaceRoot: "c:\\users\\me\\repo",
|
||||
}),
|
||||
).toEqual({
|
||||
absolutePath: "C:/Users/Me/Repo/src/app.ts",
|
||||
relativePath: "src/app.ts",
|
||||
});
|
||||
});
|
||||
|
||||
it("treats a case-only difference from the Windows root as the root itself", () => {
|
||||
expect(
|
||||
resolveWorkspaceFilePaths({
|
||||
path: "C:\\Users\\Me\\Repo",
|
||||
workspaceRoot: "c:\\users\\me\\repo",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when the workspace root is not absolute", () => {
|
||||
|
||||
@@ -113,6 +113,22 @@ function normalizeRelativePath(value: string): string | null {
|
||||
return normalized || null;
|
||||
}
|
||||
|
||||
function isWindowsPath(value: string): boolean {
|
||||
return /^[A-Za-z]:\//.test(value);
|
||||
}
|
||||
|
||||
function pathsEqual(left: string, right: string): boolean {
|
||||
return isWindowsPath(left) || isWindowsPath(right)
|
||||
? left.toLowerCase() === right.toLowerCase()
|
||||
: left === right;
|
||||
}
|
||||
|
||||
function startsWithPath(value: string, prefix: string): boolean {
|
||||
return isWindowsPath(value) || isWindowsPath(prefix)
|
||||
? value.toLowerCase().startsWith(prefix.toLowerCase())
|
||||
: value.startsWith(prefix);
|
||||
}
|
||||
|
||||
export interface ResolvedWorkspaceFilePaths {
|
||||
/** Absolute path on the host, suitable for opening in an editor / file manager. */
|
||||
absolutePath: string;
|
||||
@@ -140,16 +156,17 @@ export function resolveWorkspaceFilePaths(input: {
|
||||
if (!normalizedFile) {
|
||||
return null;
|
||||
}
|
||||
if (normalizedFile === workspaceRoot) {
|
||||
if (pathsEqual(normalizedFile, workspaceRoot)) {
|
||||
return null;
|
||||
}
|
||||
const relativePath = normalizedFile.startsWith(`${workspaceRoot}/`)
|
||||
? normalizedFile.slice(workspaceRoot.length + 1)
|
||||
const prefix = `${workspaceRoot}/`;
|
||||
const relativePath = startsWithPath(normalizedFile, prefix)
|
||||
? normalizedFile.slice(prefix.length)
|
||||
: null;
|
||||
return { absolutePath: normalizedFile, relativePath };
|
||||
}
|
||||
|
||||
if (filePath.startsWith("~")) {
|
||||
if (filePath === "~" || filePath.startsWith("~/")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user