/** * 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')