fix(live-preview): fixed cursor kicking when editing referenced block

This commit is contained in:
JK 2026-04-20 00:40:03 +02:00
parent bcc862731a
commit d1d809f029
2 changed files with 82 additions and 14 deletions

View file

@ -149,12 +149,21 @@ const tagManagerAnnotation = Annotation.define<boolean>();
function createTagManagerPlugin(plugin: LatexReferencer, equationField: StateField<EquationState>): ViewPlugin<object> {
return ViewPlugin.fromClass(class {
timeout: NodeJS.Timeout | null = null;
blockedBySelection = false;
constructor(view: EditorView) { this.scheduleCheck(view); }
constructor(view: EditorView) {
this.scheduleCheck(view);
}
update(update: ViewUpdate) {
if (update.docChanged && !update.transactions.some(tr => tr.annotation(tagManagerAnnotation))) {
this.scheduleCheck(update.view);
}
// If a previous run detected pending edits but skipped due to active selection
// inside an equation block, retry once the selection changes.
if (update.selectionSet && this.blockedBySelection) {
this.scheduleCheck(update.view);
}
}
scheduleCheck(view: EditorView) {
if (this.timeout) clearTimeout(this.timeout);
@ -166,8 +175,11 @@ function createTagManagerPlugin(plugin: LatexReferencer, equationField: StateFie
runCheck(view: EditorView) {
const equationInfos = view.state.field(equationField);
const changes: { from: number; to: number; insert: string }[] = [];
const selection = view.state.selection.main;
let blockedChanges = 0;
for (const info of equationInfos) {
const selectionOverlapsEquation = selection.from <= info.to && selection.to >= info.from;
// Determine the prefix from the OPENING line of the block.
// This is the source of truth for indentation/callout level.
const startLine = view.state.doc.lineAt(info.from);
@ -272,11 +284,17 @@ function createTagManagerPlugin(plugin: LatexReferencer, equationField: StateFie
// We simply compare the strings.
// Note: This replaces EVERYTHING from inside the block to the end of the block.
if (existingSuffix !== proposedSuffix) {
if (selectionOverlapsEquation) {
blockedChanges += 1;
continue;
}
changes.push({ from: info.from + 2, to: info.to, insert: proposedSuffix });
}
}
}
this.blockedBySelection = blockedChanges > 0;
if (changes.length > 0) {
view.dispatch({
changes,

View file

@ -1,16 +1,66 @@
/**
* A simple synchronous file logger used for intense UI diagnostic tracing.
*
* Usage:
* Inject `logDebug("My message")` aggressively into CodeMirror rendering loops
* or DOM observers to output an absolute timeline of execution to `debug.log`.
* This is especially useful for capturing logs in contexts where the Obsidian
* Developer Console might be silenced, asynchronous, or overwhelmed.
*/
import * as fs from "fs";
import * as path from "path";
export function logDebug(msg: string) {
try {
fs.appendFileSync("d:\\Codes\\plugin-latex-referencer\\debug.log", msg + "\n", { encoding: "utf-8" });
} catch (e) { }
type DebugCapablePlugin = {
settings?: { debug?: boolean };
app?: { vault?: { adapter?: { basePath?: string } } };
};
let sequence = 0;
function getDefaultLogPath(plugin?: DebugCapablePlugin): string {
const basePath = plugin?.app?.vault?.adapter?.basePath;
if (typeof basePath === "string" && basePath.length > 0) {
return path.join(basePath, "latex-referencer-debug.log");
}
return path.join(process.cwd(), "latex-referencer-debug.log");
}
function canDebug(plugin?: DebugCapablePlugin): boolean {
return !!plugin?.settings?.debug;
}
function writeDebugLine(line: string, plugin?: DebugCapablePlugin) {
try {
fs.appendFileSync(getDefaultLogPath(plugin), line + "\n", { encoding: "utf-8" });
} catch (_) {
// Best-effort only. Console logging should still work.
}
}
/**
* Structured debug logging helper used in edit/render loops.
* This is opt-in and gated by plugin.settings.debug.
*/
export function logDebugEvent(
plugin: DebugCapablePlugin | undefined,
scope: string,
event: string,
data?: Record<string, unknown> | (() => Record<string, unknown>)
) {
if (!canDebug(plugin)) return;
sequence += 1;
const payload = typeof data === "function" ? data() : (data ?? {});
const line = JSON.stringify({
seq: sequence,
ts: new Date().toISOString(),
scope,
event,
...payload,
});
writeDebugLine(line, plugin);
}
export function clearDebugLog(plugin?: DebugCapablePlugin) {
const logPath = getDefaultLogPath(plugin);
try {
fs.writeFileSync(logPath, "", { encoding: "utf-8" });
} catch (_) {
// Ignore write failures.
}
}
export function getDebugLogPath(plugin?: DebugCapablePlugin): string {
return getDefaultLogPath(plugin);
}