From 7e0da19d045d88de9822ab39af6510317dd159f1 Mon Sep 17 00:00:00 2001 From: Moritz Ketzer <48527611+moritzketzer@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:24:14 +0100 Subject: [PATCH] Prevent hover hangs outside single-line display math --- packages/quarto-core/src/markdown/cursor.ts | 2 +- packages/quarto-core/test/cursor.test.ts | 61 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/quarto-core/test/cursor.test.ts diff --git a/packages/quarto-core/src/markdown/cursor.ts b/packages/quarto-core/src/markdown/cursor.ts index bc60a887a..ffd69ad58 100644 --- a/packages/quarto-core/src/markdown/cursor.ts +++ b/packages/quarto-core/src/markdown/cursor.ts @@ -87,7 +87,7 @@ function isLatexCodeBlock(token: Token) { } const kInlineMathPattern = /\$([^ ].*?[^ ]?)\$/g; -const kSingleLineDisplayMathPattern = /\$\$([^\n]+?)\$\$/; +const kSingleLineDisplayMathPattern = /\$\$([^\n]+?)\$\$/g; function inlineMathRange(pos: Position, line: string, pattern: RegExp) { pattern.lastIndex = 0; diff --git a/packages/quarto-core/test/cursor.test.ts b/packages/quarto-core/test/cursor.test.ts new file mode 100644 index 000000000..195ad7973 --- /dev/null +++ b/packages/quarto-core/test/cursor.test.ts @@ -0,0 +1,61 @@ +/* + * cursor.test.ts + * + * Copyright (C) 2026 by Posit Software, PBC + */ + +import assert from "node:assert/strict"; +import test from "node:test"; +import { runInNewContext } from "node:vm"; + +import { Position, Range } from "vscode-languageserver-types"; + +import { Document } from "../src/document"; +import { mathRange } from "../src/markdown/cursor"; +import { markdownitParser } from "../src/markdown/parsers/markdownit"; + +function document(text: string): Document { + return { + uri: "file:///math.qmd", + languageId: "quarto", + version: 1, + lineCount: 1, + getText: () => text, + positionAt: (offset) => Position.create(0, offset), + }; +} + +for (const [text, character] of [ + ["Before $$x$$ after", 0], + ["Before $$x$$ after", 16], + ["Before $$x$$ and $$y$$ after", 14], + ["Before $$x$$ and $$y$$ after", 26], +] as const) { + test(`returns no math at character ${character} in ${JSON.stringify(text)}`, () => { + assert.equal( + runInNewContext( + "mathRange(parser, doc, position)", + { mathRange, parser: markdownitParser(), doc: document(text), position: Position.create(0, character) }, + { timeout: 1000 } + ), + null + ); + }); +} + +test("finds later display math and resets the search for repeated requests", () => { + const parser = markdownitParser(); + const doc = document("Before $$x$$ and $$y$$ after"); + for (const character of [22, 12, 22, 12]) { + assert.deepEqual( + runInNewContext( + "mathRange(parser, doc, position)", + { mathRange, parser, doc, position: Position.create(0, character) }, + { timeout: 1000 } + ), + character === 22 + ? { math: "y", range: Range.create(0, 17, 0, 22) } + : { math: "x", range: Range.create(0, 7, 0, 12) } + ); + } +});