Skip to content

Commit a186184

Browse files
committed
include cell metadata in execution requests
1 parent e8a7426 commit a186184

6 files changed

Lines changed: 50 additions & 20 deletions

File tree

apps/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"private": true,
3030
"engines": {
3131
"vscode": "^1.75.0",
32-
"positron": "^2025.12.0"
32+
"positron": "^2026.04.0"
3333
},
3434
"main": "./out/main.js",
3535
"browser": "./browser.js",

apps/vscode/src/@types/hooks.d.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ declare module 'positron' {
1818
languageId: string,
1919
code: string,
2020
focus: boolean,
21-
allowIncomplete: boolean
21+
allowIncomplete: boolean,
22+
mode?: string,
23+
errorBehavior?: string,
24+
codeLocation?: object,
25+
executionMetadata?: Record<string, any>
2226
): Thenable<boolean>;
2327

2428
executeInlineCell(
2529
documentUri: vscode.Uri,
26-
cellRanges: vscode.Range[]
30+
cellRanges: vscode.Range[],
31+
executionMetadata?: Record<string, any>[]
2732
): Thenable<void>;
2833
}
2934

apps/vscode/src/host/executors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ import { JupyterKernelspec } from "core";
2525
import { Position } from "vscode";
2626

2727
export interface CellExecutor {
28-
execute: (blocks: string[], editorUri?: Uri) => Promise<void>;
28+
execute: (blocks: string[], editorUri?: Uri, executionMetadata?: Record<string, unknown>[]) => Promise<void>;
2929
executeSelection?: () => Promise<void>;
3030
executeAtPosition?: (uri: Uri, pos: Position) => Promise<Position>;
31-
executeInlineCells?: (documentUri: Uri, cellRanges: Range[]) => Promise<void>;
31+
executeInlineCells?: (documentUri: Uri, cellRanges: Range[], executionMetadata?: Record<string, unknown>[]) => Promise<void>;
3232
}
3333

