From a5698ad192476d6760a6230125b400c0faf4be34 Mon Sep 17 00:00:00 2001 From: bdm-k Date: Thu, 25 Apr 2024 10:20:35 +0900 Subject: [PATCH 1/3] Improve tooltip position to avoid hiding the line math section --- src/editor_extensions/math_tooltip.ts | 122 ++++++++++++++++++-------- src/latex_suite.ts | 6 +- 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/editor_extensions/math_tooltip.ts b/src/editor_extensions/math_tooltip.ts index c143b39..4322aff 100644 --- a/src/editor_extensions/math_tooltip.ts +++ b/src/editor_extensions/math_tooltip.ts @@ -1,58 +1,110 @@ -import { Tooltip, showTooltip, EditorView } from "@codemirror/view"; -import { StateField, EditorState } from "@codemirror/state"; +import { Tooltip, showTooltip, EditorView, ViewUpdate } from "@codemirror/view"; +import { StateField, EditorState, EditorSelection, StateEffect } from "@codemirror/state"; import { renderMath, finishRenderMath, editorLivePreviewField } from "obsidian"; import { Context } from "src/utils/context"; import { getLatexSuiteConfig } from "src/snippets/codemirror/config"; +const updateTooltipEffect = StateEffect.define(); + export const cursorTooltipField = StateField.define({ - create: getCursorTooltips, + create: () => [], update(tooltips, tr) { - if (!tr.docChanged && !tr.selection) return tooltips; - return getCursorTooltips(tr.state); + for (const effect of tr.effects) { + if (effect.is(updateTooltipEffect)) return effect.value; + } + + return tooltips; }, provide: (f) => showTooltip.computeN([f], (state) => state.field(f)), }); -function getCursorTooltips(state: EditorState): readonly Tooltip[] { - const settings = getLatexSuiteConfig(state); - const ctx = Context.fromState(state); +// update the tooltip by dispatching an updateTooltipEffect +export function handleMathTooltip(update: ViewUpdate) { + const shouldUpdate = update.docChanged || update.selectionSet; + if (!shouldUpdate) return; + + const settings = getLatexSuiteConfig(update.state); + const ctx = Context.fromState(update.state); + + let newTooltips: Tooltip[] = []; + + if (shouldShowTooltip(update.state, ctx)) { + // HACK: eqnBounds is not null because shouldShowTooltip was true + const eqnBounds = ctx.getBounds(); + const eqn = update.state.sliceDoc(eqnBounds.start, eqnBounds.end); + + const above = settings.mathPreviewPositionIsAbove; + const create = () => { + const dom = document.createElement("div"); + dom.className = "cm-tooltip-cursor"; + + const renderedEqn = renderMath(eqn, ctx.mode.blockMath || ctx.mode.codeMath); + dom.appendChild(renderedEqn); + finishRenderMath(); + + return { dom }; + }; + + if (ctx.mode.blockMath || ctx.mode.codeMath) { + newTooltips = [{ + pos: above ? eqnBounds.start : eqnBounds.end, + above: above, + strictSide: true, + arrow: true, + create: create, + }]; + } + + if (ctx.mode.inlineMath && above) { + newTooltips = [{ + pos: eqnBounds.start, + above: true, + strictSide: true, + arrow: true, + create: create, + }]; + } - if (!ctx.mode.inMath()) { - return []; + if (ctx.mode.inlineMath && !above) { + const endRange = EditorSelection.range(eqnBounds.end, eqnBounds.end); + + newTooltips = [{ + pos: Math.max( + eqnBounds.start, + // the beginning position of the visual line where eqnBounds.end is + // located + update.view.moveToLineBoundary(endRange, false).anchor, + ), + above: false, + strictSide: true, + arrow: true, + create: create, + }]; + } } - const isLivePreview = state.field(editorLivePreviewField); - if (ctx.mode.blockMath && isLivePreview) return []; + update.view.dispatch({ + effects: [updateTooltipEffect.of(newTooltips)] + }); +} - const bounds = ctx.getBounds(); - if (!bounds) return []; +function shouldShowTooltip(state: EditorState, ctx: Context): boolean { + if (!ctx.mode.inMath()) return false; - const eqn = state.sliceDoc(bounds.start, bounds.end); + const isLivePreview = state.field(editorLivePreviewField); + if (ctx.mode.blockMath && isLivePreview) return false; + + // FIXME: eqnBounds can be null + const eqnBounds = ctx.getBounds(); + if (!eqnBounds) return false; // Don't render an empty equation - if (eqn.trim() === "") return []; - - return [ - { - pos: (ctx.mode.inlineMath || settings.mathPreviewPositionIsAbove) ? bounds.start : bounds.end, - above: settings.mathPreviewPositionIsAbove, - strictSide: true, - arrow: true, - create: () => { - const dom = document.createElement("div"); - dom.className = "cm-tooltip-cursor"; - - const renderedEqn = renderMath(eqn, ctx.mode.blockMath || ctx.mode.codeMath); - dom.appendChild(renderedEqn); - finishRenderMath(); - - return { dom }; - } - } - ]; + const eqn = state.sliceDoc(eqnBounds.start, eqnBounds.end).trim(); + if (eqn === "") return false; + return true; } export const cursorTooltipBaseTheme = EditorView.baseTheme({ diff --git a/src/latex_suite.ts b/src/latex_suite.ts index 310871f..c9a9dd5 100644 --- a/src/latex_suite.ts +++ b/src/latex_suite.ts @@ -19,9 +19,13 @@ import { concealPlugin } from "./editor_extensions/conceal"; import { LatexSuiteCMSettings } from "./settings/settings"; import { colorPairedBracketsPluginLowestPrec, highlightCursorBracketsPlugin } from "./editor_extensions/highlight_brackets"; -import { cursorTooltipBaseTheme, cursorTooltipField } from "./editor_extensions/math_tooltip"; +import { cursorTooltipBaseTheme, cursorTooltipField, handleMathTooltip } from "./editor_extensions/math_tooltip"; export const handleUpdate = (update: ViewUpdate) => { + // The math tooltip handler is driven by view updates because it utilizes + // information about visual line, which is not available in EditorState + handleMathTooltip(update); + const cursorTriggeredByChange = update.state.field(cursorTriggerStateField, false); // Remove all tabstops when the user manually moves the cursor (e.g. on mouse click; using arrow keys) From c3bbd98b65e6b16080119f9e681cf8849524336e Mon Sep 17 00:00:00 2001 From: bdm-k Date: Thu, 25 Apr 2024 22:40:45 +0900 Subject: [PATCH 2/3] A little optimization Reduce the number of times dispatching a transaction --- src/editor_extensions/math_tooltip.ts | 115 ++++++++++++++------------ 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/src/editor_extensions/math_tooltip.ts b/src/editor_extensions/math_tooltip.ts index 4322aff..f61d31a 100644 --- a/src/editor_extensions/math_tooltip.ts +++ b/src/editor_extensions/math_tooltip.ts @@ -28,61 +28,72 @@ export function handleMathTooltip(update: ViewUpdate) { const settings = getLatexSuiteConfig(update.state); const ctx = Context.fromState(update.state); - let newTooltips: Tooltip[] = []; - - if (shouldShowTooltip(update.state, ctx)) { - // HACK: eqnBounds is not null because shouldShowTooltip was true - const eqnBounds = ctx.getBounds(); - const eqn = update.state.sliceDoc(eqnBounds.start, eqnBounds.end); - - const above = settings.mathPreviewPositionIsAbove; - const create = () => { - const dom = document.createElement("div"); - dom.className = "cm-tooltip-cursor"; - - const renderedEqn = renderMath(eqn, ctx.mode.blockMath || ctx.mode.codeMath); - dom.appendChild(renderedEqn); - finishRenderMath(); - - return { dom }; - }; - - if (ctx.mode.blockMath || ctx.mode.codeMath) { - newTooltips = [{ - pos: above ? eqnBounds.start : eqnBounds.end, - above: above, - strictSide: true, - arrow: true, - create: create, - }]; + if (!shouldShowTooltip(update.state, ctx)) { + const currTooltips = update.state.field(cursorTooltipField); + // a little optimization: + // If the tooltip is not currently shown and there is no need to show it, + // we don't dispatch an transaction. + if (currTooltips.length > 0) { + update.view.dispatch({ + effects: [updateTooltipEffect.of([])], + }); } + return; + } - if (ctx.mode.inlineMath && above) { - newTooltips = [{ - pos: eqnBounds.start, - above: true, - strictSide: true, - arrow: true, - create: create, - }]; - } + /* + * process when there is a need to show the tooltip: from here + */ - if (ctx.mode.inlineMath && !above) { - const endRange = EditorSelection.range(eqnBounds.end, eqnBounds.end); - - newTooltips = [{ - pos: Math.max( - eqnBounds.start, - // the beginning position of the visual line where eqnBounds.end is - // located - update.view.moveToLineBoundary(endRange, false).anchor, - ), - above: false, - strictSide: true, - arrow: true, - create: create, - }]; - } + // HACK: eqnBounds is not null because shouldShowTooltip was true + const eqnBounds = ctx.getBounds(); + const eqn = update.state.sliceDoc(eqnBounds.start, eqnBounds.end); + + const above = settings.mathPreviewPositionIsAbove; + const create = () => { + const dom = document.createElement("div"); + dom.className = "cm-tooltip-cursor"; + + const renderedEqn = renderMath(eqn, ctx.mode.blockMath || ctx.mode.codeMath); + dom.appendChild(renderedEqn); + finishRenderMath(); + + return { dom }; + }; + + let newTooltips: Tooltip[] = []; + + if (ctx.mode.blockMath || ctx.mode.codeMath) { + newTooltips = [{ + pos: above ? eqnBounds.start : eqnBounds.end, + above: above, + strictSide: true, + arrow: true, + create: create, + }]; + } else if (ctx.mode.inlineMath && above) { + newTooltips = [{ + pos: eqnBounds.start, + above: true, + strictSide: true, + arrow: true, + create: create, + }]; + } else if (ctx.mode.inlineMath && !above) { + const endRange = EditorSelection.range(eqnBounds.end, eqnBounds.end); + + newTooltips = [{ + pos: Math.max( + eqnBounds.start, + // the beginning position of the visual line where eqnBounds.end is + // located + update.view.moveToLineBoundary(endRange, false).anchor, + ), + above: false, + strictSide: true, + arrow: true, + create: create, + }]; } update.view.dispatch({ From 7dc9772b3b51420cec2f79129a883cc0dc01b7b4 Mon Sep 17 00:00:00 2001 From: artisticat <100622574+artisticat1@users.noreply.github.com> Date: Sat, 27 Apr 2024 11:36:12 +0100 Subject: [PATCH 3/3] minor formatting fix --- src/editor_extensions/math_tooltip.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor_extensions/math_tooltip.ts b/src/editor_extensions/math_tooltip.ts index f61d31a..1c40343 100644 --- a/src/editor_extensions/math_tooltip.ts +++ b/src/editor_extensions/math_tooltip.ts @@ -22,7 +22,7 @@ export const cursorTooltipField = StateField.define({ // update the tooltip by dispatching an updateTooltipEffect export function handleMathTooltip(update: ViewUpdate) { - const shouldUpdate = update.docChanged || update.selectionSet; + const shouldUpdate = update.docChanged || update.selectionSet; if (!shouldUpdate) return; const settings = getLatexSuiteConfig(update.state); @@ -52,7 +52,7 @@ export function handleMathTooltip(update: ViewUpdate) { const above = settings.mathPreviewPositionIsAbove; const create = () => { const dom = document.createElement("div"); - dom.className = "cm-tooltip-cursor"; + dom.addClass("cm-tooltip-cursor"); const renderedEqn = renderMath(eqn, ctx.mode.blockMath || ctx.mode.codeMath); dom.appendChild(renderedEqn);