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
1 change: 1 addition & 0 deletions apps/vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.138.0 (Unreleased)

- In Positron, running a Python cell in a knitr document now respects the `quarto.cells.useReticulate` setting, instead of always routing it through reticulate on the R console (<https://github.com/quarto-dev/quarto/pull/1116>).

## 1.137.0 (Release on 2026-09-04)

Expand Down
17 changes: 11 additions & 6 deletions apps/vscode/src/host/positron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,13 @@ export function positronExtensionHost(outputChannel?: vscode.LogOutputChannel):
return;
}

if (language === "python" && isKnitrDocument(document, engine)) {
language = "r";
let executionLanguage = language;
if (
language === "python" &&
isKnitrDocument(document, engine) &&
vscode.workspace.getConfiguration("quarto").get("cells.useReticulate", true)
) {
executionLanguage = "r";
blocks = blocks.map(pythonWithReticulate);
}

Expand All @@ -119,7 +124,7 @@ export function positronExtensionHost(outputChannel?: vscode.LogOutputChannel):

try {
await runtime.executeCode(
language, // The language ID
executionLanguage, // The language ID
blocks[i], // The code string to execute
false, // Whether to focus the console
true, // Whether to allow incomplete code to run
Expand All @@ -136,7 +141,7 @@ export function positronExtensionHost(outputChannel?: vscode.LogOutputChannel):
if (!runtimeFailure) {
// The code couldn't be submitted to the runtime. Log it
// and let it propagate so the user finds out.
outputChannel?.error(`Failed to execute ${language} cell: ${message}`);
outputChannel?.error(`Failed to execute ${executionLanguage} cell: ${message}`);
throw err;
}

Expand All @@ -145,15 +150,15 @@ export function positronExtensionHost(outputChannel?: vscode.LogOutputChannel):
// record but don't let it propagate to the command handler,
// which would surface it again as a notification popup.
// https://github.com/posit-dev/positron/issues/9845
outputChannel?.debug(`Error executing ${language} cell: ${message}`);
outputChannel?.debug(`Error executing ${executionLanguage} cell: ${message}`);

// Stop executing any subsequent blocks since one failed.
break;
}
}
};

await ExecuteQueue.instance.add(language, callback);
await ExecuteQueue.instance.add(executionLanguage, callback);
},
executeSelection: async (): Promise<void> => {
await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', { languageId: language });
Expand Down
61 changes: 57 additions & 4 deletions apps/vscode/src/test/positron/execute-cell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ suite("Positron: cell execution", function () {

// `acquirePositronApi` is injected onto the global by Positron; we swap it for
// a spy during each test and must restore it afterwards.
const globalWithApi = globalThis as { acquirePositronApi?: () => unknown };
const globalWithApi = globalThis as { acquirePositronApi?: () => unknown; };
let originalAcquire: (() => unknown) | undefined;

teardown(function () {
Expand Down Expand Up @@ -73,7 +73,7 @@ suite("Positron: cell execution", function () {
// which we record instead of dispatching to a kernel.
const calls: RuntimeCall[] = [];
originalAcquire = globalWithApi.acquirePositronApi;
const realApi = originalAcquire!() as { runtime: Record<string, unknown> };
const realApi = originalAcquire!() as { runtime: Record<string, unknown>; };
const fakeRuntime = new Proxy(realApi.runtime, {
get(target, prop, receiver) {
if (prop === "executeCode") {
Expand Down Expand Up @@ -134,7 +134,7 @@ suite("Positron: cell execution", function () {
assert.ok(
call,
"Running the cell should call positron.runtime.executeCode('python', ...). " +
`Observed: ${describe(calls)}`
`Observed: ${describe(calls)}`
);

const code = String(call!.args[1]);
Expand Down Expand Up @@ -171,7 +171,7 @@ suite("Positron: cell execution", function () {
assert.ok(
call,
"A knitr Python cell should be submitted to executeCode('r', ...). " +
`Observed: ${describe(calls)}`
`Observed: ${describe(calls)}`
);

const code = String(call!.args[1]);
Expand All @@ -184,6 +184,59 @@ suite("Positron: cell execution", function () {
"the original Python code should be embedded in the reticulate call"
);
});

test("submits a knitr Python cell as python when cells.useReticulate is false", async function () {
const config = vscode.workspace.getConfiguration("quarto");
const previous = config.get<boolean>("cells.useReticulate");
await config.update(
"cells.useReticulate",
false,
vscode.ConfigurationTarget.Global
);
try {
const marker = `qmd_marker_${Date.now()}`;
const qmd = [
"---",
"engine: knitr",
"---",
"",
"```{python}",
`${marker} = 42`,
"```",
"",
].join("\n");
// Cursor on the statement (line 5).
const calls = await runCellAndCaptureCalls(qmd, 5);

const rCall = calls.find(
(c) => c.method === "executeCode" && c.args[0] === "r"
);
assert.ok(
!rCall,
"With cells.useReticulate=false, a knitr Python cell should not be " +
`submitted to the R runtime. Observed: ${describe(calls)}`
);

const pyCall = calls.find(
(c) => c.method === "executeCode" && c.args[0] === "python"
);
assert.ok(
pyCall,
"With cells.useReticulate=false, a knitr Python cell should be " +
`submitted to executeCode('python', ...). Observed: ${describe(calls)}`
);
assert.ok(
!String(pyCall!.args[1]).includes("reticulate::repl_python"),
"the code should not be wrapped in reticulate::repl_python(...)"
);
} finally {
await config.update(
"cells.useReticulate",
previous,
vscode.ConfigurationTarget.Global
);
}
});
});

/** Compact summary of observed runtime calls for assertion messages. */
Expand Down
Loading