Fix undo cursor jumps and formatting

This commit is contained in:
Quinten Kock 2025-12-03 01:10:39 +01:00
parent a570acf962
commit 4a3e78e56c
5 changed files with 88 additions and 52 deletions

View File

@ -17,8 +17,8 @@ export abstract class Displayable {
private shortcuts = new Map<string, KeyHandler>(); private shortcuts = new Map<string, KeyHandler>();
constructor() { constructor() {
// Attempt to install handlers shortly after construction. If `dom` is not // Attempt to install handlers shortly after construction.
// available yet, retry a few times. // If `dom` is not available yet, retry a few times.
setTimeout(() => this.installHandlers(0), 0); setTimeout(() => this.installHandlers(0), 0);
// Add general shortcuts // Add general shortcuts

View File

@ -167,10 +167,7 @@ export class OpenFile implements WorkspaceFile {
this.rootState.val = transaction.state; this.rootState.val = transaction.state;
if (transaction.changes && !transaction.changes.empty) { if (transaction.changes && !transaction.changes.empty) {
if (this.changes === undefined) { this.changeSet = this.changes.compose(transaction.changes);
this.changes = ChangeSet.empty(this.rootState.val.doc.length);
}
this.changes = this.changes.compose(transaction.changes);
} }
if (origin) { if (origin) {
@ -178,7 +175,12 @@ export class OpenFile implements WorkspaceFile {
es.forEach((e) => e.dispatch(e.view.state.update(trs), true)); es.forEach((e) => e.dispatch(e.view.state.update(trs), true));
} else { } else {
this.editors.forEach((e) => { this.editors.forEach((e) => {
e.dispatch(e.view.state.update(trs), true); 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);
}); });
} }
} }
@ -203,8 +205,16 @@ export class OpenFile implements WorkspaceFile {
get languageId(): string { get languageId(): string {
return inferLanguageFromPath(this.filePath.val || "") || ""; return inferLanguageFromPath(this.filePath.val || "") || "";
} }
doc: Text; doc: Text;
changes: ChangeSet; private changeSet: ChangeSet;
get changes(): ChangeSet {
if (!this.changeSet) {
this.changeSet = ChangeSet.empty(this.rootState.val.doc.length);
}
return this.changeSet;
}
// Return an EditorView to be used by the LSP Workspace for position mapping. // Return an EditorView to be used by the LSP Workspace for position mapping.
// If `main` is provided and belongs to this open file, return it. Otherwise // If `main` is provided and belongs to this open file, return it. Otherwise
// return the first available editor view, or null if none exist. // return the first available editor view, or null if none exist.

View File

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

View File

@ -86,7 +86,9 @@ function ensureLspForKey(
const len = parseInt(m[1], 10); const len = parseInt(m[1], 10);
const totalLen = headerEnd + 4 + len; const totalLen = headerEnd + 4 + len;
if (entry.buffer.length < totalLen) break; // wait for more if (entry.buffer.length < totalLen) break; // wait for more
const body = entry.buffer.subarray(headerEnd + 4, totalLen).toString(); const body = entry.buffer
.subarray(headerEnd + 4, totalLen)
.toString();
// Forward body to all attached ports // Forward body to all attached ports
try { try {
entry.ports.forEach((p) => { entry.ports.forEach((p) => {