diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index 6cde6584..ed24c32c 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.121.0 (unreleased) +- Add new controls for Positron editor action bar (). + ## 1.120.0 (Release on 2025-04-07) - Fix issue where format on save could overwrite the contents of a document with incorrect results (). diff --git a/apps/vscode/package.json b/apps/vscode/package.json index c7d86c75..eb5bb263 100644 --- a/apps/vscode/package.json +++ b/apps/vscode/package.json @@ -253,7 +253,11 @@ "command": "quarto.preview", "title": "Preview", "icon": "$(preview)", - "category": "Quarto" + "category": "Quarto", + "actionBarOptions": { + "controlType": "button", + "displayTitle": true + } }, { "command": "quarto.previewScript", @@ -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", @@ -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", @@ -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.", diff --git a/apps/vscode/src/main.ts b/apps/vscode/src/main.ts index 160a149c..a365a522 100644 --- a/apps/vscode/src/main.ts +++ b/apps/vscode/src/main.ts @@ -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)); diff --git a/apps/vscode/src/providers/context-keys.ts b/apps/vscode/src/providers/context-keys.ts index 7b7dca6b..0e36b7be 100644 --- a/apps/vscode/src/providers/context-keys.ts +++ b/apps/vscode/src/providers/context-keys.ts @@ -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('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('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( + 'setContext', + 'quarto.editor.type', + quartoEditorType + ); + + // set the quarto.editor.renderOnSave context key + const renderOnSave = renderOnSaveOverride === undefined + ? readRenderOnSaveConfiguration() + : renderOnSaveOverride; + vscode.commands.executeCommand( + 'setContext', + 'quarto.editor.renderOnSave', + renderOnSave + ); + + // set the quarto.editor.renderOnSaveShiny context key + const renderOnSaveShiny = renderOnSaveShinyOverride === undefined ? + readRenderOnSaveShinyConfiguration() : + renderOnSaveShinyOverride; + vscode.commands.executeCommand( + '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("render.renderOnSave", false); +} + +// reads the quarto.render.renderOnSaveShiny configuration. +function readRenderOnSaveShinyConfiguration() { + return workspace.getConfiguration("quarto").get("render.renderOnSaveShiny", true); +} diff --git a/apps/vscode/src/providers/editor/editor.ts b/apps/vscode/src/providers/editor/editor.ts index 3123ff82..8ff5c300 100644 --- a/apps/vscode/src/providers/editor/editor.ts +++ b/apps/vscode/src/providers/editor/editor.ts @@ -14,7 +14,7 @@ */ import path, { extname, win32 } from "path"; -import { determineMode } from "./toggle" +import { determineMode } from "./toggle"; import debounce from "lodash.debounce"; import { @@ -62,6 +62,8 @@ import { JsonRpcRequestTransport } from "core"; import { editInSourceModeCommand, editInVisualModeCommand, + toggleEditModeCommand, + toggleRenderOnSaveCommand, reopenEditorInSourceMode } from "./toggle"; import { ExtensionHost } from "../../host"; @@ -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() + ]; } diff --git a/apps/vscode/src/providers/editor/toggle.ts b/apps/vscode/src/providers/editor/toggle.ts index b0e5bb34..cbb426dd 100644 --- a/apps/vscode/src/providers/editor/toggle.ts +++ b/apps/vscode/src/providers/editor/toggle.ts @@ -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; @@ -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 diff --git a/apps/vscode/src/providers/preview/preview-util.ts b/apps/vscode/src/providers/preview/preview-util.ts index 491919cc..9fff0edf 100644 --- a/apps/vscode/src/providers/preview/preview-util.ts +++ b/apps/vscode/src/providers/preview/preview-util.ts @@ -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( @@ -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("render.renderOnSaveShiny", true) - : config.get("render.renderOnSave", false); - - return render; + // finally, consult configuration + return !isQuartoShinyDoc(engine, document) + ? getRenderOnSave() + : getRenderOnSaveShiny(); } export function haveNotebookSaveEvents() {