163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
/**
|
|
* Wraps newt.js (compiled from src/LSP.newt with some tweaks to `export`) with the
|
|
* vscode LSP server module.
|
|
*/
|
|
|
|
import { LSP_checkFile, LSP_updateFile, LSP_hoverInfo, LSP_codeActionInfo, LSP_docSymbols } from './newt.js'
|
|
|
|
import {
|
|
createConnection,
|
|
TextDocuments,
|
|
ProposedFeatures,
|
|
Hover,
|
|
InitializeParams,
|
|
InitializeResult,
|
|
TextDocumentSyncKind,
|
|
Location,
|
|
TextDocumentIdentifier,
|
|
} from "vscode-languageserver/node";
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
|
|
const connection = createConnection(ProposedFeatures.all);
|
|
const documents = new TextDocuments(TextDocument);
|
|
|
|
// the last is the most important to the user, but we run FIFO
|
|
// to ensure dependencies are seen in causal order
|
|
let changes: (TextDocument|TextDocumentIdentifier)[] = []
|
|
let running = false
|
|
let lastChange = 0
|
|
function addChange(doc: TextDocument | TextDocumentIdentifier) {
|
|
console.log('enqueue', doc.uri)
|
|
// drop stale pending changes
|
|
let before = changes.length
|
|
changes = changes.filter(ch => ch.uri != doc.uri)
|
|
console.log('DROPPED', before - changes.length);
|
|
changes.push(doc)
|
|
lastChange = +new Date()
|
|
if (!running) runChange()
|
|
}
|
|
|
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
async function runChange() {
|
|
try {
|
|
running = true;
|
|
while (changes.length) {
|
|
console.log('LOOP TOP')
|
|
// Wait until things stop changing
|
|
let prev = lastChange
|
|
while (1) {
|
|
await sleep(100)
|
|
if (prev == lastChange) break
|
|
prev = lastChange
|
|
console.log('DELAY')
|
|
}
|
|
let doc = changes.shift()
|
|
if (!doc) {
|
|
running = false
|
|
return
|
|
}
|
|
const uri = doc.uri
|
|
const start = +new Date()
|
|
const diagnostics = LSP_checkFile(doc.uri)
|
|
const end = +new Date()
|
|
console.log('CHECK', doc.uri, 'in', end - start);
|
|
await sleep(1);
|
|
if (!changes.find(ch => ch.uri === uri)) {
|
|
console.log('SEND', diagnostics.length, 'for', uri)
|
|
connection.sendDiagnostics({ uri, diagnostics })
|
|
} else {
|
|
console.log('STALE result not sent for', uri)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
running = false;
|
|
}
|
|
}
|
|
documents.onDidChangeContent(async (change) => {
|
|
console.log('DIDCHANGE', change.document.uri)
|
|
const uri = change.document.uri;
|
|
const text = change.document.getText();
|
|
// update/invalidate happens now, check happens on quiesce.
|
|
writeCache(path.basename(uri), text);
|
|
LSP_updateFile(uri, text);
|
|
addChange(change.document);
|
|
});
|
|
|
|
connection.onHover((params): Hover | null => {
|
|
// wait until quiesced (REVIEW after query-based)
|
|
if (running) return null
|
|
|
|
const uri = params.textDocument.uri;
|
|
const pos = params.position;
|
|
console.log('HOVER', uri, pos)
|
|
let res = LSP_hoverInfo(uri, pos.line, pos.character)
|
|
if (!res) return null
|
|
if (res == true) {
|
|
addChange(params.textDocument)
|
|
return null
|
|
} else {
|
|
console.log('HOVER is ', res)
|
|
return { contents: { kind: "plaintext", value: res.info } };
|
|
}
|
|
});
|
|
|
|
connection.onDefinition((params): Location | null => {
|
|
const uri = params.textDocument.uri;
|
|
const pos = params.position;
|
|
let value = LSP_hoverInfo(uri, pos.line, pos.character)
|
|
if (!value || value == true) return null;
|
|
return value.location
|
|
})
|
|
|
|
connection.onCodeAction(({textDocument, range}) => {
|
|
let actions = LSP_codeActionInfo(textDocument.uri, range.start.line, range.start.character);
|
|
console.log('ACTIONS is ', JSON.stringify(actions,null,' '))
|
|
return actions
|
|
})
|
|
|
|
connection.onDocumentSymbol((params) => {
|
|
try {
|
|
|
|
const uri = params.textDocument.uri;
|
|
let symbols = LSP_docSymbols(uri);
|
|
console.log("docs got", symbols)
|
|
return symbols;
|
|
} catch (e) {
|
|
console.error('ERROR in onDocumentSymbol', e);
|
|
}
|
|
})
|
|
|
|
connection.onInitialize((_params: InitializeParams): InitializeResult => ({
|
|
capabilities: {
|
|
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
hoverProvider: true,
|
|
definitionProvider: true,
|
|
codeActionProvider: true,
|
|
documentSymbolProvider: true,
|
|
},
|
|
}));
|
|
|
|
function writeCache(fn: string, content: string) {
|
|
const home = process.env.HOME;
|
|
if (!home) return;
|
|
const dname = path.join(home, '.cache/newt-lsp');
|
|
const fname = path.join(dname, fn);
|
|
try {
|
|
fs.mkdirSync(dname, {recursive: true});
|
|
} catch (e) {
|
|
}
|
|
try {
|
|
fs.writeFileSync(fname, content, 'utf8');
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
documents.listen(connection);
|
|
connection.listen();
|
|
console.log('STARTED')
|