diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index 332987714..b5f2e7233 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.140.0 (Unreleased) +- Fixed a bug where the language server could hang when hovering over text on a line that contains single-line display math such as `$$x$$` (). - In the visual editor, the Insert Emoji and Insert Unicode popups no longer close on their own about five seconds after the editor is first focused, which could happen while a search term was still being typed. They now close when the editor is scrolled or focus moves elsewhere within the editor, as do the completion popups for citations, cross references and the `/` insert menu (). - In the visual editor, the equation preview popup now follows its equation again after the window or editor pane is resized. Previously it stayed in place until the next edit or cursor move (). - The outline no longer shows duplicate "Quarto" groups for a document after settings change quickly, for example while VS Code starts (). 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) } + ); + } +});