Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 27e826e

Browse files
authored
fix(win32): Normalise LSP paths on windows (fixes lua) (anomalyco#5597)
1 parent 89a4f1c commit 27e826e

5 files changed

Lines changed: 44 additions & 22 deletions

File tree

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import { Editor } from "../../util/editor"
6464
import stripAnsi from "strip-ansi"
6565
import { Footer } from "./footer.tsx"
6666
import { usePromptRef } from "../../context/prompt"
67+
import { Filesystem } from "@/util/filesystem"
6768

6869
addDefaultParsers(parsers.parsers)
6970

@@ -1414,7 +1415,10 @@ ToolRegistry.register<typeof WriteTool>({
14141415
return props.input.content
14151416
})
14161417

1417-
const diagnostics = createMemo(() => props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? [])
1418+
const diagnostics = createMemo(() => {
1419+
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
1420+
return props.metadata.diagnostics?.[filePath] ?? []
1421+
})
14181422

14191423
return (
14201424
<>
@@ -1587,7 +1591,8 @@ ToolRegistry.register<typeof EditTool>({
15871591
const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
15881592

15891593
const diagnostics = createMemo(() => {
1590-
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
1594+
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
1595+
const arr = props.metadata.diagnostics?.[filePath] ?? []
15911596
return arr.filter((x) => x.severity === 1).slice(0, 3)
15921597
})
15931598

packages/opencode/src/lsp/client.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { LSPServer } from "./server"
1111
import { NamedError } from "@opencode-ai/util/error"
1212
import { withTimeout } from "../util/timeout"
1313
import { Instance } from "../project/instance"
14+
import { Filesystem } from "../util/filesystem"
1415

1516
export namespace LSPClient {
1617
const log = Log.create({ service: "lsp.client" })
@@ -47,14 +48,15 @@ export namespace LSPClient {
4748

4849
const diagnostics = new Map<string, Diagnostic[]>()
4950
connection.onNotification("textDocument/publishDiagnostics", (params) => {
50-
const path = fileURLToPath(params.uri)
51+
const filePath = Filesystem.normalizePath(fileURLToPath(params.uri))
5152
l.info("textDocument/publishDiagnostics", {
52-
path,
53+
path: filePath,
54+
count: params.diagnostics.length,
5355
})
54-
const exists = diagnostics.has(path)
55-
diagnostics.set(path, params.diagnostics)
56+
const exists = diagnostics.has(filePath)
57+
diagnostics.set(filePath, params.diagnostics)
5658
if (!exists && input.serverID === "typescript") return
57-
Bus.publish(Event.Diagnostics, { path, serverID: input.serverID })
59+
Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID })
5860
})
5961
connection.onRequest("window/workDoneProgress/create", (params) => {
6062
l.info("window/workDoneProgress/create", params)
@@ -181,14 +183,16 @@ export namespace LSPClient {
181183
return diagnostics
182184
},
183185
async waitForDiagnostics(input: { path: string }) {
184-
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
185-
log.info("waiting for diagnostics", input)
186+
const normalizedPath = Filesystem.normalizePath(
187+
path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path),
188+
)
189+
log.info("waiting for diagnostics", { path: normalizedPath })
186190
let unsub: () => void
187191
return await withTimeout(
188192
new Promise<void>((resolve) => {
189193
unsub = Bus.subscribe(Event.Diagnostics, (event) => {
190-
if (event.properties.path === input.path && event.properties.serverID === result.serverID) {
191-
log.info("got diagnostics", input)
194+
if (event.properties.path === normalizedPath && event.properties.serverID === result.serverID) {
195+
log.info("got diagnostics", { path: normalizedPath })
192196
unsub?.()
193197
resolve()
194198
}

packages/opencode/src/tool/edit.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,14 @@ export const EditTool = Tool.define("edit", {
140140
let output = ""
141141
await LSP.touchFile(filePath, true)
142142
const diagnostics = await LSP.diagnostics()
143-
for (const [file, issues] of Object.entries(diagnostics)) {
144-
if (issues.length === 0) continue
145-
if (file === filePath) {
146-
const errors = issues.filter((item) => item.severity === 1)
147-
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
148-
const suffix =
149-
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
150-
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
151-
continue
152-
}
143+
const normalizedFilePath = Filesystem.normalizePath(filePath)
144+
const issues = diagnostics[normalizedFilePath] ?? []
145+
if (issues.length > 0) {
146+
const errors = issues.filter((item) => item.severity === 1)
147+
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
148+
const suffix =
149+
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
150+
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
153151
}
154152

155153
const filediff: Snapshot.FileDiff = {

packages/opencode/src/tool/write.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,15 @@ export const WriteTool = Tool.define("write", {
8080
let output = ""
8181
await LSP.touchFile(filepath, true)
8282
const diagnostics = await LSP.diagnostics()
83+
const normalizedFilepath = Filesystem.normalizePath(filepath)
8384
let projectDiagnosticsCount = 0
8485
for (const [file, issues] of Object.entries(diagnostics)) {
8586
if (issues.length === 0) continue
8687
const sorted = issues.toSorted((a, b) => (a.severity ?? 4) - (b.severity ?? 4))
8788
const limited = sorted.slice(0, MAX_DIAGNOSTICS_PER_FILE)
8889
const suffix =
8990
issues.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${issues.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
90-
if (file === filepath) {
91+
if (file === normalizedFilepath) {
9192
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
9293
continue
9394
}

packages/opencode/src/util/filesystem.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
import { realpathSync } from "fs"
12
import { exists } from "fs/promises"
23
import { dirname, join, relative } from "path"
34

45
export namespace Filesystem {
6+
/**
7+
* On Windows, normalize a path to its canonical casing using the filesystem.
8+
* This is needed because Windows paths are case-insensitive but LSP servers
9+
* may return paths with different casing than what we send them.
10+
*/
11+
export function normalizePath(p: string): string {
12+
if (process.platform !== "win32") return p
13+
try {
14+
return realpathSync.native(p)
15+
} catch {
16+
return p
17+
}
18+
}
519
export function overlaps(a: string, b: string) {
620
const relA = relative(a, b)
721
const relB = relative(b, a)

0 commit comments

Comments
 (0)