Skip to content

Feature/Positron Editor Action Bar integration for Source / Visual toggle and [X] Render on Save checkbox #698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 18, 2025
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: 2 additions & 0 deletions apps/vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 1.121.0 (unreleased)

- Add new controls for Positron editor action bar (<https://github.com/quarto-dev/quarto/pull/698>).

## 1.120.0 (Release on 2025-04-07)

- Fix issue where format on save could overwrite the contents of a document with incorrect results (<https://github.com/quarto-dev/quarto/pull/688>).
Expand Down
46 changes: 44 additions & 2 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@
"command": "quarto.preview",
"title": "Preview",
"icon": "$(preview)",
"category": "Quarto"
"category": "Quarto",
"actionBarOptions": {
"controlType": "button",
"displayTitle": true
}
},
{
"command": "quarto.previewScript",
Expand Down Expand Up @@ -425,6 +429,28 @@
"category": "Quarto",
"enablement": "editorTextFocus && !editorReadonly && editorLangId == quarto"
},
{
"command": "quarto.toggleEditMode",
"title": "Edit Mode",
"category": "Quarto",
"enablement": "editorLangId == quarto",
"actionBarOptions": {
"controlType": "toggle",
"leftTitle": "Source",
"rightTitle": "Visual",
"toggled": "activeCustomEditorId == 'quarto.visualEditor'"
}
},
{
"command": "quarto.toggleRenderOnSave",
"title": "Render on Save",
"category": "Quarto",
"enablement": "editorLangId == quarto",
"actionBarOptions": {
"controlType": "checkbox",
"checked": "((activeCustomEditorId == 'quarto.visualEditor' || quarto.editor.type == quarto) && quarto.editor.renderOnSave) || ((activeCustomEditorId == 'quarto.visualEditor' || quarto.editor.type == 'quarto-shiny') && quarto.editor.renderOnSaveShiny)"
}
},
{
"command": "quarto.zoteroConfigureLibrary",
"category": "Quarto",
Expand Down Expand Up @@ -608,6 +634,18 @@
}
],
"menus": {
"editor/actions/right": [
{
"command": "quarto.toggleRenderOnSave",
"when": "(activeCustomEditorId == 'quarto.visualEditor' || editorLangId == quarto) && (quarto.editor.type == quarto || quarto.editor.type == 'quarto-shiny')",
"group": "Quarto"
},
{
"command": "quarto.toggleEditMode",
"when": "(activeCustomEditorId == 'quarto.visualEditor' || editorLangId == quarto) && (quarto.editor.type == quarto || quarto.editor.type == 'quarto-shiny')",
"group": "Quarto"
}
],
"editor/context": [
{
"command": "quarto.formatCell",
Expand Down Expand Up @@ -948,7 +986,11 @@
"type": "string",
"markdownDescription": "Control the background coloring of executable code cells.",
"default": "default",
"enum": ["default", "useTheme", "off"],
"enum": [
"default",
"useTheme",
"off"
],
"markdownEnumDescriptions": [
"Use the default light and dark background colors. Specify these colors with `quarto.cells.background.lightDefault` and `quarto.cells.background.darkDefault`.",
"Use the `notebook.selectedCellBackground` color from the current VS Code theme.",
Expand Down
1 change: 0 additions & 1 deletion apps/vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export async function activate(context: vscode.ExtensionContext) {
const assistCommands = activateQuartoAssistPanel(context, engine);
commands.push(...assistCommands);
}

// walkthough
commands.push(...walkthroughCommands(host, quartoContext));

Expand Down
154 changes: 135 additions & 19 deletions apps/vscode/src/providers/context-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,167 @@ import debounce from "lodash.debounce";
import { isQuartoDoc } from "../core/doc";
import { MarkdownEngine } from "../markdown/engine";
import { mainLanguage } from "../vdoc/vdoc";
import { isQuartoShinyDoc } from "./preview/preview-util";
import { workspace } from "vscode";
import { VisualEditorProvider } from "./editor/editor";

const debounceOnDidChangeDocumentMs = 250;

// state for quarto.editor.type context key
let quartoEditorType: 'quarto' | 'quarto-shiny' | undefined = undefined;

// state for quarto.editor.renderOnSave override
// this is only defined when the user has changed the value at runtime
let renderOnSaveOverride: boolean | undefined = undefined;

// state for quarto.editor.renderOnSaveShiny override
// this is only defined when the user has changed the value at runtime
let renderOnSaveShinyOverride: boolean | undefined = undefined;

export function activateContextKeySetter(
context: vscode.ExtensionContext,
engine: MarkdownEngine
) {
// set the initial context keys
setEditorContextKeys(vscode.window.activeTextEditor, engine);
setLanguageContextKeys(vscode.window.activeTextEditor, engine);

// register for quarto.render.renderOnSave or quarto.render.renderOnSaveShiny configuration change notification
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(event => {
// if the change affects quarto.render.renderOnSave or quarto.render.renderOnSaveShiny, set the editor context keys.
if (event.affectsConfiguration('quarto.render.renderOnSave') || event.affectsConfiguration('quarto.render.renderOnSaveShiny')) {
setEditorContextKeys(vscode.window.activeTextEditor, engine);
}
}));

// set context keys when active text editor changes
vscode.window.onDidChangeActiveTextEditor(
(editor) => {
if (editor) {
setContextKeys(editor, engine);
}
},
vscode.window.onDidChangeActiveTextEditor(activeTextEditor => {
setEditorContextKeys(activeTextEditor, engine);
setLanguageContextKeys(activeTextEditor, engine);
},
null,
context.subscriptions
);

// set context keys on changes to the document (if it's active)
vscode.workspace.onDidChangeTextDocument(
(event) => {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
debounce(
() => setContextKeys(activeEditor, engine),
debounceOnDidChangeDocumentMs
)();
}
},
vscode.workspace.onDidChangeTextDocument(event => {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
debounce(
() => setLanguageContextKeys(activeEditor, engine),
debounceOnDidChangeDocumentMs
)();
}
},
null,
context.subscriptions
);
}

function setContextKeys(editor: vscode.TextEditor, engine: MarkdownEngine) {
if (!editor || !isQuartoDoc(editor.document)) {
// gets render on save
export function getRenderOnSave() {
return renderOnSaveOverride === undefined
? readRenderOnSaveConfiguration()
: renderOnSaveOverride;
}

// gets render on save shiny
export function getRenderOnSaveShiny() {
return renderOnSaveShinyOverride === undefined
? readRenderOnSaveShinyConfiguration()
: renderOnSaveShinyOverride;
}

// toggles edit mode
export function toggleEditMode() {
const quartoVisualEditor = VisualEditorProvider.activeEditor();
if (quartoVisualEditor !== undefined) {
vscode.commands.executeCommand('quarto.editInSourceMode');
} else {
vscode.commands.executeCommand('quarto.editInVisualMode');
}
}

// toggles render on save override
export function toggleRenderOnSaveOverride() {
// toggle the render on save override based on the editor type (quarto or quarto-shiny)
if (quartoEditorType === 'quarto') {
// if this is the first override, read the quarto.render.renderOnSave configuration
if (renderOnSaveOverride === undefined) {
renderOnSaveOverride = readRenderOnSaveConfiguration();
}

// toggle the render on save override
renderOnSaveOverride = !renderOnSaveOverride;
vscode.commands.executeCommand<boolean>('setContext', 'quarto.editor.renderOnSave', renderOnSaveOverride);
} else if (quartoEditorType === 'quarto-shiny') {
// if this is the first override, read the quarto.render.renderOnSaveShiny configuration
if (renderOnSaveShinyOverride === undefined) {
renderOnSaveShinyOverride = readRenderOnSaveShinyConfiguration();
}

// toggle the render on save override
renderOnSaveShinyOverride = !renderOnSaveShinyOverride;
vscode.commands.executeCommand<boolean>('setContext', 'quarto.editor.renderOnSaveShiny', renderOnSaveShinyOverride);
}
}

// sets editor context keys
function setEditorContextKeys(activeTextEditor: vscode.TextEditor | undefined, engine: MarkdownEngine) {
// if a Quarto doc is active, set the editor context keys
if (isQuartoDoc(activeTextEditor?.document)) {
// set the quarto.editor.type context key
quartoEditorType = !isQuartoShinyDoc(engine, activeTextEditor?.document)
? 'quarto'
: 'quarto-shiny';
vscode.commands.executeCommand<string>(
'setContext',
'quarto.editor.type',
quartoEditorType
);

// set the quarto.editor.renderOnSave context key
const renderOnSave = renderOnSaveOverride === undefined
? readRenderOnSaveConfiguration()
: renderOnSaveOverride;
vscode.commands.executeCommand<string>(
'setContext',
'quarto.editor.renderOnSave',
renderOnSave
);

// set the quarto.editor.renderOnSaveShiny context key
const renderOnSaveShiny = renderOnSaveShinyOverride === undefined ?
readRenderOnSaveShinyConfiguration() :
renderOnSaveShinyOverride;
vscode.commands.executeCommand<string>(
'setContext',
'quarto.editor.renderOnSaveShiny',
renderOnSaveShiny
);
}
}

function setLanguageContextKeys(activeTextEditor: vscode.TextEditor | undefined, engine: MarkdownEngine) {
if (!activeTextEditor || !isQuartoDoc(activeTextEditor.document)) {
return;
}

// expose main language for use in keybindings, etc
const tokens = engine.parse(editor.document);
const tokens = engine.parse(activeTextEditor.document);
const language = mainLanguage(tokens);
vscode.commands.executeCommand(
'setContext',
'quarto.document.languageId',
language?.ids[0]);
}

// reads the quarto.render.renderOnSave configuration.
function readRenderOnSaveConfiguration() {
return workspace.getConfiguration("quarto").get<boolean>("render.renderOnSave", false);
}

// reads the quarto.render.renderOnSaveShiny configuration.
function readRenderOnSaveShinyConfiguration() {
return workspace.getConfiguration("quarto").get<boolean>("render.renderOnSaveShiny", true);
}
11 changes: 9 additions & 2 deletions apps/vscode/src/providers/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/

import path, { extname, win32 } from "path";
import { determineMode } from "./toggle"
import { determineMode } from "./toggle";
import debounce from "lodash.debounce";

import {
Expand Down Expand Up @@ -62,6 +62,8 @@ import { JsonRpcRequestTransport } from "core";
import {
editInSourceModeCommand,
editInVisualModeCommand,
toggleEditModeCommand,
toggleRenderOnSaveCommand,
reopenEditorInSourceMode
} from "./toggle";
import { ExtensionHost } from "../../host";
Expand All @@ -84,7 +86,12 @@ export function activateEditor(
context.subscriptions.push(VisualEditorProvider.register(context, host, quartoContext, lspClient, engine));

// return commands
return [editInVisualModeCommand(), editInSourceModeCommand()];
return [
editInVisualModeCommand(),
editInSourceModeCommand(),
toggleEditModeCommand(),
toggleRenderOnSaveCommand()
];
}


Expand Down
19 changes: 19 additions & 0 deletions apps/vscode/src/providers/editor/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { isQuartoDoc, kQuartoLanguageId } from "../../core/doc";
import { VisualEditorProvider } from "./editor";
import { Uri } from "vscode";
import { hasHooks } from "../../host/hooks";
import { toggleEditMode, toggleRenderOnSaveOverride } from "../context-keys";

export function determineMode(text: string, uri: Uri): string | undefined {
let editorOpener = undefined;
Expand Down Expand Up @@ -97,6 +98,24 @@ export function editInSourceModeCommand(): Command {
};
}

export function toggleEditModeCommand(): Command {
return {
id: 'quarto.toggleEditMode',
execute() {
toggleEditMode();
}
};
}

export function toggleRenderOnSaveCommand(): Command {
return {
id: 'quarto.toggleRenderOnSave',
execute() {
toggleRenderOnSaveOverride();
}
};
}

export async function reopenEditorInVisualMode(
document: TextDocument,
viewColumn?: ViewColumn
Expand Down
12 changes: 5 additions & 7 deletions apps/vscode/src/providers/preview/preview-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { isNotebook } from "../../core/doc";
import { MarkdownEngine } from "../../markdown/engine";
import { documentFrontMatter } from "../../markdown/document";
import { isKnitrDocument } from "../../host/executors";
import { getRenderOnSave, getRenderOnSaveShiny } from "../context-keys";


export function isQuartoShinyDoc(
Expand Down Expand Up @@ -123,13 +124,10 @@ export async function renderOnSave(engine: MarkdownEngine, document: TextDocumen
}
}

// finally, consult vs code settings
const config = workspace.getConfiguration("quarto");
const render = isQuartoShinyDoc(engine, document)
? config.get<boolean>("render.renderOnSaveShiny", true)
: config.get<boolean>("render.renderOnSave", false);

return render;
// finally, consult configuration
return !isQuartoShinyDoc(engine, document)
? getRenderOnSave()
: getRenderOnSaveShiny();
}

export function haveNotebookSaveEvents() {
Expand Down