Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"private": true,
"engines": {
"vscode": "^1.75.0",
"positron": "^2025.12.0"
"positron": "^2026.05.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juliasilge warned me about aggressively bumping the positron version due to posit-dev/positron#11321, but I guess that's fixed now? If so, I can also remove some runtime version checks in the StatementRange code

},
"main": "./out/main.js",
"browser": "./browser.js",
Expand Down
9 changes: 7 additions & 2 deletions apps/vscode/src/@types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>
): Thenable<boolean>;

executeInlineCell(
documentUri: vscode.Uri,
cellRanges: vscode.Range[]
cellRanges: vscode.Range[],
executionMetadata?: Record<string, any>[]
): Thenable<void>;
}

Expand Down
4 changes: 2 additions & 2 deletions apps/vscode/src/host/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import { JupyterKernelspec } from "core";
import { Position } from "vscode";

export interface CellExecutor {
execute: (blocks: string[], editorUri?: Uri) => Promise<void>;
execute: (blocks: string[], editorUri?: Uri, executionMetadata?: Record<string, unknown>[]) => Promise<void>;
executeSelection?: () => Promise<void>;
executeAtPosition?: (uri: Uri, pos: Position) => Promise<Position>;
executeInlineCells?: (documentUri: Uri, cellRanges: Range[]) => Promise<void>;
executeInlineCells?: (documentUri: Uri, cellRanges: Range[], executionMetadata?: Record<string, unknown>[]) => Promise<void>;
}

export function executableLanguages() {
Expand Down
15 changes: 10 additions & 5 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function hooksExtensionHost(): ExtensionHost {
case "csharp":
case "r":
return {
execute: async (blocks: string[], editorUri?: vscode.Uri): Promise<void> => {
execute: async (blocks: string[], editorUri?: vscode.Uri, executionMetadata?: Record<string, unknown>[]): Promise<void> => {
const runtime = hooksApi()?.runtime;

if (runtime === undefined) {
Expand All @@ -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
);
}
};

Expand All @@ -111,15 +116,15 @@ export function hooksExtensionHost(): ExtensionHost {
}
return position;
},
executeInlineCells: async (documentUri: vscode.Uri, cellRanges: Range[]): Promise<void> => {
executeInlineCells: async (documentUri: vscode.Uri, cellRanges: Range[], executionMetadata?: Record<string, unknown>[]): Promise<void> => {
const runtime = hooksApi()?.runtime;

if (runtime === undefined) {
// Can't do anything without a runtime
return;
}

await runtime.executeInlineCell(documentUri, cellRanges);
await runtime.executeInlineCell(documentUri, cellRanges, executionMetadata);
}
};

Expand Down
27 changes: 20 additions & 7 deletions apps/vscode/src/providers/cell/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { QuartoVisualEditor, VisualEditorProvider } from "../editor/editor";
import {
blockHasExecutor,
blockIsExecutable,
cellMetadataForBlock,
codeWithoutOptionsFromBlock,
executeInteractive,
executeSelectionInteractive,
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -500,21 +503,24 @@ 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<string, unknown> | undefined)[] = [];
for (const blk of blocks.filter(
isExecutableLanguageBlockOf(language)
) as Array<TokenMath | TokenCodeBlock>) {
code.push(codeWithoutOptionsFromBlock(blk));
metadata.push(cellMetadataForBlock(blk));
if (blk.range) {
ranges.push(blk.range);
}
}

// 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);
}
}
}
Expand Down Expand Up @@ -565,6 +571,7 @@ class RunCellsBelowCommand extends RunCommand implements Command {

const blocks: string[] = [];
const ranges: Range[] = [];
const metadata: (Record<string, unknown> | undefined)[] = [];
for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array<TokenMath | TokenCodeBlock>) {
// skip if the cell is above or at the cursor
if (blk.range && line < blk.range.start.line) {
Expand All @@ -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));
}
}
}
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -632,13 +641,15 @@ class RunAllCellsCommand extends RunCommand implements Command {
let language: string | undefined;
const blocks: string[] = [];
const ranges: Range[] = [];
const metadata: (Record<string, unknown> | undefined)[] = [];
for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array<TokenMath | TokenCodeBlock>) {
const blockLanguage = languageNameFromBlock(blk);
if (!language) {
language = blockLanguage;
}
if (blockLanguage === language) {
blocks.push(codeWithoutOptionsFromBlock(blk));
metadata.push(cellMetadataForBlock(blk));
if (blk.range) {
ranges.push(blk.range);
}
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
13 changes: 10 additions & 3 deletions apps/vscode/src/providers/cell/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> | 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)) {
Expand Down Expand Up @@ -89,7 +95,8 @@ export async function executeInteractive(
executor: CellExecutor,
blocks: string[],
document: TextDocument,
ranges?: Range[]
ranges?: Range[],
metadata?: Record<string, unknown>[]
): Promise<void> {
// If inline output is enabled, the document has a URI, and the executor supports
// inline execution, use that instead of the standard console execution
Expand All @@ -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);
}


Expand Down
Loading