From 8ed3f7e03b90c1e952eabbc71a41b1fce0cd94f9 Mon Sep 17 00:00:00 2001 From: yimingll <116444509+yimingll@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:01:54 +0800 Subject: [PATCH] fix: LSP tools Windows compatibility - use pathToFileURL for proper URI generation (#689) --- src/tools/lsp/client.ts | 21 +++++++++++---------- src/tools/lsp/utils.ts | 4 +++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/tools/lsp/client.ts b/src/tools/lsp/client.ts index 725594bee..449dce6fd 100644 --- a/src/tools/lsp/client.ts +++ b/src/tools/lsp/client.ts @@ -1,6 +1,7 @@ import { spawn, type Subprocess } from "bun" import { readFileSync } from "fs" import { extname, resolve } from "path" +import { pathToFileURL } from "node:url" import { getLanguageId } from "./config" import type { Diagnostic, ResolvedServer } from "./types" @@ -427,7 +428,7 @@ export class LSPClient { } async initialize(): Promise { - const rootUri = `file://${this.root}` + const rootUri = pathToFileURL(this.root).href await this.send("initialize", { processId: process.pid, rootUri, @@ -497,7 +498,7 @@ export class LSPClient { this.notify("textDocument/didOpen", { textDocument: { - uri: `file://${absPath}`, + uri: pathToFileURL(absPath).href, languageId, version: 1, text, @@ -512,7 +513,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/hover", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, position: { line: line - 1, character }, }) } @@ -521,7 +522,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/definition", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, position: { line: line - 1, character }, }) } @@ -530,7 +531,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/references", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, position: { line: line - 1, character }, context: { includeDeclaration }, }) @@ -540,7 +541,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/documentSymbol", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, }) } @@ -550,7 +551,7 @@ export class LSPClient { async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> { const absPath = resolve(filePath) - const uri = `file://${absPath}` + const uri = pathToFileURL(absPath).href await this.openFile(absPath) await new Promise((r) => setTimeout(r, 500)) @@ -571,7 +572,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/prepareRename", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, position: { line: line - 1, character }, }) } @@ -580,7 +581,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/rename", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, position: { line: line - 1, character }, newName, }) @@ -597,7 +598,7 @@ export class LSPClient { const absPath = resolve(filePath) await this.openFile(absPath) return this.send("textDocument/codeAction", { - textDocument: { uri: `file://${absPath}` }, + textDocument: { uri: pathToFileURL(absPath).href }, range: { start: { line: startLine - 1, character: startChar }, end: { line: endLine - 1, character: endChar }, diff --git a/src/tools/lsp/utils.ts b/src/tools/lsp/utils.ts index b2ca7603a..99956af11 100644 --- a/src/tools/lsp/utils.ts +++ b/src/tools/lsp/utils.ts @@ -30,12 +30,14 @@ export function findWorkspaceRoot(filePath: string): string { const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"] - while (dir !== "/") { + let prevDir = "" + while (dir !== prevDir) { for (const marker of markers) { if (existsSync(require("path").join(dir, marker))) { return dir } } + prevDir = dir dir = require("path").dirname(dir) }