diff --git a/apps/vscode/package.json b/apps/vscode/package.json index 5bdab316..fe56f3d5 100644 --- a/apps/vscode/package.json +++ b/apps/vscode/package.json @@ -29,7 +29,7 @@ "private": true, "engines": { "vscode": "^1.75.0", - "positron": "^2025.12.0" + "positron": "^2026.05.0" }, "main": "./out/main.js", "browser": "./browser.js", diff --git a/apps/vscode/src/@types/hooks.d.ts b/apps/vscode/src/@types/hooks.d.ts index c8b0c2b7..6ed510fd 100644 --- a/apps/vscode/src/@types/hooks.d.ts +++ b/apps/vscode/src/@types/hooks.d.ts @@ -18,12 +18,17 @@ declare module 'positron' { languageId: string, code: string, focus: boolean, - allowIncomplete: boolean + allowIncomplete: boolean, + mode?: string, + errorBehavior?: string, + codeLocation?: object, + executionMetadata?: Record ): Thenable; executeInlineCell( documentUri: vscode.Uri, - cellRanges: vscode.Range[] + cellRanges: vscode.Range[], + executionMetadata?: Record[] ): Thenable; } diff --git a/apps/vscode/src/host/executors.ts b/apps/vscode/src/host/executors.ts index f30e18c5..719dac4e 100644 --- a/apps/vscode/src/host/executors.ts +++ b/apps/vscode/src/host/executors.ts @@ -25,10 +25,10 @@ import { JupyterKernelspec } from "core"; import { Position } from "vscode"; export interface CellExecutor { - execute: (blocks: string[], editorUri?: Uri) => Promise; + execute: (blocks: string[], editorUri?: Uri, executionMetadata?: Record[]) => Promise; executeSelection?: () => Promise; executeAtPosition?: (uri: Uri, pos: Position) => Promise; - executeInlineCells?: (documentUri: Uri, cellRanges: Range[]) => Promise; + executeInlineCells?: (documentUri: Uri, cellRanges: Range[], executionMetadata?: Record[]) => Promise; } export function executableLanguages() { diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index bd16a8bd..8d019007 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -72,7 +72,7 @@ export function hooksExtensionHost(): ExtensionHost { case "csharp": case "r": return { - execute: async (blocks: string[], editorUri?: vscode.Uri): Promise => { + execute: async (blocks: string[], editorUri?: vscode.Uri, executionMetadata?: Record[]): Promise => { const runtime = hooksApi()?.runtime; if (runtime === undefined) { @@ -87,8 +87,13 @@ export function hooksExtensionHost(): ExtensionHost { // Our callback executes each block sequentially const callback = async () => { - for (const block of blocks) { - await runtime.executeCode(language, block, false, true); + for (let i = 0; i < blocks.length; i++) { + const metadata = executionMetadata?.[i]; + await runtime.executeCode( + language, blocks[i], false, true, + undefined, undefined, undefined, + metadata + ); } }; @@ -111,7 +116,7 @@ export function hooksExtensionHost(): ExtensionHost { } return position; }, - executeInlineCells: async (documentUri: vscode.Uri, cellRanges: Range[]): Promise => { + executeInlineCells: async (documentUri: vscode.Uri, cellRanges: Range[], executionMetadata?: Record[]): Promise => { const runtime = hooksApi()?.runtime; if (runtime === undefined) { @@ -119,7 +124,7 @@ export function hooksExtensionHost(): ExtensionHost { return; } - await runtime.executeInlineCell(documentUri, cellRanges); + await runtime.executeInlineCell(documentUri, cellRanges, executionMetadata); } }; diff --git a/apps/vscode/src/providers/cell/commands.ts b/apps/vscode/src/providers/cell/commands.ts index 8f07dff9..44abb7ed 100644 --- a/apps/vscode/src/providers/cell/commands.ts +++ b/apps/vscode/src/providers/cell/commands.ts @@ -43,6 +43,7 @@ import { QuartoVisualEditor, VisualEditorProvider } from "../editor/editor"; import { blockHasExecutor, blockIsExecutable, + cellMetadataForBlock, codeWithoutOptionsFromBlock, executeInteractive, executeSelectionInteractive, @@ -177,7 +178,8 @@ class RunCurrentCellCommand extends RunCommand implements Command { if (executor) { const code = codeWithoutOptionsFromBlock(block); const ranges = block.range ? [block.range] : undefined; - await executeInteractive(executor, [code], editor.document, ranges); + const metadata = cellMetadataForBlock(block); + await executeInteractive(executor, [code], editor.document, ranges, metadata ? [metadata] : undefined); } } } @@ -306,7 +308,8 @@ class RunCurrentCommand extends RunCommand implements Command { if (resolveToRunCell) { const code = codeWithoutOptionsFromBlock(block); - await executeInteractive(executor, [code], editor.document); + const metadata = cellMetadataForBlock(block); + await executeInteractive(executor, [code], editor.document, undefined, metadata ? [metadata] : undefined); } else { // submit const executed = await executeSelectionInteractive(executor); @@ -500,13 +503,15 @@ class RunCellsAboveCommand extends RunCommand implements Command { const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_); if (executor) { - // accumulate code and ranges + // accumulate code, ranges, and metadata const code: string[] = []; const ranges: Range[] = []; + const metadata: (Record | undefined)[] = []; for (const blk of blocks.filter( isExecutableLanguageBlockOf(language) ) as Array) { code.push(codeWithoutOptionsFromBlock(blk)); + metadata.push(cellMetadataForBlock(blk)); if (blk.range) { ranges.push(blk.range); } @@ -514,7 +519,8 @@ class RunCellsAboveCommand extends RunCommand implements Command { // execute (only pass ranges if we collected the same number as code blocks) const validRanges = ranges.length === code.length ? ranges : undefined; - await executeInteractive(executor, code, editor.document, validRanges); + const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined; + await executeInteractive(executor, code, editor.document, validRanges, validMetadata); } } } @@ -565,6 +571,7 @@ class RunCellsBelowCommand extends RunCommand implements Command { const blocks: string[] = []; const ranges: Range[] = []; + const metadata: (Record | undefined)[] = []; for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array) { // skip if the cell is above or at the cursor if (blk.range && line < blk.range.start.line) { @@ -577,6 +584,7 @@ class RunCellsBelowCommand extends RunCommand implements Command { if (blockLanguage === language) { blocks.push(codeWithoutOptionsFromBlock(blk)); ranges.push(blk.range); + metadata.push(cellMetadataForBlock(blk)); } } } @@ -585,7 +593,8 @@ class RunCellsBelowCommand extends RunCommand implements Command { const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_); if (executor) { const validRanges = ranges.length === blocks.length ? ranges : undefined; - await executeInteractive(executor, blocks, editor.document, validRanges); + const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined; + await executeInteractive(executor, blocks, editor.document, validRanges, validMetadata); } } } @@ -632,6 +641,7 @@ class RunAllCellsCommand extends RunCommand implements Command { let language: string | undefined; const blocks: string[] = []; const ranges: Range[] = []; + const metadata: (Record | undefined)[] = []; for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array) { const blockLanguage = languageNameFromBlock(blk); if (!language) { @@ -639,6 +649,7 @@ class RunAllCellsCommand extends RunCommand implements Command { } if (blockLanguage === language) { blocks.push(codeWithoutOptionsFromBlock(blk)); + metadata.push(cellMetadataForBlock(blk)); if (blk.range) { ranges.push(blk.range); } @@ -649,7 +660,8 @@ class RunAllCellsCommand extends RunCommand implements Command { if (executor) { // only pass ranges if we collected the same number as code blocks const validRanges = ranges.length === blocks.length ? ranges : undefined; - await executeInteractive(executor, blocks, editor.document, validRanges); + const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined; + await executeInteractive(executor, blocks, editor.document, validRanges, validMetadata); } } } @@ -743,7 +755,8 @@ async function runAdjacentBlock(host: ExtensionHost, editor: TextEditor, engine: const executor = await host.cellExecutorForLanguage(language, editor.document, engine); if (executor) { const ranges = block.range ? [block.range] : undefined; - await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document, ranges); + const metadata = cellMetadataForBlock(block); + await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document, ranges, metadata ? [metadata] : undefined); } } diff --git a/apps/vscode/src/providers/cell/executors.ts b/apps/vscode/src/providers/cell/executors.ts index 918a86fd..3a063e1f 100644 --- a/apps/vscode/src/providers/cell/executors.ts +++ b/apps/vscode/src/providers/cell/executors.ts @@ -62,6 +62,12 @@ export function blockIsExecutable(host: ExtensionHost, token?: Token): token is } } +// extract cell options as execution metadata (returns undefined if no options) +export function cellMetadataForBlock(token: TokenMath | TokenCodeBlock): Record | undefined { + const options = cellOptionsForToken(token); + return Object.keys(options).length > 0 ? options : undefined; +} + // skip yaml options for execution export function codeWithoutOptionsFromBlock(token: TokenMath | TokenCodeBlock) { if (isExecutableLanguageBlock(token)) { @@ -89,7 +95,8 @@ export async function executeInteractive( executor: CellExecutor, blocks: string[], document: TextDocument, - ranges?: Range[] + ranges?: Range[], + metadata?: Record[] ): Promise { // If inline output is enabled, the document has a URI, and the executor supports // inline execution, use that instead of the standard console execution @@ -98,9 +105,9 @@ export async function executeInteractive( ranges && ranges.length > 0 && executor.executeInlineCells) { - return await executor.executeInlineCells(document.uri, ranges); + return await executor.executeInlineCells(document.uri, ranges, metadata); } - return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined); + return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined, metadata); }