Inital work on the lsp
This commit is contained in:
218
lsp.ts
Normal file
218
lsp.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
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 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,
|
||||
}
|
||||
|
||||
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 params = {
|
||||
uri: reqO.params.textDocument.uri,
|
||||
diagnostics: (await getDiagnostics(reqO.params.textDocument.text)),
|
||||
}
|
||||
|
||||
notifyRPC('textDocument/publishDiagnostics', params);
|
||||
|
||||
|
||||
//error(`TODO ${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,
|
||||
}
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user