import fs from 'fs'; import * as process from 'process'; import { getDiagnostics } from './parser'; export function log(str: string) { fs.appendFileSync('/home/sylv/latex-lsp/test', str + "\n"); } export function error(str: string) { log("ERROR:" + str); process.exit(1); } let init = false; type RPCResult = { jsonrpc: string, id: string, } & ({ result: any, } | { error: any, }) type RPCRequest = { id: string; jsonrpc: string, method: string, params: string, } type InitRequest = RPCRequest & { method: 'initialize', } // ASSUMES that is has the right format function sendRPC(obj: any) { const msg = JSON.stringify(obj); const buff = Buffer.from(msg, 'utf-8'); const fullmessage = `Content-Length: ${buff.byteLength}\r\n\r\n${msg}`; log("Sending message: " + fullmessage.replace(/\n/g, '\\n').replace(/\r/g, '\\r')) process.stdout.cork() process.stdout.write(fullmessage); process.stdout.uncork(); } function sendRPCMessage(req: RPCRequest, result: any = undefined, error: any = undefined) { if (!result && !error) { error("Invalid rpc message to send") return; } sendRPC({ id: req.id, jsonrpc: req.jsonrpc, result, error } as RPCResult) } function notifyRPC(method: string, params: any) { sendRPC({ jsonrpc: '2.0', method, params, }) } type TextDocumentDidOpen = RPCRequest & { method: 'textDocument/didOpen', params: { textDocument: { uri: string; languageId: string; version: number; text: string; } } } type TextDocumentCodeAction = RPCRequest & { method: 'textDocument/codeAction', params: { range: Range, textDocument: { uri: string, } }, }; type Position = { line: number, character: number } type Range = { start: Position, end: Position, } export type Dialog = { range: Range, message: string, severity: Severity, } export enum Severity { Error = 1, Warning = 2, Information = 3, Hint = 4, } const saveRes: Record = { }; async function handleJSON(req: RPCRequest) { log(`New Message entered: method: ${req.method}`); if (init) { if (req.method === 'initialized') { // On init confirm do nothing return; } else if (req.method === 'textDocument/didOpen') { const reqO: TextDocumentDidOpen = req as any; if (reqO.params.textDocument.languageId !== 'latex') { error(`This server only supports latex! Got: ${reqO.params.textDocument.languageId}`) return; } const diags = await getDiagnostics(reqO.params.textDocument.text); saveRes[reqO.params.textDocument.uri] = diags; const params = { uri: reqO.params.textDocument.uri, diagnostics: diags.map(a => ({ message: a.message, range: a.range, severity: a.severity, }) as Dialog), } notifyRPC('textDocument/publishDiagnostics', params); //error(`TODO ${req.method}`); } else if (req.method === 'textDocument/codeAction') { const reqO: TextDocumentCodeAction = req as any; const diagRecord = saveRes[reqO.params.textDocument.uri]; if (!diagRecord) { sendRPCMessage(req, [{ title: "No diagnostics found in this file" }]); return; } log(JSON.stringify(req.params)); error("TODO handle " + req.method); } else { error(`Handle: ${req.method} after init`); } } else { if (req.method === 'initialize') { log("Recived init"); init = true; log(JSON.stringify(req)); sendRPCMessage(req, { capabilities: { diagnosticProvider: { // TODO change in the future when the server also checks the ib file as well interFileDependencies: false, workspaceDiagnostics: false, }, codeActionProvider: { codeActionKinds: [ "quickfix" ] } }, serverInfo: { name: "Super Cool latex server", version: "1.0.0", } }); return; } else { error(`Expected init method found '${req.method}'`) } } } export function handleData(data: Buffer) { let strData = data.toString(); let jsonData = strData.split('\n'); let req: RPCRequest; if (jsonData.length === 3) { try { req = JSON.parse(jsonData[2]); } catch (e) { log("Failed to parse json 1/2! Msg:\n"); log(strData.replace(/\n/g, '\\n').replace(/\r/g, '\\r')); log("\n\nFailed to parse json 2/2! JSON Line:\n"); log(jsonData[2]); return; } handleJSON(req); return; } for (let i = 2; i < jsonData.length; i += 2) { let jsonLine = jsonData[i]; if (i != jsonData.length - 1) { let index = jsonLine.indexOf('Content-Length'); if (index == -1) { error("Handling multiline expected 'Content-Length'"); return; } try { req = JSON.parse(jsonLine.substring(0, index)); } catch (e) { log("Failed to parse json 1/2! Msg:\n"); log(strData.replace(/\n/g, '\\n').replace(/\r/g, '\\r')); log("\n\nFailed to parse json 2/2! JSON Line:\n"); log(jsonLine.substring(0, index)); return; } } else { try { req = JSON.parse(jsonLine); } catch (e) { log("Failed to parse json 1/2! Msg:\n"); log(strData.replace(/\n/g, '\\n').replace(/\r/g, '\\r')); log("\n\nFailed to parse json 2/2! JSON Line:\n"); log(jsonLine); return; } } handleJSON(req); } }