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: 2 additions & 0 deletions apps/vscode/src/host/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
executeSelection?: () => Promise<void>;
executeAtPosition?: (uri: Uri, pos: Position) => Promise<Position>;
}

export function executableLanguages() {
Expand Down
8 changes: 8 additions & 0 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -83,6 +85,12 @@ export function hooksExtensionHost(): ExtensionHost {
},
executeSelection: async (): Promise<void> => {
await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', { languageId: language });
},
executeAtPosition: async (uri: Uri, position: Position): Promise<Position> => {
return await vscode.commands.executeCommand(
'workbench.action.positronConsole.executeCode',
{ languageId: language, uri, position }
);
}
};

Expand Down
82 changes: 27 additions & 55 deletions apps/vscode/src/providers/cell/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
codeWithoutOptionsFromBlock,
executeInteractive,
executeSelectionInteractive,
executeAtPositionInteractive,
} from "./executors";
import { ExtensionHost } from "../../host";
import { tryAcquirePositronApi } from "@posit-dev/positron";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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<StatementRange>(
"vscode.executeStatementRangeProvider",

const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_);
if (executor) {
const nextStatementPos = await withVirtualDocUri(
vdoc,
parentUri,
"executeSelectionAtPositionInteractive",
(uri) => executeAtPositionInteractive(
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
Expand All @@ -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");
}

}

}
}
}
Expand Down
8 changes: 8 additions & 0 deletions apps/vscode/src/providers/cell/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -90,6 +92,12 @@ export async function executeInteractive(
return await executor.execute(blocks, !document.isUntitled ? document.uri : undefined);
}


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
// if the executor doesn't support this, in which case generic
// executeInteractive will be called)
Expand Down
5 changes: 3 additions & 2 deletions apps/vscode/src/vdoc/vdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ export type VirtualDocAction =
"definition" |
"format" |
"statementRange" |
"helpTopic";
"helpTopic" |
"executeSelectionAtPositionInteractive";

export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise<void> };
export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise<void>; };

/**
* Execute a callback on a virtual document's temporary URI
Expand Down