Skip to content
Merged
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 @@ -302,7 +302,7 @@
"category": "Quarto",
"actionBarOptions": {
"controlType": "button",
"displayTitle": true
"displayTitle": false
}
},
{
Expand Down
5 changes: 5 additions & 0 deletions apps/vscode/src/@types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ declare module 'positron' {
focus: boolean,
allowIncomplete: boolean
): Thenable<boolean>;

executeInlineCell(
documentUri: vscode.Uri,
cellRanges: vscode.Range[]
): Thenable<void>;
}

export interface PositronLanguages {
Expand Down
3 changes: 2 additions & 1 deletion apps/vscode/src/host/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*
*/

import { Uri, commands, window, extensions } from "vscode";
import { Uri, commands, window, extensions, Range } from "vscode";

import semver from "semver";
import { TextDocument } from "vscode";
Expand All @@ -28,6 +28,7 @@ export interface CellExecutor {
execute: (blocks: string[], editorUri?: Uri) => Promise<void>;
executeSelection?: () => Promise<void>;
executeAtPosition?: (uri: Uri, pos: Position) => Promise<Position>;
executeInlineCells?: (documentUri: Uri, cellRanges: Range[]) => Promise<void>;
}

export function executableLanguages() {
Expand Down
24 changes: 22 additions & 2 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,19 @@ 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 { Position, Range } from 'vscode';
import { Uri } from 'vscode';

/**
* Check if inline output is enabled in Positron settings.
* This helper is shared with main.ts for code lens visibility.
*/
export function isInlineOutputEnabled(): boolean {
return vscode.workspace
.getConfiguration("positron.quarto.inlineOutput")
.get<boolean>("enabled", false);
}

declare global {
function acquirePositronApi(): hooks.PositronApi;
}
Expand Down Expand Up @@ -61,7 +71,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): Promise<void> => {
const runtime = hooksApi()?.runtime;

if (runtime === undefined) {
Expand Down Expand Up @@ -99,6 +109,16 @@ export function hooksExtensionHost(): ExtensionHost {
console.error('error when using `positron.executeCodeFromPosition`');
}
return position;
},
executeInlineCells: async (documentUri: vscode.Uri, cellRanges: Range[]): Promise<void> => {
const runtime = hooksApi()?.runtime;

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

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

Expand Down
47 changes: 42 additions & 5 deletions apps/vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { activateEditor } from "./providers/editor/editor";
import { activateCopyFiles } from "./providers/copyfiles";
import { activateZotero } from "./providers/zotero/zotero";
import { extensionHost } from "./host";
import { isInlineOutputEnabled } from "./host/hooks";
import { initQuartoContext, getSourceDescription } from "quarto-core";
import { configuredQuartoPath } from "./core/quarto";
import { activateDenoConfig } from "./providers/deno-config";
Expand Down Expand Up @@ -153,11 +154,47 @@ export async function activate(context: vscode.ExtensionContext): Promise<Quarto

commands.push(...activateCodeFormatting(engine));

// provide code lens
vscode.languages.registerCodeLensProvider(
kQuartoDocSelector,
quartoCellExecuteCodeLensProvider(host, engine)
);
// provide code lens (conditionally in Positron based on inline output setting)
const isPositron = tryAcquirePositronApi();
if (isPositron) {
// In Positron, only show code lens when inline output is disabled
let codeLensDisposable: vscode.Disposable | undefined;

const updateCodeLens = () => {
const inlineOutputEnabled = isInlineOutputEnabled();

if (inlineOutputEnabled && codeLensDisposable) {
// Dispose existing code lens when inline output is enabled
codeLensDisposable.dispose();
codeLensDisposable = undefined;
} else if (!inlineOutputEnabled && !codeLensDisposable) {
// Register code lens when inline output is disabled
codeLensDisposable = vscode.languages.registerCodeLensProvider(
kQuartoDocSelector,
quartoCellExecuteCodeLensProvider(host, engine)
);
context.subscriptions.push(codeLensDisposable);
}
};

// Initial setup
updateCodeLens();

// Listen for setting changes
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration("positron.quarto.inlineOutput.enabled")) {
updateCodeLens();
}
})
);
} else {
// In VS Code, always register the code lens
vscode.languages.registerCodeLensProvider(
kQuartoDocSelector,
quartoCellExecuteCodeLensProvider(host, engine)
);
}

