Update lsp
This commit is contained in:
parent
11e7c303e3
commit
7b1e9e063e
6
index.ts
6
index.ts
@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import process from 'process';
|
||||
import { diagnosticsRequests, getDiagnostics, parseLsp } from './parser';
|
||||
import { add_personal } from './languagetool';
|
||||
|
||||
if (process.argv.length !== 3) {
|
||||
console.log("Please provide only one pass");
|
||||
@ -13,4 +14,7 @@ const res = parseLsp(file);
|
||||
|
||||
fs.writeFileSync('./res', res.text);
|
||||
|
||||
console.log((await diagnosticsRequests(res)).map(a => a.replacements));
|
||||
//const diag = await diagnosticsRequests(res);
|
||||
const diag = await getDiagnostics(file);
|
||||
|
||||
//add_personal(['AutoML']);
|
||||
|
22
languagetool.ts
Normal file
22
languagetool.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export async function add_personal(words: string[]): Promise<void> {
|
||||
console.log("adding " + JSON.stringify({ words }));
|
||||
|
||||
try {
|
||||
const res = await fetch('https://api.languagetoolplus.com/enterprise/v1/dictionary/words?type=personal', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'authorization': process.env.AUTHORIZATION ?? '',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
words
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(await res.text());
|
||||
} catch (e) {
|
||||
console.log("got error:" + e);
|
||||
throw e;
|
||||
}
|
||||
}
|
173
lsp.ts
173
lsp.ts
@ -1,9 +1,10 @@
|
||||
import fs from 'fs';
|
||||
import * as process from 'process';
|
||||
import { getDiagnostics } from './parser';
|
||||
import { getDiagnostics, type GetDiagnosticsReturn } from './parser';
|
||||
import { add_personal } from './languagetool';
|
||||
|
||||
export function log(str: string) {
|
||||
fs.appendFileSync('/home/sylv/latex-lsp/test', str + "\n");
|
||||
fs.appendFileSync('/tmp/latex-lsp-test', str + "\n");
|
||||
}
|
||||
|
||||
export function error(str: string) {
|
||||
@ -26,18 +27,14 @@ type RPCRequest = {
|
||||
id: string;
|
||||
jsonrpc: string,
|
||||
method: string,
|
||||
params: string,
|
||||
}
|
||||
|
||||
type InitRequest = RPCRequest & {
|
||||
method: 'initialize',
|
||||
params: any,
|
||||
}
|
||||
|
||||
// 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'))
|
||||
@ -91,6 +88,15 @@ type TextDocumentCodeAction = RPCRequest & {
|
||||
},
|
||||
};
|
||||
|
||||
type WorkspaceExecuteCommand = RPCRequest & {
|
||||
method: 'workspace/executeCommand',
|
||||
params: {
|
||||
command: string,
|
||||
arguments: any[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Position = {
|
||||
line: number,
|
||||
character: number
|
||||
@ -105,6 +111,7 @@ export type Dialog = {
|
||||
range: Range,
|
||||
message: string,
|
||||
severity: Severity,
|
||||
data?: any,
|
||||
}
|
||||
|
||||
export enum Severity {
|
||||
@ -114,7 +121,36 @@ export enum Severity {
|
||||
Hint = 4,
|
||||
}
|
||||
|
||||
const saveRes: Record<string, (Dialog & {replacements: string[]})[]> = { };
|
||||
let lastRequest: null | Date = null;
|
||||
|
||||
const saveRes: Record<string, GetDiagnosticsReturn[]> = {};
|
||||
|
||||
async function request(file: string, uri: string) {
|
||||
if (lastRequest) {
|
||||
if (((new Date()).getTime() - lastRequest.getTime()) <= 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastRequest = new Date();
|
||||
let diags = await getDiagnostics(file);
|
||||
|
||||
saveRes[uri] = diags;
|
||||
|
||||
const params = {
|
||||
uri: uri,
|
||||
diagnostics: diags.map((a, i) => ({
|
||||
message: a.message,
|
||||
range: a.range,
|
||||
severity: a.severity,
|
||||
data: i,
|
||||
}) as Dialog),
|
||||
}
|
||||
|
||||
notifyRPC('textDocument/publishDiagnostics', params);
|
||||
|
||||
return diags;
|
||||
}
|
||||
|
||||
|
||||
async function handleJSON(req: RPCRequest) {
|
||||
log(`New Message entered: method: ${req.method}`);
|
||||
@ -131,20 +167,7 @@ async function handleJSON(req: RPCRequest) {
|
||||
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);
|
||||
request(reqO.params.textDocument.text, reqO.params.textDocument.uri);
|
||||
|
||||
//error(`TODO ${req.method}`);
|
||||
} else if (req.method === 'textDocument/codeAction') {
|
||||
@ -153,15 +176,104 @@ async function handleJSON(req: RPCRequest) {
|
||||
const diagRecord = saveRes[reqO.params.textDocument.uri];
|
||||
|
||||
if (!diagRecord) {
|
||||
sendRPCMessage(req, [{
|
||||
title: "File does not seam to be loaded"
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
|
||||
const saved = saveRes[req.params.textDocument.uri];
|
||||
|
||||
const character = req.params.range.start.character;
|
||||
const line = req.params.range.start.line;
|
||||
|
||||
let item: GetDiagnosticsReturn | undefined = undefined;
|
||||
|
||||
console.log(JSON.stringify(req.params));
|
||||
|
||||
if (!req.params.context?.diagnostics || !req.params.context?.diagnostics[0] || typeof req.params.context?.diagnostics[0].data != 'number') {
|
||||
for (const _item of saved) {
|
||||
if (_item.range.end.line >= line && _item.range.start.line <= line) {
|
||||
if (_item.range.end.character >= character && _item.range.start.character <= line) {
|
||||
item = _item as GetDiagnosticsReturn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("Using " + req.params.context.diagnostics[0].data)
|
||||
item = saved[req.params.context.diagnostics[0].data];
|
||||
}
|
||||
|
||||
if (item === undefined) {
|
||||
sendRPCMessage(req, [{
|
||||
title: "No diagnostics found in this file"
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.replacements.length === 0) {
|
||||
sendRPCMessage(req, [{
|
||||
title: item.message,
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
|
||||
const items: {
|
||||
title: string,
|
||||
edit?: {
|
||||
changes: Record<string, { range: Range, newText: string }[]>
|
||||
},
|
||||
command?: {
|
||||
title: string,
|
||||
command: string,
|
||||
arguments?: any[]
|
||||
},
|
||||
}[] = item.replacements.map((value) => (
|
||||
{
|
||||
title: value,
|
||||
edit: {
|
||||
changes: {
|
||||
[reqO.params.textDocument.uri]: [
|
||||
{
|
||||
range: item!.range,
|
||||
newText: value
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
if (item.rule_id.startsWith("MORFOLOGIK_RULE")) {
|
||||
items.push({
|
||||
title: "Add to personal dictinoary",
|
||||
command: {
|
||||
title: "Add to personal dictionary",
|
||||
command: "add_to_dic",
|
||||
arguments: [
|
||||
reqO.params.textDocument.uri,
|
||||
item.word,
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendRPCMessage(req, items);
|
||||
return;
|
||||
} else if (req.method === 'workspace/executeCommand') {
|
||||
let reqO: WorkspaceExecuteCommand = req as any;
|
||||
|
||||
log(JSON.stringify(req.params));
|
||||
|
||||
error("TODO handle " + req.method);
|
||||
|
||||
if (reqO.params.command === 'add_to_dic') {
|
||||
if (reqO.params.arguments[0] && reqO.params.arguments[1]) {
|
||||
await add_personal([reqO.params.arguments[1]]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
error(`TODO Handle: ${req.method} ${JSON.stringify(req.params)}`);
|
||||
} else {
|
||||
error(`Handle: ${req.method} after init`);
|
||||
}
|
||||
@ -181,7 +293,16 @@ async function handleJSON(req: RPCRequest) {
|
||||
workspaceDiagnostics: false,
|
||||
},
|
||||
codeActionProvider: {
|
||||
codeActionKinds: [ "quickfix" ]
|
||||
codeActionKinds: ["quickfix"],
|
||||
|
||||
resolveSupport: {
|
||||
properties: ["edit"],
|
||||
},
|
||||
},
|
||||
executeCommandProvider: {
|
||||
commands: [
|
||||
"add_to_dic"
|
||||
]
|
||||
}
|
||||
},
|
||||
serverInfo: {
|
||||
|
107
parser.ts
107
parser.ts
@ -1,7 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import process from 'process';
|
||||
import { Severity, type Dialog } from './lsp';
|
||||
import { createSourceFile } from 'typescript';
|
||||
|
||||
|
||||
type ParseResultConversion = {
|
||||
@ -30,7 +31,8 @@ function parseComment(text: string, curPos: number): number {
|
||||
return text.length - curPos;
|
||||
}
|
||||
|
||||
function createPartition(text: string, startPos: number, curPos: number, result: string): [ParseResultConversion[], string] | null {
|
||||
function createPartition(text: string, startPos: number, curPos: number, result: string, ignoreLast: number = 0): [ParseResultConversion[], string] | null {
|
||||
curPos = curPos - ignoreLast;
|
||||
if (startPos >= curPos || text.substring(startPos, curPos).match(/^\s*$/)) {
|
||||
return null;
|
||||
}
|
||||
@ -74,7 +76,7 @@ function createPartition(text: string, startPos: number, curPos: number, result:
|
||||
}
|
||||
|
||||
function isChar(charCode: number): boolean {
|
||||
return (charCode >= 92 && charCode <= 122) || (charCode >= 65 && charCode <= 90);
|
||||
return (charCode >= 92 && charCode <= 122) || (charCode >= 65 && charCode <= 90) || charCode == 42;
|
||||
}
|
||||
function readBalanced(endChar: string, startChar: string, text: string, curPos: number): number {
|
||||
let bal = 1;
|
||||
@ -104,19 +106,26 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
}
|
||||
|
||||
if (text[curPos + 1] === '\\') {
|
||||
return [1, {
|
||||
return [2, {
|
||||
length: 1,
|
||||
position: result.length,
|
||||
original_position: curPos + 1,
|
||||
type: 'text'
|
||||
}, '\\'];
|
||||
} else if (text[curPos + 1] === '%') {
|
||||
return [1, {
|
||||
return [2, {
|
||||
length: 1,
|
||||
position: result.length,
|
||||
original_position: curPos + 1,
|
||||
type: 'text'
|
||||
}, '%'];
|
||||
} else if (text[curPos + 1] === '_') {
|
||||
return [2, {
|
||||
length: 1,
|
||||
position: result.length,
|
||||
original_position: curPos + 1,
|
||||
type: 'text'
|
||||
}, '_'];
|
||||
}
|
||||
|
||||
let commandName = "";
|
||||
@ -166,7 +175,7 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
}
|
||||
|
||||
console.log(text.substring(i - 20, i + 20));
|
||||
console.log('Char:', char.charCodeAt(0));
|
||||
console.log('Char:' + char.charCodeAt(0));
|
||||
throw new Error("TODO handle not char chars in the parse command function");
|
||||
}
|
||||
}
|
||||
@ -175,6 +184,9 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
throw new Error("Could not end of the command");
|
||||
}
|
||||
|
||||
//console.log("Parsed '" + text.substring(curPos, len) + "'")
|
||||
//console.log("Ranged '" + text.substring(curPos - 5 , len + 5) + "'")
|
||||
|
||||
len = len - curPos;
|
||||
|
||||
switch (commandName) {
|
||||
@ -195,11 +207,13 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
case 'includegraphics':
|
||||
case 'appendix':
|
||||
case 'printbibliography':
|
||||
case 'subsection*':
|
||||
case 'section*':
|
||||
return [len];
|
||||
case 'title':
|
||||
case 'author':
|
||||
case 'end':
|
||||
console.log("TODO: add way to check the", commandName)
|
||||
console.log("TODO: add way to check the " + commandName)
|
||||
return [len];
|
||||
|
||||
case 'cite':
|
||||
@ -207,15 +221,49 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
return [len];
|
||||
|
||||
case 'begin':
|
||||
case 'item':
|
||||
console.log("TODO handle", commandName, ":", args)
|
||||
|
||||
switch (args[0]) {
|
||||
|
||||
case 'verbatim':
|
||||
|
||||
const find = '\end{verbatim}';
|
||||
|
||||
let endPos = text.indexOf(find, curPos) + find.length;
|
||||
|
||||
len = endPos - curPos;
|
||||
break
|
||||
default:
|
||||
console.log("Do not know how to handle " + args[0])
|
||||
}
|
||||
|
||||
|
||||
return [len];
|
||||
|
||||
case 'item':
|
||||
|
||||
if (args[0]) {
|
||||
return [len, {
|
||||
length: 2 + args[0].length + 1,
|
||||
original_length: len,
|
||||
original_position: curPos + commandName.length,
|
||||
position: result.length,
|
||||
type: 'text'
|
||||
}, "— " + args[0] + '\n'];
|
||||
}
|
||||
|
||||
return [len, {
|
||||
length: 2,
|
||||
original_length: len,
|
||||
original_position: curPos,
|
||||
position: result.length,
|
||||
type: 'text'
|
||||
}, "— "];
|
||||
|
||||
case 'section':
|
||||
return [len, {
|
||||
length: args[0].length + 1,
|
||||
original_length: len,
|
||||
original_position: curPos - commandName.length,
|
||||
original_position: curPos + 2 + commandName.length,
|
||||
position: result.length,
|
||||
type: 'h1',
|
||||
}, args[0] + '\n']
|
||||
@ -224,13 +272,13 @@ function parseCommand(text: string, curPos: number, result: string): [number, Pa
|
||||
return [len, {
|
||||
length: args[0].length + 1,
|
||||
original_length: len,
|
||||
original_position: curPos - commandName.length,
|
||||
original_position: curPos + 2 + commandName.length,
|
||||
position: result.length,
|
||||
type: 'h2',
|
||||
}, args[0] + '\n']
|
||||
|
||||
default:
|
||||
console.log("Command name:", commandName, "options:", options, "args:", args);
|
||||
console.log("Command name: " + commandName + " options: " + options + " args: " + args);
|
||||
throw new Error("TODO handle this case");
|
||||
}
|
||||
|
||||
@ -294,7 +342,6 @@ export function parseLsp(text: string): ParseResult {
|
||||
const [len] = res;
|
||||
i += len;
|
||||
} else {
|
||||
|
||||
const [len, conv, toAdd] = res;
|
||||
|
||||
result.conversions.push(conv);
|
||||
@ -309,7 +356,7 @@ export function parseLsp(text: string): ParseResult {
|
||||
throw new Error("Handle double math expression");
|
||||
}
|
||||
|
||||
const possiblePartition = createPartition(text, conversionStartPosition, i - 1, result.text);
|
||||
const possiblePartition = createPartition(text, conversionStartPosition, i, result.text);
|
||||
|
||||
if (possiblePartition) {
|
||||
const [conv, toAdd] = possiblePartition;
|
||||
@ -319,6 +366,18 @@ export function parseLsp(text: string): ParseResult {
|
||||
|
||||
const len = readUntil(text, '$', i + 1);
|
||||
|
||||
let to_add = 'mathexpr' + (text[i + len + 1 + 1] === ' ' ? ' ' : '');
|
||||
|
||||
result.conversions = result.conversions.concat(
|
||||
[{
|
||||
length: to_add.length,
|
||||
position: result.text.length,
|
||||
original_position: i,
|
||||
type: 'text',
|
||||
}]
|
||||
);
|
||||
result.text += to_add;
|
||||
|
||||
i += len + 1;
|
||||
|
||||
conversionStartPosition = i + 1;
|
||||
@ -328,6 +387,8 @@ export function parseLsp(text: string): ParseResult {
|
||||
|
||||
}
|
||||
|
||||
result.text = result.text.replace(/``/g, "''");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -418,7 +479,7 @@ type Match = {
|
||||
}
|
||||
|
||||
|
||||
export async function diagnosticsRequests(res: ParseResult): Promise < Match[] > {
|
||||
export async function diagnosticsRequests(res: ParseResult): Promise<Match[]> {
|
||||
const formData = new URLSearchParams();
|
||||
|
||||
formData.set('text', res.text);
|
||||
@ -436,6 +497,7 @@ export async function diagnosticsRequests(res: ParseResult): Promise < Match[] >
|
||||
});
|
||||
|
||||
if (rawRes.status !== 200) {
|
||||
console.log("Error:" + (await (await rawRes.blob()).text()))
|
||||
process.exit(2);
|
||||
}
|
||||
const body = await rawRes.json();
|
||||
@ -443,10 +505,14 @@ export async function diagnosticsRequests(res: ParseResult): Promise < Match[] >
|
||||
return body.matches;
|
||||
}
|
||||
|
||||
export async function getDiagnostics(file: string): Promise<(Dialog & {replacements: string[]})[]> {
|
||||
export type GetDiagnosticsReturn = (Dialog & { replacements: string[], rule_id: string, word?: string });
|
||||
|
||||
export async function getDiagnostics(file: string): Promise<GetDiagnosticsReturn[]> {
|
||||
|
||||
const res = parseLsp(file);
|
||||
|
||||
fs.writeFileSync('/tmp/latex-lsp-res', res.text)
|
||||
|
||||
const matches = await diagnosticsRequests(res);
|
||||
|
||||
const lineIndex = buildLineIndex(file);
|
||||
@ -460,6 +526,13 @@ export async function getDiagnostics(file: string): Promise<(Dialog & {replaceme
|
||||
console.log("Could not find the original position")
|
||||
continue;
|
||||
}
|
||||
|
||||
let word: string | undefined = undefined;
|
||||
|
||||
if (match.rule.id.startsWith("MORFOLOGIK_RULE")) {
|
||||
word = file.substring(original_position, original_position + match.length);
|
||||
}
|
||||
|
||||
const [startLine, startChar] = getLineAndChar(lineIndex, original_position);
|
||||
const [endLine, endChar] = getLineAndChar(lineIndex, original_position + match.length);
|
||||
|
||||
@ -473,6 +546,8 @@ export async function getDiagnostics(file: string): Promise<(Dialog & {replaceme
|
||||
severity: Severity.Error,
|
||||
message: match.message,
|
||||
replacements: match.replacements.map(a => a.value),
|
||||
rule_id: match.rule.id,
|
||||
word,
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user