Skip to content

Commit 499eeaa

Browse files
committed
fix(win32): Normalise LSP paths on windows
1 parent d0ae6a5 commit 499eeaa

5 files changed

Lines changed: 41 additions & 46 deletions

File tree

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

Lines changed: 5 additions & 18 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

@@ -1415,14 +1416,8 @@ ToolRegistry.register<typeof WriteTool>({
14151416
})
14161417

14171418
const diagnostics = createMemo(() => {
1418-
const filePath = props.input.filePath ?? ""
1419-
const diags = props.metadata.diagnostics ?? {}
1420-
// Case-insensitive lookup on Windows
1421-
if (process.platform === "win32") {
1422-
const key = Object.keys(diags).find((k) => k.toLowerCase() === filePath.toLowerCase())
1423-
return key ? diags[key] : []
1424-
}
1425-
return diags[filePath] ?? []
1419+
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
1420+
return props.metadata.diagnostics?.[filePath] ?? []
14261421
})
14271422

14281423
return (
@@ -1596,16 +1591,8 @@ ToolRegistry.register<typeof EditTool>({
15961591
const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
15971592

15981593
const diagnostics = createMemo(() => {
1599-
const filePath = props.input.filePath ?? ""
1600-
const diags = props.metadata.diagnostics ?? {}
1601-
// Case-insensitive lookup on Windows
1602-
let arr: any[]
1603-
if (process.platform === "win32") {
1604-
const key = Object.keys(diags).find((k) => k.toLowerCase() === filePath.toLowerCase())
1605-
arr = key ? diags[key] : []
1606-
} else {
1607-
arr = diags[filePath] ?? []
1608-
}
1594+
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
1595+
const arr = props.metadata.diagnostics?.[filePath] ?? []
16091596
return arr.filter((x) => x.severity === 1).slice(0, 3)
16101597
})
16111598

packages/opencode/src/lsp/client.ts

Lines changed: 12 additions & 13 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,15 +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,
5354
count: params.diagnostics.length,
5455
})
55-
const exists = diagnostics.has(path)
56-
diagnostics.set(path, params.diagnostics)
56+
const exists = diagnostics.has(filePath)
57+
diagnostics.set(filePath, params.diagnostics)
5758
if (!exists && input.serverID === "typescript") return
58-
Bus.publish(Event.Diagnostics, { path, serverID: input.serverID })
59+
Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID })
5960
})
6061
connection.onRequest("window/workDoneProgress/create", (params) => {
6162
l.info("window/workDoneProgress/create", params)
@@ -182,18 +183,16 @@ export namespace LSPClient {
182183
return diagnostics
183184
},
184185
async waitForDiagnostics(input: { path: string }) {
185-
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
186-
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 })
187190
let unsub: () => void
188191
return await withTimeout(
189192
new Promise<void>((resolve) => {
190193
unsub = Bus.subscribe(Event.Diagnostics, (event) => {
191-
const pathsMatch =
192-
process.platform === "win32"
193-
? event.properties.path.toLowerCase() === input.path.toLowerCase()
194-
: event.properties.path === input.path
195-
if (pathsMatch && event.properties.serverID === result.serverID) {
196-
log.info("got diagnostics", input)
194+
if (event.properties.path === normalizedPath && event.properties.serverID === result.serverID) {
195+
log.info("got diagnostics", { path: normalizedPath })
197196
unsub?.()
198197
resolve()
199198
}

packages/opencode/src/tool/edit.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +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-
const fileMatches =
146-
process.platform === "win32" ? file.toLowerCase() === filePath.toLowerCase() : file === filePath
147-
if (fileMatches) {
148-
const errors = issues.filter((item) => item.severity === 1)
149-
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
150-
const suffix =
151-
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
152-
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
153-
continue
154-
}
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`
155151
}
156152

157153
const filediff: Snapshot.FileDiff = {

packages/opencode/src/tool/write.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +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-
const fileMatches =
91-
process.platform === "win32" ? file.toLowerCase() === filepath.toLowerCase() : file === filepath
92-
if (fileMatches) {
91+
if (file === normalizedFilepath) {
9392
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
9493
continue
9594
}

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)