diff --git a/src/features/equations/live-preview-equations.ts b/src/features/equations/live-preview-equations.ts index 15f9686..ef994f1 100644 --- a/src/features/equations/live-preview-equations.ts +++ b/src/features/equations/live-preview-equations.ts @@ -149,12 +149,21 @@ const tagManagerAnnotation = Annotation.define(); function createTagManagerPlugin(plugin: LatexReferencer, equationField: StateField): ViewPlugin { 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, diff --git a/src/utils/debug.ts b/src/utils/debug.ts index 4fba9aa..332ab99 100644 --- a/src/utils/debug.ts +++ b/src/utils/debug.ts @@ -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 | (() => Record) +) { + 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); }