mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 02:46:29 +00:00
* fix(app): open a nested-repo file when the workspace-root path is missing A workspace that contains another git repo often gets paths written relative to that nested repo. `schema/x.md` is really `repo-a/schema/x.md`. A markdown link was joined onto the workspace root and opened a missing file. Inline code already asked the daemon for a suffix match. Resolved workspace-relative paths that contain a slash now use that same lookup before opening. An exact hit stays on the original path. A suffix hit replaces it and keeps the line range. A failed lookup still opens the original path. Bare names, absolute paths, and `~/` paths are unchanged. Example: - Input: workspace `/repo`, link `schema/x.md`, file at `repo-a/schema/x.md` - Before: open `/repo/schema/x.md` (missing) - After: open `/repo/repo-a/schema/x.md` * fix(app): preserve explicit destinations when finding nested files Relative chat links can name a file inside a nested repository. Retain the suffix lookup while using the original href and document base to preserve absolute paths, preview links, and folder destinations. Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com> * fix(app): keep network share links on their absolute destination Use the shared absolute-path classifier so network share hrefs retain their destination alongside drive and POSIX paths. * fix(app): keep nested-file lookup inside ordered path opening Resolve the original directory before considering suffix matches, and allocate click ordering before asynchronous lookup. The existing opener now owns the complete operation so a slow earlier click cannot replace a later selection. * test(app): load UI modules before test lifecycle hooks Cold module transformation exceeded the lifecycle hook deadline before these suites could run. Complete module initialization before registering the tests, retaining their assertions and timeout settings. --------- Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
import { normalizeInlinePathTarget, type InlinePathTarget } from "./parse";
|
|
import {
|
|
normalizeWorkspaceFileLocation,
|
|
type WorkspaceFileLocation,
|
|
type OpenFileDisposition,
|
|
} from "@/workspace/file-open";
|
|
import { isAbsolutePath } from "@/utils/path";
|
|
import { resolveMissingNestedRelativeFile, type GetDirectorySuggestions } from "./resolver";
|
|
|
|
interface PathOpenInput {
|
|
file?: string;
|
|
directory: string;
|
|
lineStart?: number;
|
|
workspaceRoot: string;
|
|
client: { listDirectory(root: string, path: string): Promise<unknown> } | null;
|
|
}
|
|
|
|
type PathOpenTarget =
|
|
| { kind: "file" }
|
|
| { kind: "directory"; directoryPath: string }
|
|
| { kind: "outsideDirectory"; directoryPath: string };
|
|
|
|
// Workspace paths arrive relative to the root; anything still absolute or
|
|
// home-relative lives outside it, where the Files panel cannot list it.
|
|
function isOutsideWorkspacePath(pathValue: string): boolean {
|
|
return isAbsolutePath(pathValue) || pathValue === "~" || pathValue.startsWith("~/");
|
|
}
|
|
|
|
/** Resolve workspace folders from filesystem facts, including names that contain dots. */
|
|
export async function resolveInlinePathOpen(input: PathOpenInput): Promise<PathOpenTarget> {
|
|
if (!input.file) {
|
|
return isOutsideWorkspacePath(input.directory)
|
|
? { kind: "outsideDirectory", directoryPath: input.directory }
|
|
: { kind: "directory", directoryPath: input.directory };
|
|
}
|
|
if (
|
|
input.lineStart !== undefined ||
|
|
isAbsolutePath(input.file) ||
|
|
!input.workspaceRoot.trim() ||
|
|
!input.client
|
|
) {
|
|
return { kind: "file" };
|
|
}
|
|
try {
|
|
await input.client.listDirectory(input.workspaceRoot, input.file);
|
|
} catch {
|
|
return { kind: "file" };
|
|
}
|
|
return { kind: "directory", directoryPath: input.file };
|
|
}
|
|
|
|
export function createInlinePathOpener(input: {
|
|
workspaceRoot: string;
|
|
client: {
|
|
listDirectory(root: string, path: string): Promise<unknown>;
|
|
getDirectorySuggestions: GetDirectorySuggestions;
|
|
} | null;
|
|
resolveRelativeFiles?: boolean;
|
|
openFile: (location: WorkspaceFileLocation, disposition: OpenFileDisposition) => void;
|
|
openDirectory: (path: string) => void;
|
|
openOutsideDirectory: (path: string) => void;
|
|
}) {
|
|
let generation = 0;
|
|
return {
|
|
async open(target: InlinePathTarget, disposition: OpenFileDisposition) {
|
|
const ownGeneration = ++generation;
|
|
const normalized = normalizeInlinePathTarget(target.path, input.workspaceRoot);
|
|
if (!normalized) return;
|
|
const destination = await resolveInlinePathOpen({
|
|
...normalized,
|
|
lineStart: target.lineStart,
|
|
workspaceRoot: input.workspaceRoot,
|
|
client: input.client,
|
|
});
|
|
if (ownGeneration !== generation) return;
|
|
if (destination.kind === "directory") {
|
|
input.openDirectory(destination.directoryPath);
|
|
} else if (destination.kind === "outsideDirectory") {
|
|
input.openOutsideDirectory(destination.directoryPath);
|
|
} else if (normalized.file) {
|
|
const resolvedTarget =
|
|
input.resolveRelativeFiles && input.client
|
|
? await resolveMissingNestedRelativeFile({
|
|
target,
|
|
workspaceRoot: input.workspaceRoot,
|
|
getDirectorySuggestions: input.client.getDirectorySuggestions.bind(input.client),
|
|
})
|
|
: target;
|
|
if (ownGeneration !== generation) return;
|
|
const resolved = normalizeInlinePathTarget(resolvedTarget.path, input.workspaceRoot);
|
|
if (!resolved?.file) return;
|
|
const location = normalizeWorkspaceFileLocation({
|
|
path: resolved.file,
|
|
lineStart: resolvedTarget.lineStart,
|
|
lineEnd: resolvedTarget.lineEnd,
|
|
});
|
|
if (location) input.openFile(location, disposition);
|
|
}
|
|
},
|
|
cancel() {
|
|
generation += 1;
|
|
},
|
|
};
|
|
}
|