feat(coder): add LSP code intelligence tools
- 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
This commit is contained in:
75
apps/coder/src/services/lsp/client.ts
Normal file
75
apps/coder/src/services/lsp/client.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user