Fix diagnostic propagation

This commit is contained in:
Quinten Kock 2025-12-02 23:22:39 +01:00
parent 0e70335791
commit db5f33e961
3 changed files with 84 additions and 10 deletions

View File

@ -8,6 +8,7 @@ import {
ChangeSet,
} from "@codemirror/state";
import { history } from "@codemirror/commands";
import { Diagnostic, setDiagnostics } from "@codemirror/lint";
import { Editor } from "./editor";
import van, { State } from "vanjs-core";
import { WorkspaceFile } from "@codemirror/lsp-client";
@ -104,6 +105,11 @@ export class OpenFile implements WorkspaceFile {
createEditor(): Editor {
const editor = new Editor(this);
this.editors.push(editor);
editor.dispatch(
editor.view.state.update(
setDiagnostics(editor.view.state, this.diagnostics || []),
),
);
return editor;
}
@ -172,12 +178,7 @@ export class OpenFile implements WorkspaceFile {
es.forEach((e) => e.dispatch(e.view.state.update(trs), true));
} else {
this.editors.forEach((e) => {
const changes = transaction.changes;
const userEvent = transaction.annotation(Transaction.userEvent);
const annotations = userEvent
? [Transaction.userEvent.of(userEvent)]
: [];
e.dispatch(e.view.state.update({ changes, annotations }), true);
e.dispatch(e.view.state.update(trs), true);
});
}
}
@ -215,4 +216,14 @@ export class OpenFile implements WorkspaceFile {
if (this.editors.length > 0) return this.editors[0].view;
return null;
}
private diagnostics: Diagnostic[];
setDiagnostics(diagnostics: Diagnostic[]) {
this.diagnostics = diagnostics;
for (const editor of this.editors) {
editor.view.dispatch(
setDiagnostics(editor.view.state, diagnostics),
);
}
}
}

View File

@ -7,11 +7,14 @@ import { EditorView } from "@codemirror/view";
import {
LSPClient,
LSPPlugin,
languageServerExtensions,
Workspace,
serverCompletion,
hoverTooltips,
signatureHelp,
} from "@codemirror/lsp-client";
import { OpenFile } from "./filestate";
import { serverDiagnostics } from "./lsp/diagnostics";
// Create a very small MessagePort-based transport implementation
// compatible with @codemirror/lsp-client's expected Transport interface.
@ -108,9 +111,9 @@ class OpenFileWorkspace extends Workspace {
file.version = this.nextFileVersion(file.uri);
file.changes = ChangeSet.empty(file.doc.length);
}
for(const e of file.editors) {
for (const e of file.editors) {
const plugin = LSPPlugin.get(e.view);
if(plugin) {
if (plugin) {
plugin.clear();
}
}
@ -257,7 +260,12 @@ export async function createLspExtension(
try {
const client = new LSPClient({
extensions: languageServerExtensions(),
extensions: [
serverDiagnostics(),
serverCompletion(),
hoverTooltips(),
signatureHelp(),
],
rootUri: rootUri,
workspace: (c) => new OpenFileWorkspace(c),
});

View File

@ -0,0 +1,55 @@
import type * as lsp from "vscode-languageserver-protocol"
import {setDiagnostics} from "@codemirror/lint"
import {ViewPlugin, ViewUpdate} from "@codemirror/view"
import {LSPPlugin, LSPClientExtension} from "@codemirror/lsp-client"
import {OpenFile} from "../filestate"
import { Text } from "@codemirror/state"
function toSeverity(sev: lsp.DiagnosticSeverity) {
return sev == 1 ? "error" : sev == 2 ? "warning" : sev == 3 ? "info" : "hint"
}
const autoSync = ViewPlugin.fromClass(class {
pending: any | null = null
update(update: ViewUpdate) {
if (update.docChanged) {
if (this.pending != null) clearTimeout(this.pending)
this.pending = setTimeout(() => {
this.pending = null
let plugin = LSPPlugin.get(update.view)
if (plugin) plugin.client.sync()
}, 500)
}
}
destroy() {
if (this.pending != null) clearTimeout(this.pending)
}
})
function fromPosition(doc: Text, pos: lsp.Position): number {
let line = doc.line(pos.line + 1)
return line.from + pos.character
}
export function serverDiagnostics(): LSPClientExtension {
return {
clientCapabilities: {textDocument: {publishDiagnostics: {versionSupport: true}}},
notificationHandlers: {
"textDocument/publishDiagnostics": (client, params: lsp.PublishDiagnosticsParams) => {
let file = client.workspace.getFile(params.uri) as OpenFile;
if (!file || params.version != null && params.version != file.version) return false;
for(const view of file.editors.map(e => e.view)) {
const mapPos = (p: number) => file.changes ? file.changes.mapPos(p) : p;
file.setDiagnostics(params.diagnostics.map(item => ({
from: mapPos(fromPosition(file.doc, item.range.start)),
to: mapPos(fromPosition(file.doc, item.range.end)),
severity: toSeverity(item.severity ?? 1),
message: item.message,
})));
}
return true
}
},
editorExtension: autoSync
}
}