From ad2a87c201d282313ebedef56c90960c77cad5df Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Sat, 26 Oct 2024 19:22:39 -0600 Subject: [PATCH 1/3] Don't resolve `quarto.runCurrent` to "run cell" for known runtimes in Positron --- apps/vscode/src/providers/cell/commands.ts | 116 +++++++++++---------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/apps/vscode/src/providers/cell/commands.ts b/apps/vscode/src/providers/cell/commands.ts index d3e66914..9fc942cc 100644 --- a/apps/vscode/src/providers/cell/commands.ts +++ b/apps/vscode/src/providers/cell/commands.ts @@ -24,15 +24,15 @@ import { TextEditorRevealType, window, } from "vscode"; -import { - Token, - TokenCodeBlock, - TokenMath, - isDisplayMath, - isExecutableLanguageBlock, - isExecutableLanguageBlockOf, - languageBlockAtPosition, - languageNameFromBlock +import { + Token, + TokenCodeBlock, + TokenMath, + isDisplayMath, + isExecutableLanguageBlock, + isExecutableLanguageBlockOf, + languageBlockAtPosition, + languageNameFromBlock } from "quarto-core"; import { Command } from "../../core/command"; import { isQuartoDoc } from "../../core/doc"; @@ -46,6 +46,7 @@ import { executeSelectionInteractive, } from "./executors"; import { ExtensionHost } from "../../host"; +import { hasHooks } from "../../host/hooks"; import { isKnitrDocument } from "../../host/executors"; import { commands } from "vscode"; @@ -53,7 +54,7 @@ export function cellCommands(host: ExtensionHost, engine: MarkdownEngine): Comma return [ new RunCurrentCommand(host, engine), new RunSelectionCommand(host, engine), - new RunCurrentAdvanceCommand(host,engine), + new RunCurrentAdvanceCommand(host, engine), new RunCurrentCellCommand(host, engine), new RunNextCellCommand(host, engine), new RunPreviousCellCommand(host, engine), @@ -69,7 +70,7 @@ abstract class RunCommand { constructor( protected readonly host_: ExtensionHost, protected readonly engine_: MarkdownEngine - ) {} + ) { } public async execute(line?: number): Promise { @@ -116,7 +117,7 @@ abstract class RunCommand { } } - + } protected includeFence() { @@ -130,7 +131,7 @@ abstract class RunCommand { protected abstract doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise; + ): Promise; protected abstract doExecute( editor: TextEditor, @@ -175,7 +176,7 @@ class RunCurrentCellCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const activeBlock = context.blocks.find(block => block.active); if (activeBlock) { const executor = await this.cellExecutorForLanguage(activeBlock.language, editor.document, this.engine_); @@ -204,7 +205,7 @@ class RunNextCellCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const activeBlockIndex = context.blocks.findIndex(block => block.active); const nextBlock = context.blocks[activeBlockIndex + 1]; if (nextBlock) { @@ -239,7 +240,7 @@ class RunPreviousCellCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const activeBlockIndex = context.blocks.findIndex(block => block.active); const prevBlock = context.blocks[activeBlockIndex - 1]; if (prevBlock) { @@ -258,15 +259,15 @@ class RunPreviousCellCommand extends RunCommand implements Command { class RunCurrentCommand extends RunCommand implements Command { constructor( - host: ExtensionHost, - engine: MarkdownEngine, + host: ExtensionHost, + engine: MarkdownEngine, private readonly runSelection_ = false ) { super(host, engine); } public readonly id: string = "quarto.runCurrent"; - + override includeFence() { return false; } @@ -281,32 +282,37 @@ class RunCurrentCommand extends RunCommand implements Command { const language = languageNameFromBlock(block); const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_); if (executor && isExecutableLanguageBlock(block)) { - - // if the selection is empty and this isn't a knitr document then it resolves to run cell - if (editor.selection.isEmpty && !isKnitrDocument(editor.document, this.engine_) && !this.runSelection_) { - + // Resolve this command to "run cell" when we can't find a selection: + // - the selection is empty + // - this is not a knitr document + // - this is not a Python or R document being used in Positron + const resolveToRunCell = editor.selection.isEmpty && + !this.runSelection_ && + !isKnitrDocument(editor.document, this.engine_) && + (!hasHooks() && (language === "python" || language === "r")); + + if (resolveToRunCell) { const code = codeWithoutOptionsFromBlock(block); await executeInteractive(executor, [code], editor.document); - } else { // submit const executed = await executeSelectionInteractive(executor); - // if the executor isn't capable of lenguage aware runSelection + // if the executor isn't capable of language aware runSelection // then determine the selection manually if (!executed) { // if the selection is empty take the whole line, otherwise // take the selected text exactly const selection = editor.selection.isEmpty ? editor.document.getText( - new Range( - new Position(editor.selection.start.line, 0), - new Position( - editor.selection.end.line, - editor.document.lineAt(editor.selection.end).text.length - ) + new Range( + new Position(editor.selection.start.line, 0), + new Position( + editor.selection.end.line, + editor.document.lineAt(editor.selection.end).text.length ) ) + ) : editor.document.getText(editor.selection); // for empty selections we advance to the next line @@ -325,7 +331,7 @@ class RunCurrentCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { // get selection and active block let selection = context.selectedText; const activeBlock = context.blocks.find(block => block.active); @@ -354,7 +360,7 @@ class RunCurrentCommand extends RunCommand implements Command { const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); if (executor) { await executeInteractive(executor, [selection], editor.document); - + // advance cursor if necessary if (action) { editor.setBlockSelection(context, "nextline"); @@ -400,11 +406,11 @@ class RunCurrentAdvanceCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const activeBlock = context.blocks.find(block => block.active); if (activeBlock) { const executor = await this.cellExecutorForLanguage(activeBlock.language, editor.document, this.engine_); - if (executor) { + if (executor) { await executeInteractive(executor, [activeBlock.code], editor.document); const blockContext = await editor.getActiveBlockContext(); if (blockContext) { @@ -466,11 +472,11 @@ class RunCellsAboveCommand extends RunCommand implements Command { } } } - + override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); if (executor) { const code: string[] = []; @@ -538,7 +544,7 @@ class RunCellsBelowCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { + ): Promise { const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_); if (executor) { let code: string[] | undefined; @@ -596,8 +602,8 @@ class RunAllCellsCommand extends RunCommand implements Command { override async doExecuteVisualMode( editor: QuartoVisualEditor, context: CodeViewActiveBlockContext - ) : Promise { - const code : string[] = []; + ): Promise { + const code: string[] = []; for (const block of context.blocks) { if (block.language === context.activeLanguage) { code.push(block.code); @@ -615,7 +621,7 @@ class RunAllCellsCommand extends RunCommand implements Command { class GoToCellCommand { constructor( - host: ExtensionHost, + host: ExtensionHost, engine: MarkdownEngine, dir: "next" | "previous" ) { @@ -651,7 +657,7 @@ class GoToCellCommand { } } } - + } @@ -695,17 +701,17 @@ function navigateToBlock(editor: TextEditor, block: Token) { } function nextBlock( - host: ExtensionHost, - line: number, - tokens: Token[], + host: ExtensionHost, + line: number, + tokens: Token[], requireEvaluated = false, requireExecutor = true -) : TokenMath | TokenCodeBlock | undefined { +): TokenMath | TokenCodeBlock | undefined { for (const block of tokens.filter( - requireExecutor - ? requireEvaluated - ? (token?: Token) => blockIsExecutable(host, token) - : (token?: Token) => blockHasExecutor(host, token) + requireExecutor + ? requireEvaluated + ? (token?: Token) => blockIsExecutable(host, token) + : (token?: Token) => blockHasExecutor(host, token) : (token?: Token) => token && isExecutableLanguageBlock(token) && !isDisplayMath(token) )) { if (block.range.start.line > line) { @@ -721,13 +727,13 @@ function previousBlock( tokens: Token[], requireEvaluated = false, requireExecutor = true -) : TokenMath | TokenCodeBlock | undefined { +): TokenMath | TokenCodeBlock | undefined { for (const block of tokens .filter( - requireExecutor - ? requireEvaluated - ? (token?: Token) => blockIsExecutable(host, token) - : (token?: Token) => blockHasExecutor(host, token) + requireExecutor + ? requireEvaluated + ? (token?: Token) => blockIsExecutable(host, token) + : (token?: Token) => blockHasExecutor(host, token) : (token?: Token) => token && isExecutableLanguageBlock(token) && !isDisplayMath(token)) .reverse()) { if (block.range.end.line < line) { From f6d116804f65525e7a987936b5fb4643222eaa8f Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Sat, 26 Oct 2024 19:23:11 -0600 Subject: [PATCH 2/3] Need `unadjustedRange()` for returned statement range --- apps/vscode/src/host/hooks.ts | 91 +++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 91aa8946..23ff087c 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -20,15 +20,16 @@ import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider } from '.'; import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors'; import { ExecuteQueue } from './execute-queue'; import { MarkdownEngine } from '../markdown/engine'; -import { virtualDoc, virtualDocUri, adjustedPosition } from "../vdoc/vdoc"; +import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange } from "../vdoc/vdoc"; +import { EmbeddedLanguage } from '../vdoc/languages'; declare global { - function acquirePositronApi() : hooks.PositronApi; + function acquirePositronApi(): hooks.PositronApi; } -let api : hooks.PositronApi | null | undefined; +let api: hooks.PositronApi | null | undefined; -export function hooksApi() : hooks.PositronApi | null { +export function hooksApi(): hooks.PositronApi | null { if (api === undefined) { try { api = acquirePositronApi(); @@ -43,20 +44,20 @@ export function hasHooks() { return !!hooksApi(); } -export function hooksExtensionHost() : ExtensionHost { +export function hooksExtensionHost(): ExtensionHost { return { // supported executable languages (we delegate to the default for langugaes // w/o runtimes so we support all languages) executableLanguages, - cellExecutorForLanguage: async (language: string, document: vscode.TextDocument, engine: MarkdownEngine, silent?: boolean) + cellExecutorForLanguage: async (language: string, document: vscode.TextDocument, engine: MarkdownEngine, silent?: boolean) : Promise => { - switch(language) { + switch (language) { // use hooks for known runtimes case "python": case "r": return { - execute: async (blocks: string[], _editorUri?: vscode.Uri) : Promise => { + execute: async (blocks: string[], _editorUri?: vscode.Uri): Promise => { const runtime = hooksApi()?.runtime; if (runtime === undefined) { @@ -68,7 +69,7 @@ export function hooksExtensionHost() : ExtensionHost { language = "r"; blocks = blocks.map(pythonWithReticulate); } - + // Our callback executes each block sequentially const callback = async () => { for (const block of blocks) { @@ -78,9 +79,9 @@ export function hooksExtensionHost() : ExtensionHost { await ExecuteQueue.instance.add(language, callback); }, - executeSelection: async () : Promise => { - await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', {languageId: language}); - } + executeSelection: async (): Promise => { + await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', { languageId: language }); + } }; // delegate for other languages @@ -92,16 +93,16 @@ export function hooksExtensionHost() : ExtensionHost { registerStatementRangeProvider: (engine: MarkdownEngine): vscode.Disposable => { const hooks = hooksApi(); if (hooks) { - return hooks.languages.registerStatementRangeProvider('quarto', + return hooks.languages.registerStatementRangeProvider('quarto', new EmbeddedStatementRangeProvider(engine)); } - return new vscode.Disposable(() => {}); + return new vscode.Disposable(() => { }); }, createPreviewPanel: ( - viewType: string, + viewType: string, title: string, - preserveFocus?: boolean, + preserveFocus?: boolean, options?: vscode.WebviewPanelOptions & vscode.WebviewOptions ): HostWebviewPanel => { @@ -117,7 +118,7 @@ export function hooksExtensionHost() : ExtensionHost { portMapping: options?.portMapping } )!; - + // adapt to host interface return new HookWebviewPanel(panel); } @@ -126,7 +127,7 @@ export function hooksExtensionHost() : ExtensionHost { class HookWebviewPanel implements HostWebviewPanel { - constructor(private readonly panel_: hooks.PreviewPanel) {} + constructor(private readonly panel_: hooks.PreviewPanel) { } get webview() { return this.panel_.webview; }; get visible() { return this.panel_.visible; }; @@ -139,43 +140,49 @@ class HookWebviewPanel implements HostWebviewPanel { } class EmbeddedStatementRangeProvider implements HostStatementRangeProvider { - private readonly _engine: MarkdownEngine; + private readonly _engine: MarkdownEngine; - constructor( - readonly engine: MarkdownEngine, - ) { - this._engine = engine; - } + constructor( + readonly engine: MarkdownEngine, + ) { + this._engine = engine; + } async provideStatementRange( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken): Promise { - const vdoc = await virtualDoc(document, position, this._engine); - if (vdoc) { - const vdocUri = await virtualDocUri(vdoc, document.uri, "statementRange"); - try { - return getStatementRange(vdocUri.uri, adjustedPosition(vdoc.language, position)); - } catch (error) { - return undefined; - } finally { - if (vdocUri.cleanup) { - await vdocUri.cleanup(); - } - } - } else { + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken): Promise { + const vdoc = await virtualDoc(document, position, this._engine); + if (vdoc) { + const vdocUri = await virtualDocUri(vdoc, document.uri, "statementRange"); + try { + return getStatementRange( + vdocUri.uri, + adjustedPosition(vdoc.language, position), + vdoc.language + ); + } catch (error) { return undefined; + } finally { + if (vdocUri.cleanup) { + await vdocUri.cleanup(); + } } - }; + } else { + return undefined; + } + }; } async function getStatementRange( uri: vscode.Uri, position: vscode.Position, + language: EmbeddedLanguage ) { - return await vscode.commands.executeCommand( + const result = await vscode.commands.executeCommand( "vscode.executeStatementRangeProvider", uri, position ); + return { range: unadjustedRange(language, result.range), code: result.code }; } From 6d208067a1b70e1c1bf3f2d203984237074bd424 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Mon, 28 Oct 2024 11:08:37 -0600 Subject: [PATCH 3/3] Update changelog --- apps/vscode/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index 7c5e7387..8b31812d 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.117.0 (unreleased) +- Improved statement execution for Python `.qmd` files in Positron () + ## 1.116.0 (Release on 2024-10-08) - Fix issue with raw html blocks being removed from document by Visual Editor ()