From 34cd411826175482a45f1681ff9f51705fcce47e Mon Sep 17 00:00:00 2001 From: elliot Date: Fri, 14 Nov 2025 11:13:13 -0500 Subject: [PATCH 1/2] Fake code to call `positronConsole.executeCode` with uri, position returning position --- apps/vscode/src/host/executors.ts | 2 + apps/vscode/src/host/hooks.ts | 8 ++ apps/vscode/src/providers/cell/commands.ts | 82 +++++++-------------- apps/vscode/src/providers/cell/executors.ts | 8 ++ apps/vscode/src/vdoc/vdoc.ts | 5 +- 5 files changed, 48 insertions(+), 57 deletions(-) diff --git a/apps/vscode/src/host/executors.ts b/apps/vscode/src/host/executors.ts index 8306cc87..11db0d50 100644 --- a/apps/vscode/src/host/executors.ts +++ b/apps/vscode/src/host/executors.ts @@ -22,10 +22,12 @@ import { documentFrontMatter } from "../markdown/document"; import { isExecutableLanguageBlockOf } from "quarto-core"; import { workspace } from "vscode"; import { JupyterKernelspec } from "core"; +import { Position } from "vscode"; export interface CellExecutor { execute: (blocks: string[], editorUri?: Uri) => Promise; executeSelection?: () => Promise; + executeSelectionAtPosition?: (uri: Uri, pos: Position) => Promise; } export function executableLanguages() { diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index d7bbf093..1514c8e5 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -23,6 +23,8 @@ import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocu import { ExecuteQueue } from './execute-queue'; import { MarkdownEngine } from '../markdown/engine'; import { virtualDoc, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc"; +import { Position } from 'vscode'; +import { Uri } from 'vscode'; declare global { function acquirePositronApi(): hooks.PositronApi; @@ -83,6 +85,12 @@ export function hooksExtensionHost(): ExtensionHost { }, executeSelection: async (): Promise => { await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', { languageId: language }); + }, + executeSelectionAtPosition: async (uri: Uri, position: Position): Promise => { + return await vscode.commands.executeCommand( + 'workbench.action.positronConsole.executeCode', + { languageId: language, uri, position } + ); } }; diff --git a/apps/vscode/src/providers/cell/commands.ts b/apps/vscode/src/providers/cell/commands.ts index e2250fdc..bc7c384d 100644 --- a/apps/vscode/src/providers/cell/commands.ts +++ b/apps/vscode/src/providers/cell/commands.ts @@ -46,6 +46,7 @@ import { codeWithoutOptionsFromBlock, executeInteractive, executeSelectionInteractive, + executionSelectionAtPositionInteractive, } from "./executors"; import { ExtensionHost } from "../../host"; import { tryAcquirePositronApi } from "@posit-dev/positron"; @@ -265,15 +266,7 @@ class RunPreviousCellCommand extends RunCommand implements Command { // More permissive type than `Position` so its easier to construct via a literal type LineAndCharPos = { line: number, character: number; }; -// More permissive type than `Range` so its easier to construct via a literal -type LineAndCharRange = { start: LineAndCharPos, end: LineAndCharPos; }; - -function extractRangeFromCode(code: string, range: LineAndCharRange): string { - const extractedRange = lines(code).slice(range.start.line, range.end.line + 1); - extractedRange[0] = extractedRange[0].slice(range.start.character); - extractedRange[extractedRange.length - 1] = extractedRange[extractedRange.length - 1].slice(0, range.end.character); - return extractedRange.join('\n'); -} + // Run the code at the cursor class RunCurrentCommand extends RunCommand implements Command { @@ -355,14 +348,6 @@ class RunCurrentCommand extends RunCommand implements Command { const selection = context.selectedText; const activeBlock = context.blocks.find(block => block.active); - const exec = async (action: CodeViewSelectionAction, selection: string) => { - const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); - if (executor) { - await executeInteractive(executor, [selection], editor.document); - await editor.setBlockSelection(context, action); - } - }; - // if in Positron if (isPositron) { if (activeBlock && selection.length <= 0) { @@ -376,43 +361,23 @@ class RunCurrentCommand extends RunCommand implements Command { new Position(p.line + injectedLines, p.character); const positionOutOfVdoc = (p: LineAndCharPos) => new Position(p.line - injectedLines, p.character); - const rangeOutOfVdoc = (r: Range): LineAndCharRange => ({ - start: positionOutOfVdoc(r.start), - end: positionOutOfVdoc(r.end) - }); - const getStatementRange = async (pos: LineAndCharPos) => { - const result = await withVirtualDocUri(vdoc, parentUri, "statementRange", async (uri) => { - return await commands.executeCommand( - "vscode.executeStatementRangeProvider", + + const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); + if (executor) { + const nextStatementPos = await withVirtualDocUri( + vdoc, + parentUri, + "executeSelectionAtPositionInteractive", + (uri) => executionSelectionAtPositionInteractive( + executor, uri, - positionIntoVdoc(pos) - ); - }); - return rangeOutOfVdoc(result.range); - }; - - const range = await getStatementRange(context.selection.start); - const code = extractRangeFromCode(activeBlock.code, range); - - // BEGIN ref: https://github.com/posit-dev/positron/blob/main/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts#L428 - // strategy from Positron using `StatementRangeProvider` to find range of next statement - // and move cursor based on that. - if (range.end.line + 1 <= codeLines.length) { - // get range of statement at line after current statement) - const nextRange = await getStatementRange(new Position(range.end.line + 1, 1)); - - if (nextRange.start.line > range.end.line) { - exec(nextRange.start, code); - // the next statement range may start before & end after the current statement if e.g. inside a function: - } else if (nextRange.end.line > range.end.line) { - exec(nextRange.end, code); - } else { - exec("nextline", code); + positionIntoVdoc(context.selection.start) + ) + ); + if (nextStatementPos !== undefined) { + await editor.setBlockSelection(context, positionOutOfVdoc(nextStatementPos)); } - } else { - exec("nextline", code); } - // END ref. } } // if not in Positron @@ -427,11 +392,18 @@ class RunCurrentCommand extends RunCommand implements Command { } } } else { - if (selection.length > 0) { - exec("nextline", selection); - } else if (activeBlock) { // if the selection is empty take the whole line as the selection - exec("nextline", lines(activeBlock.code)[context.selection.start.line]); + const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); + if (executor) { + if (selection.length > 0) { + await executeInteractive(executor, [selection], editor.document); + await editor.setBlockSelection(context, "nextline"); + } else if (activeBlock) { // if the selection is empty take the whole line as the selection + await executeInteractive(executor, [lines(activeBlock.code)[context.selection.start.line]], editor.document); + await editor.setBlockSelection(context, "nextline"); + } + } + } } } diff --git a/apps/vscode/src/providers/cell/executors.ts b/apps/vscode/src/providers/cell/executors.ts index e6092386..3581f1bb 100644 --- a/apps/vscode/src/providers/cell/executors.ts +++ b/apps/vscode/src/providers/cell/executors.ts @@ -34,6 +34,8 @@ import { cellOptionsForToken, kExecuteEval } from "./options"; import { CellExecutor, ExtensionHost } from "../../host"; import { executableLanguages } from "../../host/executors"; +import { Position } from "vscode"; +import { Uri } from "vscode"; export function hasExecutor(_host: ExtensionHost, language: string) { @@ -90,6 +92,12 @@ export async function executeInteractive( return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined); } + +export async function executionSelectionAtPositionInteractive(executor: CellExecutor, uri: Uri, position: Position) { + if (executor?.executeSelectionAtPosition) { + return await executor.executeSelectionAtPosition(uri, position); + } +} // attempt language aware execution of current selection (returns false // if the executor doesn't support this, in which case generic // executeInteractive will be called) diff --git a/apps/vscode/src/vdoc/vdoc.ts b/apps/vscode/src/vdoc/vdoc.ts index f1441fe8..224b3506 100644 --- a/apps/vscode/src/vdoc/vdoc.ts +++ b/apps/vscode/src/vdoc/vdoc.ts @@ -118,9 +118,10 @@ export type VirtualDocAction = "definition" | "format" | "statementRange" | - "helpTopic"; + "helpTopic" | + "executeSelectionAtPositionInteractive"; -export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise }; +export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise; }; /** * Execute a callback on a virtual document's temporary URI From 361ca0eb584bcbc6f84c54cf4699d1d1bd0c9bdd Mon Sep 17 00:00:00 2001 From: elliot Date: Fri, 14 Nov 2025 11:46:07 -0500 Subject: [PATCH 2/2] Rename `executeSelectionAtPosition` to `executeAtPosition` --- apps/vscode/src/host/executors.ts | 2 +- apps/vscode/src/host/hooks.ts | 2 +- apps/vscode/src/providers/cell/commands.ts | 4 ++-- apps/vscode/src/providers/cell/executors.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/vscode/src/host/executors.ts b/apps/vscode/src/host/executors.ts index 11db0d50..af23290e 100644 --- a/apps/vscode/src/host/executors.ts +++ b/apps/vscode/src/host/executors.ts @@ -27,7 +27,7 @@ import { Position } from "vscode"; export interface CellExecutor { execute: (blocks: string[], editorUri?: Uri) => Promise; executeSelection?: () => Promise; - executeSelectionAtPosition?: (uri: Uri, pos: Position) => Promise; + executeAtPosition?: (uri: Uri, pos: Position) => Promise; } export function executableLanguages() { diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 1514c8e5..1db10456 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -86,7 +86,7 @@ export function hooksExtensionHost(): ExtensionHost { executeSelection: async (): Promise => { await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', { languageId: language }); }, - executeSelectionAtPosition: async (uri: Uri, position: Position): Promise => { + executeAtPosition: async (uri: Uri, position: Position): Promise => { return await vscode.commands.executeCommand( 'workbench.action.positronConsole.executeCode', { languageId: language, uri, position } diff --git a/apps/vscode/src/providers/cell/commands.ts b/apps/vscode/src/providers/cell/commands.ts index bc7c384d..d2829a35 100644 --- a/apps/vscode/src/providers/cell/commands.ts +++ b/apps/vscode/src/providers/cell/commands.ts @@ -46,7 +46,7 @@ import { codeWithoutOptionsFromBlock, executeInteractive, executeSelectionInteractive, - executionSelectionAtPositionInteractive, + executeAtPositionInteractive, } from "./executors"; import { ExtensionHost } from "../../host"; import { tryAcquirePositronApi } from "@posit-dev/positron"; @@ -368,7 +368,7 @@ class RunCurrentCommand extends RunCommand implements Command { vdoc, parentUri, "executeSelectionAtPositionInteractive", - (uri) => executionSelectionAtPositionInteractive( + (uri) => executeAtPositionInteractive( executor, uri, positionIntoVdoc(context.selection.start) diff --git a/apps/vscode/src/providers/cell/executors.ts b/apps/vscode/src/providers/cell/executors.ts index 3581f1bb..9f28fab9 100644 --- a/apps/vscode/src/providers/cell/executors.ts +++ b/apps/vscode/src/providers/cell/executors.ts @@ -93,9 +93,9 @@ export async function executeInteractive( } -export async function executionSelectionAtPositionInteractive(executor: CellExecutor, uri: Uri, position: Position) { - if (executor?.executeSelectionAtPosition) { - return await executor.executeSelectionAtPosition(uri, position); +export async function executeAtPositionInteractive(executor: CellExecutor, uri: Uri, position: Position) { + if (executor?.executeAtPosition) { + return await executor.executeAtPosition(uri, position); } } // attempt language aware execution of current selection (returns false