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>();
constructor() {
// Attempt to install handlers shortly after construction. If `dom` is not
// available yet, retry a few times.
// Attempt to install handlers shortly after construction.
// If `dom` is not available yet, retry a few times.
setTimeout(() => this.installHandlers(0), 0);
// Add general shortcuts

View File

@ -167,10 +167,7 @@ export class OpenFile implements WorkspaceFile {
this.rootState.val = transaction.state;
if (transaction.changes && !transaction.changes.empty) {
if (this.changes === undefined) {
this.changes = ChangeSet.empty(this.rootState.val.doc.length);
}
this.changes = this.changes.compose(transaction.changes);
this.changeSet = this.changes.compose(transaction.changes);
}
if (origin) {
@ -178,7 +175,12 @@ export class OpenFile implements WorkspaceFile {
es.forEach((e) => e.dispatch(e.view.state.update(trs), true));
} else {
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 {
return inferLanguageFromPath(this.filePath.val || "") || "";
}
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.
// If `main` is provided and belongs to this open file, return it. Otherwise
// 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 {ViewPlugin, ViewUpdate} from "@codemirror/view"
import {LSPPlugin, LSPClientExtension} from "@codemirror/lsp-client"
import {OpenFile} from "../filestate"
import { Text } from "@codemirror/state"
import type * as lsp from "vscode-languageserver-protocol";
import { ChangeSet } from "@codemirror/state";
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"
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
const plugin = LSPPlugin.get(update.view)
if (plugin) plugin.client.sync()
}, 500)
}
}
destroy() {
if (this.pending != null) clearTimeout(this.pending)
}
})
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;
const 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 {
const line = doc.line(pos.line + 1)
return line.from + pos.character
const 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) => {
const file = client.workspace.getFile(params.uri) as OpenFile;
if (!file || params.version != null && params.version != file.version) return false;
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
}
return {
clientCapabilities: {
textDocument: { publishDiagnostics: { versionSupport: true } },
},
notificationHandlers: {
"textDocument/publishDiagnostics": (
client,
params: lsp.PublishDiagnosticsParams,
) => {
const file = client.workspace.getFile(params.uri) as OpenFile;
if (!file) {
return false;
}
if (params.version != null && params.version != file.version) {
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),
message: item.message,
})),
);
return true;
},
},
editorExtension: autoSync,
};
}

View File

@ -20,7 +20,7 @@ let currentWorkspaceRoot: string | null = null;
let watcher: chokidar.FSWatcher | null = null;
export function getCurrentWorkspaceRoot(): string | null {
return currentWorkspaceRoot;
return currentWorkspaceRoot;
}
// 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 totalLen = headerEnd + 4 + len;
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
try {
entry.ports.forEach((p) => {