Initial LSP implementation/vscode support
Some checks failed
Publish Playground / build (push) Has been cancelled
Publish Playground / deploy (push) Has been cancelled

This commit is contained in:
2026-02-12 20:14:14 -08:00
parent 01a05ba186
commit a9718621e3
36 changed files with 6909 additions and 76 deletions

View File

@@ -0,0 +1,36 @@
// This needs to be fleshed out a lot, I've been adding them as needed
// There is a mix of agda, lean, and my own shortcuts here
export const ABBREV: Record<string, string> = {
"\\x": "×",
"\\r": "→",
"\\all": "∀",
"\\\\": "\\",
"\\==": "≡",
"\\circ": "∘",
"\\oplus": "⊕",
"\\otimes": "⊗",
// lean
"\\1": "₁",
"\\2": "₂",
"\\<": "⟨",
"\\>": "⟩",
// agda
"\\_0": "₀",
"\\_1": "₁",
"\\_2": "₂",
"\\_3": "₃",
// lean has \n here, which is a royal pain
"\\neg": "¬",
"\\bN": "",
"\\bZ": "",
"\\GG": "Γ",
"\\Gi": "ι",
"\\Gl": "λ",
"\\Gs": "σ",
"\\Gt": "τ",
"\\GD": "Δ",
"\\GS": "Σ",
"\\GP": "∏",
"\\[[": "⟦",
"\\]]": "⟧",
};

View File

@@ -0,0 +1,95 @@
import * as vscode from "vscode";
import { ABBREV } from "./abbrev";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient/node';
// They put this at module level for deactivate below.
let client: LanguageClient
export function activate(context: vscode.ExtensionContext) {
const serverModule = context.asAbsolutePath('./dist/lsp.js')
console.log('*** servervModule', serverModule)
const config = vscode.workspace.getConfiguration("newt");
const cmd = config.get<string | null>("lspPath");
let serverOptions: ServerOptions
if (cmd) {
serverOptions = {
run: { command: "node", args: [cmd], transport: TransportKind.pipe },
debug: { command: "node", args: [cmd], transport: TransportKind.pipe },
}
} else {
serverOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc },
}
}
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'newt' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('*.newt')
}
}
client = new LanguageClient(
'NewtLanguageServer',
'Newt Language Server',
serverOptions,
clientOptions
)
client.start();
console.log('CLIENT started')
vscode.workspace.onDidChangeTextDocument((event) => {
const editor = vscode.window.activeTextEditor;
if (!editor || event.document !== editor.document) return;
const changes = event.contentChanges;
if (changes.length === 0) return;
// TODO - agda input mode does the replacement as soon as possible
// but if the sequence is a prefix, it will change for subsequent characters
// The latter would require keeping state, but if we don't allow sequences to prefix
// each other, we could activate quicker.
const lastChange = changes[changes.length - 1];
const text = lastChange.text;
// Check if the last change is a potential shortcut trigger
if (!text || !(" ')\\_".includes(text) || text.startsWith('\n'))) return;
const document = editor.document;
const position = lastChange.range.end;
const lineText = document.lineAt(position.line).text;
const start = Math.max(0, position.character - 10);
const snippet = lineText.slice(start, position.character);
const m = snippet.match(/(\\[^ ]+)$/);
if (m) {
const cand = m[0];
console.log("cand", cand);
const replacement = ABBREV[cand];
console.log("repl", replacement);
if (replacement) {
const range = new vscode.Range(
position.line,
position.character - cand.length,
position.line,
position.character
);
editor.edit((editBuilder) => {
editBuilder.replace(range, replacement);
});
}
}
});
return;
}
export function deactivate() {
if (client) client.stop();
}

View File

@@ -0,0 +1,53 @@
/**
* WIP
*
* 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 } from './newt.js'
import {
createConnection,
TextDocuments,
ProposedFeatures,
Hover,
InitializeParams,
InitializeResult,
TextDocumentSyncKind,
} from "vscode-languageserver/node";
import { TextDocument } from "vscode-languageserver-textdocument";
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
documents.onDidChangeContent(async (change) => {
console.log('DIDCHANGE', change.document.uri)
const uri = change.document.uri;
const text = change.document.getText();
LSP_updateFile(uri, text);
const diagnostics = LSP_checkFile(uri);
console.log(`Got ${JSON.stringify(diagnostics, null, ' ')}`)
connection.sendDiagnostics({ uri, diagnostics })
});
connection.onHover((params): Hover | null => {
const uri = params.textDocument.uri;
const pos = params.position;
console.log('HOVER', uri, pos)
let value = LSP_hoverInfo(uri, pos.line, pos.character)
if (!value) return null
console.log('HOVER is ', value)
return { contents: { kind: "plaintext", value } };
});
connection.onInitialize((_params: InitializeParams): InitializeResult => ({
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
hoverProvider: true,
},
}));
documents.listen(connection);
connection.listen();
console.log('STARTED')

5
newt-vscode-lsp/src/newt.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import { Diagnostic } from "vscode-languageserver";
export function LSP_updateFile(name: string, content: string): (eta: any) => any;
export function LSP_checkFile(name: string): Diagnostic[];
export function LSP_hoverInfo(name: string, row: number, col: number): string|null;