3434
export function executableLanguages() {

apps/vscode/src/host/hooks.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function hooksExtensionHost(): ExtensionHost {
7272
case "csharp":
7373
case "r":
7474
return {
75-
execute: async (blocks: string[], editorUri?: vscode.Uri): Promise<void> => {
75+
execute: async (blocks: string[], editorUri?: vscode.Uri, executionMetadata?: Record<string, unknown>[]): Promise<void> => {
7676
const runtime = hooksApi()?.runtime;
7777

7878
if (runtime === undefined) {
@@ -87,8 +87,13 @@ export function hooksExtensionHost(): ExtensionHost {
8787

8888
// Our callback executes each block sequentially
8989
const callback = async () => {
90-
for (const block of blocks) {
91-
await runtime.executeCode(language, block, false, true);
90+
for (let i = 0; i < blocks.length; i++) {
91+
const metadata = executionMetadata?.[i];
92+
await runtime.executeCode(
93+
language, blocks[i], false, true,
94+
undefined, undefined, undefined,
95+
metadata
96+
);
9297
}
9398
};
9499

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

117122
if (runtime === undefined) {
118123
// Can't do anything without a runtime
119124
return;
120125
}
121126

122-
await runtime.executeInlineCell(documentUri, cellRanges);
127+
await runtime.executeInlineCell(documentUri, cellRanges, executionMetadata);
123128
}
124129
};
125130

apps/vscode/src/providers/cell/commands.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { QuartoVisualEditor, VisualEditorProvider } from "../editor/editor";
4343
import {
4444
blockHasExecutor,
4545
blockIsExecutable,
46+
cellMetadataForBlock,
4647
codeWithoutOptionsFromBlock,
4748
executeInteractive,
4849
executeSelectionInteractive,
@@ -177,7 +178,8 @@ class RunCurrentCellCommand extends RunCommand implements Command {
177178
if (executor) {
178179
const code = codeWithoutOptionsFromBlock(block);
179180
const ranges = block.range ? [block.range] : undefined;
180-
await executeInteractive(executor, [code], editor.document, ranges);
181+
const metadata = cellMetadataForBlock(block);
182+
await executeInteractive(executor, [code], editor.document, ranges, metadata ? [metadata] : undefined);
181183
}
182184
}
183185
}
@@ -306,7 +308,8 @@ class RunCurrentCommand extends RunCommand implements Command {
306308

307309
if (resolveToRunCell) {
308310
const code = codeWithoutOptionsFromBlock(block);
309-
await executeInteractive(executor, [code], editor.document);
311+
const metadata = cellMetadataForBlock(block);
312+
await executeInteractive(executor, [code], editor.document, undefined, metadata ? [metadata] : undefined);
310313
} else {
311314
// submit
312315
const executed = await executeSelectionInteractive(executor);
@@ -500,21 +503,24 @@ class RunCellsAboveCommand extends RunCommand implements Command {
500503

501504
const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
502505
if (executor) {
503-
// accumulate code and ranges
506+
// accumulate code, ranges, and metadata
504507
const code: string[] = [];
505508
const ranges: Range[] = [];
509+
const metadata: (Record<string, unknown> | undefined)[] = [];
506510
for (const blk of blocks.filter(
507511
isExecutableLanguageBlockOf(language)
508512
) as Array<TokenMath | TokenCodeBlock>) {
509513
code.push(codeWithoutOptionsFromBlock(blk));
514+
metadata.push(cellMetadataForBlock(blk));
510515
if (blk.range) {
511516
ranges.push(blk.range);
512517
}
513518
}
514519

515520
// execute (only pass ranges if we collected the same number as code blocks)
516521
const validRanges = ranges.length === code.length ? ranges : undefined;
517-
await executeInteractive(executor, code, editor.document, validRanges);
522+
const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined;
523+
await executeInteractive(executor, code, editor.document, validRanges, validMetadata);
518524
}
519525
}
520526
}
@@ -565,6 +571,7 @@ class RunCellsBelowCommand extends RunCommand implements Command {
565571

566572
const blocks: string[] = [];
567573
const ranges: Range[] = [];
574+
const metadata: (Record<string, unknown> | undefined)[] = [];
568575
for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array<TokenMath | TokenCodeBlock>) {
569576
// skip if the cell is above or at the cursor
570577
if (blk.range && line < blk.range.start.line) {
@@ -577,6 +584,7 @@ class RunCellsBelowCommand extends RunCommand implements Command {
577584
if (blockLanguage === language) {
578585
blocks.push(codeWithoutOptionsFromBlock(blk));
579586
ranges.push(blk.range);
587+
metadata.push(cellMetadataForBlock(blk));
580588
}
581589
}
582590
}
@@ -585,7 +593,8 @@ class RunCellsBelowCommand extends RunCommand implements Command {
585593
const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
586594
if (executor) {
587595
const validRanges = ranges.length === blocks.length ? ranges : undefined;
588-
await executeInteractive(executor, blocks, editor.document, validRanges);
596+
const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined;
597+
await executeInteractive(executor, blocks, editor.document, validRanges, validMetadata);
589598
}
590599
}
591600
}
@@ -632,13 +641,15 @@ class RunAllCellsCommand extends RunCommand implements Command {
632641
let language: string | undefined;
633642
const blocks: string[] = [];
634643
const ranges: Range[] = [];
644+
const metadata: (Record<string, unknown> | undefined)[] = [];
635645
for (const blk of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array<TokenMath | TokenCodeBlock>) {
636646
const blockLanguage = languageNameFromBlock(blk);
637647
if (!language) {
638648
language = blockLanguage;
639649
}
640650
if (blockLanguage === language) {
641651
blocks.push(codeWithoutOptionsFromBlock(blk));
652+
metadata.push(cellMetadataForBlock(blk));
642653
if (blk.range) {
643654
ranges.push(blk.range);
644655
}
@@ -649,7 +660,8 @@ class RunAllCellsCommand extends RunCommand implements Command {
649660
if (executor) {
650661
// only pass ranges if we collected the same number as code blocks
651662
const validRanges = ranges.length === blocks.length ? ranges : undefined;
652-
await executeInteractive(executor, blocks, editor.document, validRanges);
663+
const validMetadata = metadata.some(m => m !== undefined) ? metadata.map(m => m ?? {}) : undefined;
664+
await executeInteractive(executor, blocks, editor.document, validRanges, validMetadata);
653665
}
654666
}
655667
}
@@ -743,7 +755,8 @@ async function runAdjacentBlock(host: ExtensionHost, editor: TextEditor, engine:
743755
const executor = await host.cellExecutorForLanguage(language, editor.document, engine);
744756
if (executor) {
745757
const ranges = block.range ? [block.range] : undefined;
746-
await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document, ranges);
758+
const metadata = cellMetadataForBlock(block);
759+
await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document, ranges, metadata ? [metadata] : undefined);
747760
}
748761
}
749762

apps/vscode/src/providers/cell/executors.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export function blockIsExecutable(host: ExtensionHost, token?: Token): token is
6262
}
6363
}
6464

65+
// extract cell options as execution metadata (returns undefined if no options)
66+
export function cellMetadataForBlock(token: TokenMath | TokenCodeBlock): Record<string, unknown> | undefined {
67+
const options = cellOptionsForToken(token);
68+
return Object.keys(options).length > 0 ? options : undefined;
69+
}
70+
6571
// skip yaml options for execution
6672
export function codeWithoutOptionsFromBlock(token: TokenMath | TokenCodeBlock) {
6773
if (isExecutableLanguageBlock(token)) {
@@ -89,7 +95,8 @@ export async function executeInteractive(
8995
executor: CellExecutor,
9096
blocks: string[],
9197
document: TextDocument,
92-
ranges?: Range[]
98+
ranges?: Range[],
99+
metadata?: Record<string, unknown>[]
93100
): Promise<void> {
94101
// If inline output is enabled, the document has a URI, and the executor supports
95102
// inline execution, use that instead of the standard console execution
@@ -98,9 +105,9 @@ export async function executeInteractive(
98105
ranges &&
99106
ranges.length > 0 &&
100107
executor.executeInlineCells) {
101-
return await executor.executeInlineCells(document.uri, ranges);
108+
return await executor.executeInlineCells(document.uri, ranges, metadata);
102109
}
103-
return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined);
110+
return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined, metadata);
104111
}
105112

106113

0 commit comments

Comments
 (0)