summaryrefslogtreecommitdiffhomepage
path: root/js/src/lsp/client.ts
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-30 20:47:56 -0400
committerDax Raad <[email protected]>2025-05-30 20:48:36 -0400
commitf3da73553c45f17e04b1e77cb13eb0fca714d1bd (patch)
treea24317a19e1ab2a89da50db669dc6894f15d00d1 /js/src/lsp/client.ts
parent9a26b3058ffc1023e5c7e54b6d571c903d15888e (diff)
downloadopencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.tar.gz
opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.zip
sync
Diffstat (limited to 'js/src/lsp/client.ts')
-rw-r--r--js/src/lsp/client.ts208
1 files changed, 0 insertions, 208 deletions
diff --git a/js/src/lsp/client.ts b/js/src/lsp/client.ts
deleted file mode 100644
index 82caa82a2..000000000
--- a/js/src/lsp/client.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import { spawn } from "child_process";
-import path from "path";
-import {
- createMessageConnection,
- StreamMessageReader,
- StreamMessageWriter,
-} from "vscode-jsonrpc/node";
-import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types";
-import { App } from "../app/app";
-import { Log } from "../util/log";
-import { LANGUAGE_EXTENSIONS } from "./language";
-import { Bus } from "../bus";
-import z from "zod";
-
-export namespace LSPClient {
- const log = Log.create({ service: "lsp.client" });
-
- export type Info = Awaited<ReturnType<typeof create>>;
-
- export type Diagnostic = VSCodeDiagnostic;
-
- export const Event = {
- Diagnostics: Bus.event(
- "lsp.client.diagnostics",
- z.object({
- serverID: z.string(),
- path: z.string(),
- }),
- ),
- };
-
- export async function create(input: { cmd: string[]; serverID: string }) {
- log.info("starting client", input);
-
- const app = await App.use();
- const [command, ...args] = input.cmd;
- const server = spawn(command, args, {
- stdio: ["pipe", "pipe", "pipe"],
- cwd: app.root,
- });
-
- const connection = createMessageConnection(
- new StreamMessageReader(server.stdout),
- new StreamMessageWriter(server.stdin),
- );
-
- const diagnostics = new Map<string, Diagnostic[]>();
- connection.onNotification("textDocument/publishDiagnostics", (params) => {
- const path = new URL(params.uri).pathname;
- log.info("textDocument/publishDiagnostics", {
- path,
- });
- const exists = diagnostics.has(path);
- diagnostics.set(path, params.diagnostics);
- // servers seem to send one blank publishDiagnostics event before the first real one
- if (!exists && !params.diagnostics.length) return;
- Bus.publish(Event.Diagnostics, { path, serverID: input.serverID });
- });
- connection.listen();
-
- await connection.sendRequest("initialize", {
- processId: server.pid,
- initializationOptions: {
- workspaceFolders: [
- {
- name: "workspace",
- uri: "file://" + app.root,
- },
- ],
- tsserver: {
- path: require.resolve("typescript/lib/tsserver.js"),
- },
- },
- capabilities: {
- workspace: {
- configuration: true,
- didChangeConfiguration: {
- dynamicRegistration: true,
- },
- didChangeWatchedFiles: {
- dynamicRegistration: true,
- relativePatternSupport: true,
- },
- },
- textDocument: {
- synchronization: {
- dynamicRegistration: true,
- didSave: true,
- },
- completion: {
- completionItem: {},
- },
- codeLens: {
- dynamicRegistration: true,
- },
- documentSymbol: {},
- codeAction: {
- codeActionLiteralSupport: {
- codeActionKind: {
- valueSet: [],
- },
- },
- },
- publishDiagnostics: {
- versionSupport: true,
- },
- semanticTokens: {
- requests: {
- range: {},
- full: {},
- },
- tokenTypes: [],
- tokenModifiers: [],
- formats: [],
- },
- },
- window: {},
- },
- });
- await connection.sendNotification("initialized", {});
- log.info("initialized");
-
- const files = new Set<string>();
-
- const result = {
- get clientID() {
- return input.serverID;
- },
- get connection() {
- return connection;
- },
- notify: {
- async open(input: { path: string }) {
- const file = Bun.file(input.path);
- const text = await file.text();
- const opened = files.has(input.path);
- if (!opened) {
- log.info("textDocument/didOpen", input);
- diagnostics.delete(input.path);
- const extension = path.extname(input.path);
- const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext";
- await connection.sendNotification("textDocument/didOpen", {
- textDocument: {
- uri: `file://` + input.path,
- languageId,
- version: Date.now(),
- text,
- },
- });
- files.add(input.path);
- return;
- }
-
- log.info("textDocument/didChange", input);
- diagnostics.delete(input.path);
- await connection.sendNotification("textDocument/didChange", {
- textDocument: {
- uri: `file://` + input.path,
- version: Date.now(),
- },
- contentChanges: [
- {
- text,
- },
- ],
- });
- },
- },
- get diagnostics() {
- return diagnostics;
- },
- async waitForDiagnostics(input: { path: string }) {
- log.info("waiting for diagnostics", input);
- let unsub: () => void;
- let timeout: NodeJS.Timeout;
- return await Promise.race([
- new Promise<void>(async (resolve) => {
- unsub = Bus.subscribe(Event.Diagnostics, (event) => {
- if (
- event.properties.path === input.path &&
- event.properties.serverID === result.clientID
- ) {
- log.info("got diagnostics", input);
- clearTimeout(timeout);
- unsub?.();
- resolve();
- }
- });
- }),
- new Promise<void>((resolve) => {
- timeout = setTimeout(() => {
- log.info("timed out refreshing diagnostics", input);
- unsub?.();
- resolve();
- }, 5000);
- }),
- ]);
- },
- async shutdown() {
- log.info("shutting down");
- connection.end();
- connection.dispose();
- },
- };
-
- return result;
- }
-}