From acd1cd1b774bf193c4855f5b71286cd2981b9c8d Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Wed, 11 Feb 2026 10:43:17 -0500 Subject: [PATCH 1/4] Make `provideStatementRange()` create a single chunk virtual doc --- apps/vscode/src/host/hooks.ts | 4 ++-- apps/vscode/src/vdoc/vdoc.ts | 38 ++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 647521cb..8063f4ac 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -22,7 +22,7 @@ import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider, HostHelpTo import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors'; import { ExecuteQueue } from './execute-queue'; import { MarkdownEngine } from '../markdown/engine'; -import { virtualDoc, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc"; +import { virtualDoc, adjustedPosition, unadjustedRange, withVirtualDocUri, VirtualDocStyle } from "../vdoc/vdoc"; import { Position, Range } from 'vscode'; import { Uri } from 'vscode'; @@ -199,7 +199,7 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider { document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const vdoc = await virtualDoc(document, position, this._engine); + const vdoc = await virtualDoc(document, position, this._engine, VirtualDocStyle.Block); if (vdoc) { return await withVirtualDocUri(vdoc, document.uri, "statementRange", async (uri: vscode.Uri) => { const result = await vscode.commands.executeCommand( diff --git a/apps/vscode/src/vdoc/vdoc.ts b/apps/vscode/src/vdoc/vdoc.ts index b1830bc1..06c68dc4 100644 --- a/apps/vscode/src/vdoc/vdoc.ts +++ b/apps/vscode/src/vdoc/vdoc.ts @@ -28,11 +28,19 @@ export interface VirtualDoc { content: string; } +export enum VirtualDocStyle { + /// Every block corresponding to the current position's language + Language, + + /// Only the block corresponding to the current position + Block +} + export async function virtualDoc( document: TextDocument, position: Position, engine: MarkdownEngine, - block?: Token + style = VirtualDocStyle.Language ): Promise { // make sure this is a quarto doc if (!isQuartoDoc(document)) { @@ -41,20 +49,32 @@ export async function virtualDoc( // check if the cursor is in a fenced code block const tokens = engine.parse(document); - const language = languageAtPosition(tokens, position); - if (language) { - if (block) { - return virtualDocForBlock(document, block, language); - } else { + const block = languageBlockAtPosition(tokens, position); + if (!block) { + return undefined; + } + + const language = languageFromBlock(block); + if (!language) { + return undefined; + } + + switch (style) { + case VirtualDocStyle.Language: { return virtualDocForLanguage(document, tokens, language); } - } else { - return undefined; + case VirtualDocStyle.Block: { + return virtualDocForBlock(document, block, language); + } + default: { + // Should be unreachable + return undefined; + } } } -export function virtualDocForBlock(document: TextDocument, block: Token, language: EmbeddedLanguage) { +function virtualDocForBlock(document: TextDocument, block: Token, language: EmbeddedLanguage) { const lines = linesForLanguage(document, language); fillLinesFromBlock(lines, document, block); padLinesForLanguage(lines, language); From fab39b44dafd62c84254ee34840c1f13fd3cc26b Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Wed, 11 Feb 2026 10:49:28 -0500 Subject: [PATCH 2/4] CHANGELOG --- apps/vscode/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index f5bacbd9..1c1cefac 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.130.0 (Unreleased) +- Fixed a bug where a parse error in one chunk meant you could not perform statement execution in another chunk (). + ## 1.129.0 (Release on 2026-01-29) - Fixed Copilot completions in `.qmd` documents (). From cf2881870956b7eb92440bc125f7a79a033f3ca5 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Thu, 12 Feb 2026 15:30:38 -0500 Subject: [PATCH 3/4] Add basic virtual doc snapshot tests --- apps/vscode/src/test/codeBlocks.test.ts | 6 +- .../generated_snapshots/vdoc/blocks-python.py | 31 +++++++++ .../generated_snapshots/vdoc/blocks-r.R | 29 +++++++++ .../generated_snapshots/vdoc/one-block.R | 29 +++++++++ apps/vscode/src/test/examples/vdoc/blocks.qmd | 26 ++++++++ apps/vscode/src/test/examples/vdoc/oob.qmd | 12 ++++ apps/vscode/src/test/quartoDoc.test.ts | 12 ++-- apps/vscode/src/test/test-utils.ts | 12 +++- apps/vscode/src/test/vdoc.test.ts | 63 +++++++++++++++++++ 9 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-python.py create mode 100644 apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-r.R create mode 100644 apps/vscode/src/test/examples/generated_snapshots/vdoc/one-block.R create mode 100644 apps/vscode/src/test/examples/vdoc/blocks.qmd create mode 100644 apps/vscode/src/test/examples/vdoc/oob.qmd create mode 100644 apps/vscode/src/test/vdoc.test.ts diff --git a/apps/vscode/src/test/codeBlocks.test.ts b/apps/vscode/src/test/codeBlocks.test.ts index 98b1a378..40d866c0 100644 --- a/apps/vscode/src/test/codeBlocks.test.ts +++ b/apps/vscode/src/test/codeBlocks.test.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import * as assert from "assert"; -import { WORKSPACE_PATH, examplesOutUri, openAndShowTextDocument } from "./test-utils"; +import { WORKSPACE_PATH, examplesOutUri, openAndShowExamplesOutTextDocument } from "./test-utils"; import { isExecutableLanguageBlock, languageNameFromBlock } from "quarto-core"; import { MarkdownEngine } from "../markdown/engine"; @@ -17,7 +17,7 @@ suite("Code block detection", function () { // doesn't have a preceding blank line. This real-world example has many // .notes divs which caused later code blocks to not be detected. test("Detects code blocks in document with many divs (issue #521)", async function () { - const { doc } = await openAndShowTextDocument("div-code-blocks.qmd"); + const { doc } = await openAndShowExamplesOutTextDocument("div-code-blocks.qmd"); const tokens = engine.parse(doc); const executableBlocks = tokens.filter(isExecutableLanguageBlock); @@ -49,7 +49,7 @@ suite("Code block detection", function () { }); test("Detects code block in simple document", async function () { - const { doc } = await openAndShowTextDocument("hello.qmd"); + const { doc } = await openAndShowExamplesOutTextDocument("hello.qmd"); const tokens = engine.parse(doc); const executableBlocks = tokens.filter(isExecutableLanguageBlock); diff --git a/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-python.py b/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-python.py new file mode 100644 index 00000000..cb715d27 --- /dev/null +++ b/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-python.py @@ -0,0 +1,31 @@ +# type: ignore +# flake8: noqa +# +# +# +# +# +# +# +# +1 + 1 +# +# +# +2 + 2 +# +# +# +# +# +# +# +4 + 4 +# +# +# +# +# +# +# +# diff --git a/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-r.R b/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-r.R new file mode 100644 index 00000000..b69371ca --- /dev/null +++ b/apps/vscode/src/test/examples/generated_snapshots/vdoc/blocks-r.R @@ -0,0 +1,29 @@ +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +3 + 3 +# +# +# +# +# +# +# +5 + 5 +# +# +# +# diff --git a/apps/vscode/src/test/examples/generated_snapshots/vdoc/one-block.R b/apps/vscode/src/test/examples/generated_snapshots/vdoc/one-block.R new file mode 100644 index 00000000..28a414d0 --- /dev/null +++ b/apps/vscode/src/test/examples/generated_snapshots/vdoc/one-block.R @@ -0,0 +1,29 @@ +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +3 + 3 +# +# +# +# +# +# +# +# +# +# +# +# diff --git a/apps/vscode/src/test/examples/vdoc/blocks.qmd b/apps/vscode/src/test/examples/vdoc/blocks.qmd new file mode 100644 index 00000000..6c69eaa2 --- /dev/null +++ b/apps/vscode/src/test/examples/vdoc/blocks.qmd @@ -0,0 +1,26 @@ +--- +title: "Test all blocks for language at position" +format: html +--- + +## Header + +```{python} +1 + 1 +``` + +```{python} +2 + 2 +``` + +```{r} +3 + 3 +``` + +```{python} +4 + 4 +``` + +```{r} +5 + 5 +``` diff --git a/apps/vscode/src/test/examples/vdoc/oob.qmd b/apps/vscode/src/test/examples/vdoc/oob.qmd new file mode 100644 index 00000000..aaa1ff1f --- /dev/null +++ b/apps/vscode/src/test/examples/vdoc/oob.qmd @@ -0,0 +1,12 @@ +--- +title: "OOB" +format: html +--- + +## Header + +```{python} +1 + 1 +``` + +Yo diff --git a/apps/vscode/src/test/quartoDoc.test.ts b/apps/vscode/src/test/quartoDoc.test.ts index 498d835d..2fe1e1b4 100644 --- a/apps/vscode/src/test/quartoDoc.test.ts +++ b/apps/vscode/src/test/quartoDoc.test.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import * as assert from "assert"; -import { WORKSPACE_PATH, readOrCreateSnapshot, examplesOutUri, wait, roundtrip, openAndShowTextDocument } from "./test-utils"; +import { WORKSPACE_PATH, readOrCreateSnapshot, examplesOutUri, wait, roundtrip, openAndShowExamplesOutTextDocument } from "./test-utils"; import { isQuartoDoc } from "../core/doc"; @@ -13,7 +13,7 @@ suite("Quarto basics", function () { }); test("Can open a Quarto document", async function () { - const { editor } = await openAndShowTextDocument("hello.qmd"); + const { editor } = await openAndShowExamplesOutTextDocument("hello.qmd"); assert.strictEqual(editor?.document.languageId, "quarto"); assert.strictEqual(isQuartoDoc(editor?.document), true); @@ -23,7 +23,7 @@ suite("Quarto basics", function () { // test. That's okay for this test, but could cause issues if you expect a qmd to look how it // does in `/examples`. test("Roundtrip doesn't change hello.qmd", async function () { - const { doc } = await openAndShowTextDocument("hello.qmd"); + const { doc } = await openAndShowExamplesOutTextDocument("hello.qmd"); const { before, after } = await roundtrip(doc); @@ -44,7 +44,7 @@ suite("Quarto basics", function () { // a test to prevent situations like https://github.com/quarto-dev/quarto/issues/845 test("Can open a non-qmd file normally", async function () { - const { editor, doc } = await openAndShowTextDocument("hello.lua"); + const { editor, doc } = await openAndShowExamplesOutTextDocument("hello.lua"); editor.edit((editBuilder) => { editBuilder.insert(new vscode.Position(0, 0), 'print("hiyo")\n'); @@ -57,7 +57,7 @@ suite("Quarto basics", function () { }); test("Roundtrip doesn't change nested-checked-list.qmd", async function () { - const { doc } = await openAndShowTextDocument("nested-checked-list.qmd"); + const { doc } = await openAndShowExamplesOutTextDocument("nested-checked-list.qmd"); const { before, after } = await roundtrip(doc); @@ -78,7 +78,7 @@ function roundtripSnapshotTest(filename: string) { const snapshotFileName = `roundtripped-${filename}`; test(`Roundtripped ${filename} matches snapshot`, async function () { - const { doc } = await openAndShowTextDocument(filename); + const { doc } = await openAndShowExamplesOutTextDocument(filename); const { after } = await roundtrip(doc); diff --git a/apps/vscode/src/test/test-utils.ts b/apps/vscode/src/test/test-utils.ts index c6ce53c5..62ef1b45 100644 --- a/apps/vscode/src/test/test-utils.ts +++ b/apps/vscode/src/test/test-utils.ts @@ -26,8 +26,16 @@ export function wait(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export async function openAndShowTextDocument(fileName: string) { - const doc = await vscode.workspace.openTextDocument(examplesOutUri(fileName)); +export async function openAndShowExamplesTextDocument(fileName: string) { + return openAndShowUri(examplesUri(fileName)); +} + +export async function openAndShowExamplesOutTextDocument(fileName: string) { + return openAndShowUri(examplesOutUri(fileName)); +} + +async function openAndShowUri(uri: vscode.Uri) { + const doc = await vscode.workspace.openTextDocument(uri); const editor = await vscode.window.showTextDocument(doc); return { doc, editor }; } diff --git a/apps/vscode/src/test/vdoc.test.ts b/apps/vscode/src/test/vdoc.test.ts new file mode 100644 index 00000000..3656fa17 --- /dev/null +++ b/apps/vscode/src/test/vdoc.test.ts @@ -0,0 +1,63 @@ +import * as vscode from "vscode"; +import * as assert from "assert"; +import { readOrCreateSnapshot, openAndShowExamplesTextDocument } from "./test-utils"; +import { MarkdownEngine } from "../markdown/engine"; +import { virtualDoc, VirtualDocStyle } from "../vdoc/vdoc"; +import path from "path"; + +suite("Virtual documents", function () { + const engine = new MarkdownEngine(); + + snapshotVirtualDocument( + "vdoc/blocks-python.py", + "vdoc/blocks.qmd", + new vscode.Position(8, 0), + engine, + VirtualDocStyle.Language + ); + snapshotVirtualDocument( + "vdoc/blocks-r.R", + "vdoc/blocks.qmd", + new vscode.Position(16, 0), + engine, + VirtualDocStyle.Language + ); + + snapshotVirtualDocument( + "vdoc/one-block.R", + "vdoc/blocks.qmd", + new vscode.Position(16, 0), + engine, + VirtualDocStyle.Block + ); + + test("OOB position returns `undefined` virtual doc", async function () { + const { doc } = await openAndShowExamplesTextDocument("vdoc/oob.qmd"); + + const position = new vscode.Position(0, 0); + const style = VirtualDocStyle.Language; + + const vdoc = await virtualDoc(doc, position, engine, style); + assert.strictEqual(vdoc, undefined); + }); +}); + +function snapshotVirtualDocument( + snapshotFilename: string, + filename: string, + position: vscode.Position, + engine: MarkdownEngine, + style: VirtualDocStyle +) { + test(`Virtual document ${filename} matches snapshot ${snapshotFilename}`, async function () { + const { doc } = await openAndShowExamplesTextDocument(filename); + + const vdoc = await virtualDoc(doc, position, engine, style); + assert.ok(vdoc); + + assert.equal( + vdoc.content, + await readOrCreateSnapshot(snapshotFilename, vdoc.content) + ); + }); +} From c01a0cbccf018667e4b6833562acad2bf06babd8 Mon Sep 17 00:00:00 2001 From: Davis Vaughan Date: Thu, 12 Feb 2026 15:32:31 -0500 Subject: [PATCH 4/4] Copyright year --- apps/vscode/src/host/hooks.ts | 2 +- apps/vscode/src/vdoc/vdoc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 8063f4ac..afcd8b34 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -3,7 +3,7 @@ * * Positron-specific functionality. * - * Copyright (C) 2022 by Posit Software, PBC + * Copyright (C) 2022-2026 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant * to the terms of a commercial license agreement with Posit Software, then diff --git a/apps/vscode/src/vdoc/vdoc.ts b/apps/vscode/src/vdoc/vdoc.ts index 06c68dc4..29a8c178 100644 --- a/apps/vscode/src/vdoc/vdoc.ts +++ b/apps/vscode/src/vdoc/vdoc.ts @@ -1,7 +1,7 @@ /* * vdoc.ts * - * Copyright (C) 2022-2025 by Posit Software, PBC + * Copyright (C) 2022-2026 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant * to the terms of a commercial license agreement with Posit Software, then