// provide file copy/drop handling
activateCopyFiles(context);
Expand Down
46 changes: 31 additions & 15 deletions apps/vscode/src/providers/cell/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ class RunCurrentCellCommand extends RunCommand implements Command {
const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
if (executor) {
const code = codeWithoutOptionsFromBlock(block);
await executeInteractive(executor, [code], editor.document);
const ranges = block.range ? [block.range] : undefined;
await executeInteractive(executor, [code], editor.document, ranges);
}
}
}
Expand Down Expand Up @@ -499,16 +500,21 @@ class RunCellsAboveCommand extends RunCommand implements Command {

const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
if (executor) {
// accumulate code
// accumulate code and ranges
const code: string[] = [];
for (const block of blocks.filter(
const ranges: Range[] = [];
for (const blk of blocks.filter(
isExecutableLanguageBlockOf(language)
)) {
code.push(codeWithoutOptionsFromBlock(block));
) as Array<TokenMath | TokenCodeBlock>) {
code.push(codeWithoutOptionsFromBlock(blk));
if (blk.range) {
ranges.push(blk.range);
}
}

// execute
await executeInteractive(executor, code, editor.document);
// 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);
}
}
}
Expand Down Expand Up @@ -558,9 +564,10 @@ class RunCellsBelowCommand extends RunCommand implements Command {
: undefined;

const blocks: string[] = [];
const ranges: Range[] = [];
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 (line < blk.range.start.line) {
if (blk.range && line < blk.range.start.line) {
// set language if needed
const blockLanguage = languageNameFromBlock(blk);
if (!language) {
Expand All @@ -569,14 +576,16 @@ class RunCellsBelowCommand extends RunCommand implements Command {
// include blocks of this language
if (blockLanguage === language) {
blocks.push(codeWithoutOptionsFromBlock(blk));
ranges.push(blk.range);
}
}
}
// execute
// execute (only pass ranges if we collected the same number as code blocks)
if (language && blocks.length > 0) {
const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
if (executor) {
await executeInteractive(executor, blocks, editor.document);
const validRanges = ranges.length === blocks.length ? ranges : undefined;
await executeInteractive(executor, blocks, editor.document, validRanges);
}
}
}
Expand Down Expand Up @@ -622,19 +631,25 @@ class RunAllCellsCommand extends RunCommand implements Command {
) {
let language: string | undefined;
const blocks: string[] = [];
for (const block of tokens.filter((token?: Token) => blockIsExecutable(this.host_, token)) as Array<TokenMath | TokenCodeBlock>) {
const blockLanguage = languageNameFromBlock(block);
const ranges: Range[] = [];
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(block));
blocks.push(codeWithoutOptionsFromBlock(blk));
if (blk.range) {
ranges.push(blk.range);
}
}
}
if (language && blocks.length > 0) {
const executor = await this.cellExecutorForLanguage(language, editor.document, this.engine_);
if (executor) {
await executeInteractive(executor, blocks, editor.document);
// 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);
}
}
}
Expand Down Expand Up @@ -727,7 +742,8 @@ async function runAdjacentBlock(host: ExtensionHost, editor: TextEditor, engine:
const language = languageNameFromBlock(block);
const executor = await host.cellExecutorForLanguage(language, editor.document, engine);
if (executor) {
await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document);
const ranges = block.range ? [block.range] : undefined;
await executeInteractive(executor, [codeWithoutOptionsFromBlock(block)], editor.document, ranges);
}
}

Expand Down
15 changes: 13 additions & 2 deletions apps/vscode/src/providers/cell/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// (e.g. see https://github.com/JeepShen/vscode-markdown-code-runner)


import { TextDocument } from "vscode";
import { TextDocument, Range } from "vscode";

import {
codeForExecutableLanguageBlock,
Expand All @@ -34,6 +34,7 @@ import { cellOptionsForToken, kExecuteEval } from "./options";

import { CellExecutor, ExtensionHost } from "../../host";
import { executableLanguages } from "../../host/executors";
import { isInlineOutputEnabled } from "../../host/hooks";
import { Position } from "vscode";
import { Uri } from "vscode";

Expand Down Expand Up @@ -87,8 +88,18 @@ export function codeWithoutOptionsFromBlock(token: TokenMath | TokenCodeBlock) {
export async function executeInteractive(
executor: CellExecutor,
blocks: string[],
document: TextDocument
document: TextDocument,
ranges?: Range[]
): 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
if (isInlineOutputEnabled() &&
!document.isUntitled &&
ranges &&
ranges.length > 0 &&
executor.executeInlineCells) {
return await executor.executeInlineCells(document.uri, ranges);
}
return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined);
}

Expand Down