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 {
update(update: ViewUpdate) { pending: any | null = null;
if (update.docChanged) { update(update: ViewUpdate) {
if (this.pending != null) clearTimeout(this.pending) if (update.docChanged) {
this.pending = setTimeout(() => { if (this.pending != null) clearTimeout(this.pending);
this.pending = null this.pending = setTimeout(() => {
const plugin = LSPPlugin.get(update.view) this.pending = null;
if (plugin) plugin.client.sync() const plugin = LSPPlugin.get(update.view);
}, 500) if (plugin) plugin.client.sync();
} }, 500);
} }
destroy() { }
if (this.pending != null) clearTimeout(this.pending) destroy() {
} 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: {
notificationHandlers: { textDocument: { publishDiagnostics: { versionSupport: true } },
"textDocument/publishDiagnostics": (client, params: lsp.PublishDiagnosticsParams) => { },
const file = client.workspace.getFile(params.uri) as OpenFile; notificationHandlers: {
if (!file || params.version != null && params.version != file.version) return false; "textDocument/publishDiagnostics": (
const mapPos = (p: number) => file.changes ? file.changes.mapPos(p) : p; client,
file.setDiagnostics(params.diagnostics.map(item => ({ params: lsp.PublishDiagnosticsParams,
from: mapPos(fromPosition(file.doc, item.range.start)), ) => {
to: mapPos(fromPosition(file.doc, item.range.end)), const file = client.workspace.getFile(params.uri) as OpenFile;
severity: toSeverity(item.severity ?? 1), if (!file) {
message: item.message, return false;
}))); }
return true if (params.version != null && params.version != file.version) {
} return false;
}, }
editorExtension: autoSync 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),
message: item.message,
})),
);
return true;
},
},
editorExtension: autoSync,
};
} }

View File

@ -20,7 +20,7 @@ let currentWorkspaceRoot: string | null = null;
let watcher: chokidar.FSWatcher | null = null; let watcher: chokidar.FSWatcher | null = null;
export function getCurrentWorkspaceRoot(): string | null { export function getCurrentWorkspaceRoot(): string | null {
return currentWorkspaceRoot; return currentWorkspaceRoot;
} }
// Helper to (re)create watcher and wire up IPC notifications to renderer // Helper to (re)create watcher and wire up IPC notifications to renderer

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) => {