summaryrefslogtreecommitdiffhomepage
path: root/packages/lsp/src/root.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/lsp/src/root.ts')
-rw-r--r--packages/lsp/src/root.ts44
1 files changed, 44 insertions, 0 deletions
diff --git a/packages/lsp/src/root.ts b/packages/lsp/src/root.ts
new file mode 100644
index 0000000..fc7814e
--- /dev/null
+++ b/packages/lsp/src/root.ts
@@ -0,0 +1,44 @@
+/**
+ * Root finder — nearest ancestor containing a marker file, bounded at cwd.
+ */
+
+export async function findRoot(
+ startDir: string,
+ cwd: string,
+ markers: readonly string[],
+ exists: (path: string) => Promise<boolean>,
+): Promise<string> {
+ const normalizedStart = normalizePath(startDir);
+ const normalizedCwd = normalizePath(cwd);
+
+ let current = normalizedStart;
+ while (true) {
+ for (const marker of markers) {
+ const markerPath = current === "/" ? `/${marker}` : `${current}/${marker}`;
+ if (await exists(markerPath)) {
+ return current;
+ }
+ }
+ if (current === normalizedCwd || current === "/") {
+ return normalizedCwd;
+ }
+ const parent = getParent(current);
+ if (parent === current) return normalizedCwd;
+ current = parent;
+ }
+}
+
+function normalizePath(p: string): string {
+ let normalized = p.replace(/\\/g, "/");
+ if (normalized.length > 1 && normalized.endsWith("/")) {
+ normalized = normalized.slice(0, -1);
+ }
+ return normalized || "/";
+}
+
+function getParent(p: string): string {
+ if (p === "/") return "/";
+ const lastSlash = p.lastIndexOf("/");
+ if (lastSlash <= 0) return "/";
+ return p.slice(0, lastSlash) || "/";
+}