oh-my-opencode/src/tools/lsp/lsp-client.ts
YeonGyu-Kim 29155ec7bc refactor: wave 1 - extract leaf modules, rename catch-all files, split index.ts hooks
- Split 25+ index.ts files into hook.ts + extracted modules
- Rename all catch-all utils.ts/helpers.ts to domain-specific names
- Split src/tools/lsp/ into ~15 focused modules
- Split src/tools/delegate-task/ into ~18 focused modules
- Separate shared types from implementation
- 155 files changed, 60+ new files created
- All typecheck clean, 61 tests pass
2026-02-08 13:57:26 +09:00

130 lines
4.0 KiB
TypeScript

import { readFileSync } from "fs"
import { extname, resolve } from "path"
import { pathToFileURL } from "node:url"
import { getLanguageId } from "./config"
import { LSPClientConnection } from "./lsp-client-connection"
import type { Diagnostic } from "./types"
export class LSPClient extends LSPClientConnection {
private openedFiles = new Set<string>()
private documentVersions = new Map<string, number>()
private lastSyncedText = new Map<string, string>()
async openFile(filePath: string): Promise<void> {
const absPath = resolve(filePath)
const uri = pathToFileURL(absPath).href
const text = readFileSync(absPath, "utf-8")
if (!this.openedFiles.has(absPath)) {
const ext = extname(absPath)
const languageId = getLanguageId(ext)
const version = 1
this.sendNotification("textDocument/didOpen", {
textDocument: {
uri,
languageId,
version,
text,
},
})
this.openedFiles.add(absPath)
this.documentVersions.set(uri, version)
this.lastSyncedText.set(uri, text)
await new Promise((r) => setTimeout(r, 1000))
return
}
const prevText = this.lastSyncedText.get(uri)
if (prevText === text) {
return
}
const nextVersion = (this.documentVersions.get(uri) ?? 1) + 1
this.documentVersions.set(uri, nextVersion)
this.lastSyncedText.set(uri, text)
this.sendNotification("textDocument/didChange", {
textDocument: { uri, version: nextVersion },
contentChanges: [{ text }],
})
// Some servers update diagnostics only after save
this.sendNotification("textDocument/didSave", {
textDocument: { uri },
text,
})
}
async definition(filePath: string, line: number, character: number): Promise<unknown> {
const absPath = resolve(filePath)
await this.openFile(absPath)
return this.sendRequest("textDocument/definition", {
textDocument: { uri: pathToFileURL(absPath).href },
position: { line: line - 1, character },
})
}
async references(filePath: string, line: number, character: number, includeDeclaration = true): Promise<unknown> {
const absPath = resolve(filePath)
await this.openFile(absPath)
return this.sendRequest("textDocument/references", {
textDocument: { uri: pathToFileURL(absPath).href },
position: { line: line - 1, character },
context: { includeDeclaration },
})
}
async documentSymbols(filePath: string): Promise<unknown> {
const absPath = resolve(filePath)
await this.openFile(absPath)
return this.sendRequest("textDocument/documentSymbol", {
textDocument: { uri: pathToFileURL(absPath).href },
})
}
async workspaceSymbols(query: string): Promise<unknown> {
return this.sendRequest("workspace/symbol", { query })
}
async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> {
const absPath = resolve(filePath)
const uri = pathToFileURL(absPath).href
await this.openFile(absPath)
await new Promise((r) => setTimeout(r, 500))
try {
const result = await this.sendRequest<{ items?: Diagnostic[] }>("textDocument/diagnostic", {
textDocument: { uri },
})
if (result && typeof result === "object" && "items" in result) {
return result as { items: Diagnostic[] }
}
} catch {}
return { items: this.diagnosticsStore.get(uri) ?? [] }
}
async prepareRename(filePath: string, line: number, character: number): Promise<unknown> {
const absPath = resolve(filePath)
await this.openFile(absPath)
return this.sendRequest("textDocument/prepareRename", {
textDocument: { uri: pathToFileURL(absPath).href },
position: { line: line - 1, character },
})
}
async rename(filePath: string, line: number, character: number, newName: string): Promise<unknown> {
const absPath = resolve(filePath)
await this.openFile(absPath)
return this.sendRequest("textDocument/rename", {
textDocument: { uri: pathToFileURL(absPath).href },
position: { line: line - 1, character },
newName,
})
}
}