- lsp/ module: types, config, JSON-RPC client, server-manager, operations - lsp_diagnostics: TypeScript/JavaScript diagnostics for a file - lsp_goto_definition: find symbol definition at position - lsp_find_references: find all references to a symbol - Registered as READ_TOOLS in tool index
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { createInterface } from 'node:readline';
|
|
import type { Readable, Writable } from 'node:stream';
|
|
|
|
interface RpcRequest {
|
|
jsonrpc: '2.0';
|
|
id: number;
|
|
method: string;
|
|
params?: unknown;
|
|
}
|
|
|
|
interface RpcResponse {
|
|
jsonrpc: '2.0';
|
|
id: number;
|
|
result?: unknown;
|
|
error?: { code: number; message: string };
|
|
}
|
|
|
|
export class LspClient {
|
|
private nextId = 1;
|
|
private pending = new Map<number, { resolve: (v: RpcResponse) => void; reject: (e: Error) => void }>();
|
|
private buffer = '';
|
|
|
|
constructor(
|
|
private stdin: Writable,
|
|
private stdout: Readable,
|
|
) {
|
|
const rl = createInterface({ input: stdout, crlfDelay: Infinity });
|
|
rl.on('line', (line) => this.handleLine(line));
|
|
}
|
|
|
|
private handleLine(line: string): void {
|
|
this.buffer += line + '\n';
|
|
const match = this.buffer.match(/Content-Length: (\d+)\r?\n\r?\n/);
|
|
if (!match || !match[1]) return;
|
|
const len = parseInt(match[1], 10);
|
|
const headerEnd = match.index! + match[0].length;
|
|
const body = this.buffer.slice(headerEnd, headerEnd + len);
|
|
if (body.length < len) return;
|
|
this.buffer = this.buffer.slice(headerEnd + len);
|
|
try {
|
|
const msg: RpcResponse = JSON.parse(body);
|
|
const cb = this.pending.get(msg.id);
|
|
if (cb) {
|
|
this.pending.delete(msg.id);
|
|
cb.resolve(msg);
|
|
}
|
|
} catch {
|
|
// Malformed JSON, ignore
|
|
}
|
|
}
|
|
|
|
async request(method: string, params?: unknown): Promise<unknown> {
|
|
const id = this.nextId++;
|
|
const req: RpcRequest = { jsonrpc: '2.0', id, method, params };
|
|
const body = JSON.stringify(req);
|
|
const header = `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.pending.set(id, {
|
|
resolve: (resp) => {
|
|
if (resp.error) reject(new Error(resp.error.message));
|
|
else resolve(resp.result);
|
|
},
|
|
reject,
|
|
});
|
|
this.stdin.write(header + body);
|
|
});
|
|
}
|
|
|
|
async notify(method: string, params?: unknown): Promise<void> {
|
|
const body = JSON.stringify({ jsonrpc: '2.0', method, params });
|
|
const header = `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n`;
|
|
this.stdin.write(header + body);
|
|
}
|
|
}
|