diff --git a/apps/vscode/src/core/doc.ts b/apps/vscode/src/core/doc.ts index 1ad3cf724..a28affaee 100644 --- a/apps/vscode/src/core/doc.ts +++ b/apps/vscode/src/core/doc.ts @@ -18,14 +18,9 @@ import fs from "node:fs"; import * as vscode from "vscode"; import { Uri } from "vscode"; -import { revealSlideIndex } from "../markdown/reveal"; -import { VisualEditorProvider } from "../providers/editor/editor"; import { extname } from "./path"; -import { MarkdownEngine } from "../markdown/engine"; -import { QuartoContext, projectDirForDocument } from "quarto-core"; -import { TextDocument } from "vscode"; +import { projectDirForDocument } from "quarto-core"; import { workspace } from "vscode"; -import { NotebookDocument } from "vscode"; import { isJupyterPercentScript, isKnitrSpinScript } from "core-node"; export const kQuartoLanguageId = "quarto"; @@ -58,18 +53,19 @@ function isLanguageDoc(languageId: string, doc?: vscode.TextDocument) { return !!doc && doc.languageId === languageId; } -export function isNotebook(doc?: vscode.TextDocument) { - return !!doc && isNotebookUri(doc.uri); +function isNotebook(doc?: vscode.TextDocument | vscode.NotebookDocument): doc is vscode.NotebookDocument { + return !!doc && 'notebookType' in doc; } -export function isNotebookUri(uri: Uri) { +function isIpynbUri(uri: Uri) { return extname(uri.fsPath).toLowerCase() === ".ipynb"; } - -export function canPreviewDoc(doc?: TextDocument) { +export function canPreviewDoc(doc?: vscode.TextDocument | vscode.NotebookDocument) { if (doc) { - if (isQuartoDoc(doc) || isNotebook(doc)) { + if (isNotebook(doc)) { + return isIpynbUri(doc.uri); + } else if (isQuartoDoc(doc)) { return true; } else if (validatateQuartoCanRender(doc)) { return true; @@ -159,132 +155,6 @@ export function getWholeRange(doc: vscode.TextDocument) { return new vscode.Range(begin, end); } -export function preserveEditorFocus(editor?: QuartoEditor) { - // focus the editor (sometimes the terminal steals focus) - editor = - editor || - (vscode.window.activeTextEditor - ? quartoEditor(vscode.window.activeTextEditor) - : undefined); - if (editor) { - if (!isNotebook(editor?.document)) { - setTimeout(() => { - if (editor) { - editor.activate(); - } - }, 200); - } - } else { - // see if there is a visual editor we should be preserving focus for - const visualEditor = VisualEditorProvider.activeEditor(); - if (visualEditor) { - setTimeout(async () => { - if (!(await visualEditor.hasFocus())) { - await visualEditor.activate(); - } - }, 200); - } - } -} - -export interface QuartoEditor { - document: vscode.TextDocument; - activate: () => Promise; - slideIndex: () => Promise; - viewColumn?: vscode.ViewColumn; - textEditor?: vscode.TextEditor; - notebook?: vscode.NotebookDocument; -} - -export function findQuartoEditor( - engine: MarkdownEngine, - context: QuartoContext, - filter: (doc: vscode.TextDocument) => boolean, - includeVisible = true -): QuartoEditor | undefined { - // first check for an active visual editor - const activeVisualEditor = VisualEditorProvider.activeEditor(); - if (activeVisualEditor && filter(activeVisualEditor.document)) { - return activeVisualEditor; - } - - // then check for active notebook editor - const notebookEditor = (vscode.window as any).activeNotebookEditor as - | vscode.NotebookEditor - | undefined; - if (notebookEditor) { - const notebookDocument = (notebookEditor as any).notebook as - | vscode.NotebookDocument - | undefined; - if (notebookDocument) { - const textEditor = vscode.window.visibleTextEditors.find((editor) => { - return editor.document.uri.fsPath.includes(notebookDocument.uri.fsPath); - }); - if (textEditor && filter(textEditor.document)) { - return quartoEditor(textEditor, engine, context, notebookDocument); - } - } - } - - // active text editor - const textEditor = vscode.window.activeTextEditor; - if (textEditor && filter(textEditor.document)) { - return quartoEditor(textEditor, engine, context); - // check visible text editors - } else if (includeVisible) { - // visible visual editor (sometime it loses track of 'active' so we need to use 'visible') - const visibleVisualEditor = VisualEditorProvider.activeEditor(true); - if (visibleVisualEditor && filter(visibleVisualEditor.document)) { - return visibleVisualEditor; - } - - // visible text editors - const visibleEditor = vscode.window.visibleTextEditors.find((editor) => - filter(editor.document) - ); - if (visibleEditor) { - return quartoEditor(visibleEditor, engine, context); - } else { - return undefined; - } - } else { - return undefined; - } -} - -export function quartoEditor( - editor: vscode.TextEditor, - engine?: MarkdownEngine, - context?: QuartoContext, - notebook?: NotebookDocument -) { - return { - document: editor.document, - activate: async () => { - await vscode.window.showTextDocument( - editor.document, - editor.viewColumn, - false - ); - }, - slideIndex: async () => { - if (engine && context) { - return await revealSlideIndex( - editor.selection.active, - editor.document, - engine, - context - ); - } else { - return 0; - } - }, - viewColumn: editor.viewColumn, - textEditor: editor, - notebook, - }; -} - async function tryResolveUriToQuartoDoc( resource: vscode.Uri ): Promise { diff --git a/apps/vscode/src/core/quartoEditor.ts b/apps/vscode/src/core/quartoEditor.ts new file mode 100644 index 000000000..5509d68f8 --- /dev/null +++ b/apps/vscode/src/core/quartoEditor.ts @@ -0,0 +1,180 @@ +import * as vscode from "vscode"; +import { QuartoContext } from "quarto-core"; +import { MarkdownEngine } from "../markdown/engine"; +import { revealSlideIndex } from "../markdown/reveal"; +import { QuartoVisualEditor, VisualEditorProvider } from "../providers/editor/editor"; +import { notebookFrontMatterYaml } from "../markdown/notebook"; +import { documentFrontMatterYaml } from "../markdown/document"; + +export type QuartoEditor = QuartoTextEditor | QuartoNotebookEditor | QuartoVisualEditor; + +export interface QuartoEditorBase { + // TODO: Can we remove `document: TextDocument` from QuartoNotebookEditor? + // Many things use it for `uri` (easy) and `getText()` (harder). + document: vscode.TextDocument; + activate: () => Promise; + + /** + * Get the slide index for the current cursor position. + * + * This method is undefined for editors that don't yet support + * slide parsing e.g. `QuartoNotebookEditor`. + */ + slideIndex?: () => Promise; + + selectAndRevealRange: (range: vscode.Range) => void; + + preserveEditorFocus(): void; + + frontMatterYaml(engine: MarkdownEngine): string; +} + +export interface QuartoTextEditor extends QuartoEditorBase { + type: 'text'; + textEditor: vscode.TextEditor; +} + +export interface QuartoNotebookEditor extends QuartoEditorBase { + type: 'notebook'; + notebookEditor: vscode.NotebookEditor; +} + +export function isQuartoNotebookEditor(editor: QuartoEditor): editor is QuartoNotebookEditor { + return editor.type === 'notebook'; +} + +export function isQuartoTextEditor(editor: QuartoEditor): editor is QuartoTextEditor { + return editor.type === 'text'; +} + +export function isQuartoVisualEditor(editor: QuartoEditor): editor is QuartoVisualEditor { + return editor.type === 'visual'; +} + +export function findQuartoEditor( + engine: MarkdownEngine, + context: QuartoContext, + filter: (doc: vscode.TextDocument | vscode.NotebookDocument) => boolean = () => true, +): QuartoEditor | undefined { + const activeEditor = activeQuartoEditor(filter, engine, context); + if (activeEditor) { + return activeEditor; + } + + // visible visual editor (sometime it loses track of 'active' so we need to use 'visible') + const visibleVisualEditor = VisualEditorProvider.activeEditor(true); + if (visibleVisualEditor && filter(visibleVisualEditor.document)) { + return visibleVisualEditor; + } + + // visible text editors + const visibleEditor = vscode.window.visibleTextEditors.find((editor) => filter(editor.document)); + if (visibleEditor) { + return quartoTextEditor(visibleEditor, engine, context); + } + + return undefined; +} + +export function activeQuartoEditor( + filter: (doc: vscode.TextDocument | vscode.NotebookDocument) => boolean = () => true, + engine?: MarkdownEngine, + context?: QuartoContext +): QuartoEditor | undefined { + // first check for an active visual editor + const activeVisualEditor = VisualEditorProvider.activeEditor(); + if (activeVisualEditor && filter(activeVisualEditor.document)) { + return activeVisualEditor; + } + + // then check for active notebook editor + const notebookEditor = vscode.window.activeNotebookEditor; + if (notebookEditor && filter(notebookEditor.notebook)) { + return quartoNotebookEditor(notebookEditor); + } + + // active text editor + const textEditor = vscode.window.activeTextEditor; + if (textEditor && filter(textEditor.document)) { + return quartoTextEditor(textEditor, engine, context); + } + + return undefined; +} + +export function quartoTextEditor( + editor: vscode.TextEditor, + engine?: MarkdownEngine, + context?: QuartoContext): QuartoTextEditor { + const activate = async () => { + await vscode.window.showTextDocument( + editor.document, + editor.viewColumn, + false + ); + }; + + return { + type: 'text', + document: editor.document, + activate, + slideIndex: async () => { + if (engine && context) { + return await revealSlideIndex( + editor.selection.active, + editor.document, + engine, + context + ); + } else { + return 0; + } + }, + selectAndRevealRange: (range: vscode.Range) => { + // if the current selection is outside of the error region then + // navigate to the top of the error region + if ( + editor.selection.active.isBefore(range.start) || + editor.selection.active.isAfter(range.end) + ) { + editor.selection = new vscode.Selection(range.start, range.start); + editor.revealRange( + range, + vscode.TextEditorRevealType.InCenterIfOutsideViewport + ); + } + }, + preserveEditorFocus: () => { + setTimeout(() => { + activate(); + }, 200); + }, + frontMatterYaml: (engine) => documentFrontMatterYaml(engine, editor.document), + textEditor: editor, + }; +} + +function quartoNotebookEditor( + notebookEditor: vscode.NotebookEditor, +): QuartoNotebookEditor { + // TODO: Why is cellAt always defined?... + const firstCellDoc = notebookEditor.notebook.cellAt(0)?.document; + return { + type: 'notebook', + document: firstCellDoc, + activate: async () => { + await vscode.window.showNotebookDocument( + notebookEditor.notebook, + { preserveFocus: false } + ); + }, + selectAndRevealRange: () => { + // Not implemented yet. + }, + preserveEditorFocus: () => { + // Not implemented yet. + }, + frontMatterYaml: () => notebookFrontMatterYaml(notebookEditor.notebook), + notebookEditor, + }; +} diff --git a/apps/vscode/src/lsp/client.ts b/apps/vscode/src/lsp/client.ts index e9b26ba16..6737fb3bd 100644 --- a/apps/vscode/src/lsp/client.ts +++ b/apps/vscode/src/lsp/client.ts @@ -538,21 +538,3 @@ function unadjustSymbolRanges( }; }); } - -/** - * Creates a diagnostic handler middleware that filters out diagnostics from virtual documents - * - * @returns A handler function for the middleware - */ -export function createDiagnosticFilter() { - return (uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => { - // If this is not a virtual document, pass through all diagnostics - if (!isVirtualDoc(uri)) { - next(uri, diagnostics); - return; - } - - // For virtual documents, filter out all diagnostics - next(uri, []); - }; -} diff --git a/apps/vscode/src/markdown/notebook.ts b/apps/vscode/src/markdown/notebook.ts new file mode 100644 index 000000000..4ef915bc8 --- /dev/null +++ b/apps/vscode/src/markdown/notebook.ts @@ -0,0 +1,5 @@ +import * as vscode from "vscode"; + +export function notebookFrontMatterYaml(notebook: vscode.NotebookDocument): string { + return notebook.cellAt(0)?.document.getText() || ""; +} diff --git a/apps/vscode/src/providers/assist/commands.ts b/apps/vscode/src/providers/assist/commands.ts index 07ed689bc..fc07d5516 100644 --- a/apps/vscode/src/providers/assist/commands.ts +++ b/apps/vscode/src/providers/assist/commands.ts @@ -15,7 +15,8 @@ import { Position, Selection, window, commands } from "vscode"; import { Command } from "../../core/command"; -import { isQuartoDoc, preserveEditorFocus } from "../../core/doc"; +import { isQuartoDoc } from "../../core/doc"; +import { activeQuartoEditor } from "../../core/quartoEditor"; import { MarkdownEngine } from "../../markdown/engine"; import { QuartoAssistViewProvider } from "./webview"; import { CodeViewCellContext } from "editor-types"; @@ -80,7 +81,7 @@ export class CodeViewAssistCommand implements Command { function activateAssistPanel(provider: QuartoAssistViewProvider) { // attempt to activate (if we fail to the view has been closed so // recreate it by calling focus) - preserveEditorFocus(); + activeQuartoEditor()?.preserveEditorFocus(); if (!provider.activate()) { commands.executeCommand("quarto-assist.focus"); } diff --git a/apps/vscode/src/providers/convert.ts b/apps/vscode/src/providers/convert.ts index e59c46fd1..87fb036d9 100644 --- a/apps/vscode/src/providers/convert.ts +++ b/apps/vscode/src/providers/convert.ts @@ -20,7 +20,8 @@ import { commands, LogOutputChannel, Uri, window } from "vscode"; import { QuartoContext } from "quarto-core"; import { Command } from "../core/command"; -import { canPreviewDoc, findQuartoEditor } from "../core/doc"; +import { canPreviewDoc } from "../core/doc"; +import { findQuartoEditor } from "../core/quartoEditor"; import { promptForQuartoInstallation } from "../core/quarto"; import { MarkdownEngine } from "../markdown/engine"; diff --git a/apps/vscode/src/providers/editor/editor.ts b/apps/vscode/src/providers/editor/editor.ts index 83c7af1b4..8974398c5 100644 --- a/apps/vscode/src/providers/editor/editor.ts +++ b/apps/vscode/src/providers/editor/editor.ts @@ -13,7 +13,7 @@ * */ -import path, { extname, win32 } from "path"; +import path, { extname } from "path"; import { determineMode } from "./toggle"; import debounce from "lodash.debounce"; @@ -46,7 +46,8 @@ import { CodeViewActiveBlockContext, CodeViewSelectionAction, HostContext, NavLo import { getNonce } from "../../core/nonce"; import { isWindows } from "../../core/platform"; -import { isQuartoDoc, QuartoEditor } from "../../core/doc"; +import { isQuartoDoc } from "../../core/doc"; +import { QuartoEditorBase } from "../../core/quartoEditor"; import { Command } from "../../core/command"; import { visualEditorClient, visualEditorServer } from "./connection"; @@ -67,13 +68,16 @@ import { } from "./toggle"; import { ExtensionHost } from "../../host"; import { TabInputCustom } from "vscode"; +import { documentFrontMatterYaml } from "../../markdown/document"; const kVisualModeConfirmed = "visualModeConfirmed"; -export interface QuartoVisualEditor extends QuartoEditor { +export interface QuartoVisualEditor extends QuartoEditorBase { + type: 'visual'; hasFocus(): Promise; getActiveBlockContext(): Promise; setBlockSelection(context: CodeViewActiveBlockContext, action: CodeViewSelectionAction): Promise; + viewColumn: ViewColumn | undefined; } export function activateEditor( @@ -286,14 +290,19 @@ export class VisualEditorProvider implements CustomTextEditorProvider { public static activeEditor(includeVisible?: boolean): QuartoVisualEditor | undefined { const editor = this.visualEditors.activeEditor(includeVisible); if (editor) { + const hasFocus = async () => { + return await editor.editor.isFocused(); + }; + + const activate = async () => { + activateVisualEditor(editor); + }; + return { + type: 'visual', document: editor.document, - hasFocus: async () => { - return await editor.editor.isFocused(); - }, - activate: async () => { - activateVisualEditor(editor); - }, + hasFocus, + activate, slideIndex: async () => { return await editor.editor.getSlideIndex(); }, @@ -303,6 +312,17 @@ export class VisualEditorProvider implements CustomTextEditorProvider { setBlockSelection: async (context, action) => { await editor.editor.setBlockSelection(context, action); }, + selectAndRevealRange: (range: Range) => { + // Not implemented yet. + }, + preserveEditorFocus: () => { + setTimeout(async () => { + if (!(await hasFocus())) { + await activate(); + } + }, 200); + }, + frontMatterYaml: (engine) => documentFrontMatterYaml(engine, editor.document), viewColumn: editor.webviewPanel.viewColumn }; } else { diff --git a/apps/vscode/src/providers/preview/commands.ts b/apps/vscode/src/providers/preview/commands.ts index 5117263b3..43c24bb23 100644 --- a/apps/vscode/src/providers/preview/commands.ts +++ b/apps/vscode/src/providers/preview/commands.ts @@ -16,7 +16,7 @@ import * as path from "path"; import * as fs from "fs"; -import { TextDocument, window, Uri, workspace, commands, QuickPickItem } from "vscode"; +import { TextDocument, window, Uri, workspace, commands } from "vscode"; import { QuartoContext, QuartoFormatInfo, quartoDocumentFormats } from "quarto-core"; import { Command } from "../../core/command"; @@ -25,9 +25,9 @@ import { previewDoc, } from "./preview"; import { MarkdownEngine } from "../../markdown/engine"; -import { canPreviewDoc, findQuartoEditor, isNotebook } from "../../core/doc"; +import { canPreviewDoc } from "../../core/doc"; +import { findQuartoEditor } from "../../core/quartoEditor"; import { renderOnSave } from "./preview-util"; -import { documentFrontMatterYaml } from "../../markdown/document"; import { FormatQuickPickItem, RenderCommand } from "../render"; import { QuickPickItemKind } from "vscode"; @@ -56,7 +56,7 @@ abstract class PreviewDocumentCommandBase extends RenderCommand { protected async renderFormat(format?: string | null, onShow?: () => void) { const targetEditor = findQuartoEditor(this.engine_, this.quartoContext(), canPreviewDoc); if (targetEditor) { - const hasRenderOnSave = await renderOnSave(this.engine_, targetEditor.document); + const hasRenderOnSave = await renderOnSave(this.engine_, targetEditor); const render = !hasRenderOnSave || (hasRenderOnSave && format) || @@ -64,9 +64,7 @@ abstract class PreviewDocumentCommandBase extends RenderCommand { if (render) { if (format === kChooseFormat) { - const frontMatter = targetEditor.notebook - ? targetEditor.notebook.cellAt(0)?.document.getText() || "" - : documentFrontMatterYaml(this.engine_, targetEditor.document); + const frontMatter = targetEditor.frontMatterYaml(this.engine_); const formats = quartoDocumentFormats(this.quartoContext(), targetEditor.document.uri.fsPath, frontMatter); if (formats) { @@ -110,9 +108,7 @@ abstract class PreviewDocumentCommandBase extends RenderCommand { } } else { // show the editor - if (!isNotebook(targetEditor.document)) { - await targetEditor.activate(); - } + await targetEditor.activate(); // save (will trigger render b/c renderOnSave is enabled) await commands.executeCommand("workbench.action.files.save"); diff --git a/apps/vscode/src/providers/preview/preview-util.ts b/apps/vscode/src/providers/preview/preview-util.ts index c1b43b791..9a2188af3 100644 --- a/apps/vscode/src/providers/preview/preview-util.ts +++ b/apps/vscode/src/providers/preview/preview-util.ts @@ -13,17 +13,14 @@ * */ -import semver from "semver"; - -import vscode from "vscode"; -import { TextDocument, Uri, workspace } from "vscode"; +import { TextDocument } from "vscode"; import { projectDirForDocument, metadataFilesForDocument, yamlFromMetadataFile, } from "quarto-core"; -import { isNotebook } from "../../core/doc"; +import { isQuartoNotebookEditor, QuartoEditor } from "../../core/quartoEditor"; import { MarkdownEngine } from "../../markdown/engine"; import { documentFrontMatter } from "../../markdown/document"; @@ -60,19 +57,14 @@ export function isQuartoShinyKnitrDoc( } -export async function renderOnSave(engine: MarkdownEngine, document: TextDocument) { - // if its a notebook and we don't have a save hook for notebooks then don't - // allow renderOnSave (b/c we can't detect the saves) - if (isNotebook(document) && !haveNotebookSaveEvents()) { - return false; - } - +export async function renderOnSave(engine: MarkdownEngine, editor: QuartoEditor) { // notebooks automatically get renderOnSave - if (isNotebook(document)) { + if (isQuartoNotebookEditor(editor)) { return true; } // first look for document level editor setting + const { document } = editor; const docYaml = documentFrontMatter(engine, document); const docSetting = readRenderOnSave(docYaml); if (docSetting !== undefined) { @@ -102,13 +94,6 @@ export async function renderOnSave(engine: MarkdownEngine, document: TextDocumen : getRenderOnSaveShiny(); } -export function haveNotebookSaveEvents() { - return ( - semver.gte(vscode.version, "1.67.0") && - !!(workspace as any).onDidSaveNotebookDocument - ); -} - function readRenderOnSave(yaml: Record) { if (typeof yaml["editor"] === "object") { const yamlObj = yaml["editor"] as Record; diff --git a/apps/vscode/src/providers/preview/preview.ts b/apps/vscode/src/providers/preview/preview.ts index 5a36daf4b..e0376a63e 100644 --- a/apps/vscode/src/providers/preview/preview.ts +++ b/apps/vscode/src/providers/preview/preview.ts @@ -26,13 +26,11 @@ import vscode, { MessageItem, Terminal, TextDocument, - Selection, Range, Uri, ViewColumn, window, Position, - TextEditorRevealType, NotebookDocument, ProgressLocation, CancellationToken, @@ -47,14 +45,15 @@ import { previewCommands } from "./commands"; import { Command } from "../../core/command"; import { canPreviewDoc, - findQuartoEditor, - isNotebook, - preserveEditorFocus, previewDirForDocument, quartoCanRenderScript, - QuartoEditor, validatateQuartoCanRender, } from "../../core/doc"; +import { + findQuartoEditor, + isQuartoNotebookEditor, + QuartoEditor +} from "../../core/quartoEditor"; import { PreviewOutputSink } from "./preview-output"; import { isHtmlContent, isTextContent, isPdfContent } from "core-node"; @@ -71,7 +70,6 @@ import { QuartoPreviewWebviewManager, } from "./preview-webview"; import { - haveNotebookSaveEvents, isQuartoShinyDoc, isQuartoShinyKnitrDoc, renderOnSave, @@ -120,7 +118,7 @@ export function activatePreview( if (editor) { if ( canPreviewDoc(editor.document) && - (await renderOnSave(engine, editor.document)) && + (await renderOnSave(engine, editor)) && (await previewManager.isPreviewRunningForDoc(editor.document)) ) { await previewDoc(editor, undefined, true, engine, quartoContext); @@ -132,17 +130,13 @@ export function activatePreview( await onSave(doc.uri); }) ); - // we use 1.66 as our minimum version (and type import) but - // onDidSaveNotebookDocument was introduced in 1.67 - if (haveNotebookSaveEvents()) { - context.subscriptions.push( - (vscode.workspace as any).onDidSaveNotebookDocument( - async (notebook: NotebookDocument) => { - await onSave(notebook.uri); - } - ) - ); - } + context.subscriptions.push( + vscode.workspace.onDidSaveNotebookDocument( + async (notebook: NotebookDocument) => { + await onSave(notebook.uri); + } + ) + ); // monitor active document to see whether it can be rendered by quarto const updateRenderDocActive = (editor?: vscode.TextEditor) => { @@ -194,9 +188,7 @@ export async function previewDoc( ) { // set the slide index from the source editor so we can // navigate to it in the preview frame - const slideIndex = !isNotebook(editor.document) - ? await editor.slideIndex() - : undefined; + const slideIndex = editor.slideIndex && await editor.slideIndex(); previewManager.setSlideIndex(slideIndex); // set onShow if provided @@ -207,9 +199,7 @@ export async function previewDoc( // if this wasn't a renderOnSave then activate the editor and save if (!renderOnSave) { // activate the editor - if (!isNotebook(editor.document)) { - await editor.activate(); - } + await editor.activate(); await commands.executeCommand("workbench.action.files.save"); if (editor.document.isDirty) { @@ -226,7 +216,7 @@ export async function previewDoc( if (previewEditor) { // error if we didn't save using a valid quarto extension if ( - !isNotebook(previewEditor.document) && + !isQuartoNotebookEditor(previewEditor) && !validatateQuartoCanRender(previewEditor.document) ) { window.showErrorMessage("Unsupported File Extension", { @@ -240,25 +230,18 @@ export async function previewDoc( // run the preview await previewManager.preview( - previewEditor.document.uri, - previewEditor.document, + previewEditor, format, slideIndex ); // focus the editor (sometimes the terminal steals focus) if (!renderOnSave) { - if (!isNotebook(previewEditor.document)) { - await previewEditor.activate(); - } + await previewEditor.activate(); } } } -export async function previewProject(target: Uri, format?: string) { - await previewManager.preview(target, undefined, format); -} - class PreviewManager { constructor( context: ExtensionContext, @@ -302,12 +285,12 @@ class PreviewManager { } public async preview( - uri: Uri, - doc: TextDocument | undefined, + editor: QuartoEditor, format: string | null | undefined, slideIndex?: number ) { // resolve format if we need to + const uri = editor.document.uri; if (format === undefined) { format = this.previewFormats_.get(uri.fsPath) || null; } else { @@ -317,8 +300,9 @@ class PreviewManager { this.progressDismiss(); this.progressCancellationToken_ = undefined; this.previewOutput_ = ""; - this.previewDoc_ = doc; + this.previewEditor_ = editor; const previewEnv = await this.previewEnvManager_.previewEnv(uri); + const doc = editor?.document; if (doc && (await this.canReuseRunningPreview(doc, previewEnv))) { try { const response = await this.previewRenderRequest(doc, format); @@ -551,7 +535,7 @@ class PreviewManager { if (browseMatch) { // earlier versions of quarto serve didn't print out vscode urls // correctly so we compenstate for that here - if (isQuartoShinyDoc(this.engine_, this.previewDoc_)) { + if (isQuartoShinyDoc(this.engine_, this.previewEditor_?.document)) { this.previewUrl_ = vsCodeWebUrl(browseMatch[2]); } else { this.previewUrl_ = browseMatch[2]; @@ -589,7 +573,7 @@ class PreviewManager { } } } - this.progressDismiss() + this.progressDismiss(); } private progressShow(uri: Uri) { @@ -620,16 +604,15 @@ class PreviewManager { } private async detectErrorNavigation(output: string) { - // bail if this is a notebook or we don't have a previewDoc - if (!this.previewDoc_ || isNotebook(this.previewDoc_)) { + // bail if this is a notebook or we don't have a previewEditor + if (!this.previewEditor_ || isQuartoNotebookEditor(this.previewEditor_)) { return; } - // normalize output = normalizeNewlines(output); // run all of our tests - const previewFile = this.previewDoc_.uri.fsPath; + const previewFile = this.previewEditor_.document.uri.fsPath; const previewDir = this.previewDir_ || this.targetDir(); const errorLoc = yamlErrorLocation(output, previewFile, previewDir) || @@ -651,24 +634,13 @@ class PreviewManager { (doc) => doc.uri.fsPath === fileUri.fsPath ); if (editor) { - if (editor.textEditor) { - // if the current selection is outside of the error region then - // navigate to the top of the error region - const errPos = new Position(errorLoc.lineBegin - 1, 0); - const errEndPos = new Position(errorLoc.lineEnd - 1, 0); - const textEditor = editor.textEditor; - if ( - textEditor.selection.active.isBefore(errPos) || - textEditor.selection.active.isAfter(errEndPos) - ) { - textEditor.selection = new Selection(errPos, errPos); - textEditor.revealRange( - new Range(errPos, errPos), - TextEditorRevealType.InCenterIfOutsideViewport - ); - } - } - preserveEditorFocus(editor); + // if the current selection is outside of the error region then + // navigate to the top of the error region + const errPos = new Position(errorLoc.lineBegin - 1, 0); + const errEndPos = new Position(errorLoc.lineEnd - 1, 0); + const errRange = new Range(errPos, errEndPos); + editor.selectAndRevealRange(errRange); + editor.preserveEditorFocus(); } } } @@ -783,7 +755,7 @@ class PreviewManager { } private previewOutput_ = ""; - private previewDoc_: TextDocument | undefined; + private previewEditor_: QuartoEditor | undefined; private previewEnv_: PreviewEnv | undefined; private previewTarget_: Uri | undefined; private previewUrl_: string | undefined; diff --git a/apps/vscode/src/providers/render.ts b/apps/vscode/src/providers/render.ts index 3b944d622..0f7720274 100644 --- a/apps/vscode/src/providers/render.ts +++ b/apps/vscode/src/providers/render.ts @@ -23,11 +23,11 @@ import { Command } from "../core/command"; import { MarkdownEngine } from "../markdown/engine"; import { promptForQuartoInstallation } from "../core/quarto"; -import { QuartoEditor, canPreviewDoc, findQuartoEditor, isNotebook } from "../core/doc"; +import { canPreviewDoc } from "../core/doc"; +import { QuartoEditor, findQuartoEditor } from "../core/quartoEditor"; import { commands } from "vscode"; import { killTerminal, sendTerminalCommand, terminalCommand, terminalEnv, terminalOptions } from "../core/terminal"; import { QuickPickItem } from "vscode"; -import { documentFrontMatterYaml } from "../markdown/document"; import { QuickPickItemKind } from "vscode"; import { Uri } from "vscode"; @@ -93,9 +93,7 @@ class RenderDocumentCommand extends RenderCommand if (targetEditor) { // show the editor and save - if (!isNotebook(targetEditor.document)) { - await targetEditor.activate(); - } + await targetEditor.activate(); await commands.executeCommand("workbench.action.files.save"); // kill any existing terminal @@ -123,9 +121,7 @@ class RenderDocumentCommand extends RenderCommand await sendTerminalCommand(terminal, env, this.quartoContext(), cmd); // focus the editor (sometimes the terminal steals focus) - if (!isNotebook(targetEditor.document)) { - await targetEditor.activate(); - } + await targetEditor.activate(); } } @@ -134,9 +130,7 @@ class RenderDocumentCommand extends RenderCommand private async resolveFormat(targetEditor: QuartoEditor) { return new Promise((resolve) => { - const frontMatter = targetEditor.notebook - ? targetEditor.notebook.cellAt(0)?.document.getText() || "" - : documentFrontMatterYaml(this.engine_, targetEditor.document); + const frontMatter = targetEditor.frontMatterYaml(this.engine_); const kDeclaredFormats = "Declared Formats"; const kOtherFormats = "Other Formats"; diff --git a/apps/vscode/src/providers/webview.ts b/apps/vscode/src/providers/webview.ts index fea04d170..8ed1e7ed5 100644 --- a/apps/vscode/src/providers/webview.ts +++ b/apps/vscode/src/providers/webview.ts @@ -13,20 +13,19 @@ * */ -import vscode, { +import { Uri, WebviewPanel, window, ViewColumn, EventEmitter, ExtensionContext, - WebviewPanelOnDidChangeViewStateEvent, } from "vscode"; import { Disposable } from "../core/dispose"; -import { preserveEditorFocus } from "../core/doc"; import { getNonce } from "../core/nonce"; import { ExtensionHost, HostWebviewPanel } from "../host"; +import { activeQuartoEditor } from "../core/quartoEditor"; export interface ShowOptions { readonly preserveFocus?: boolean; @@ -73,7 +72,7 @@ export class QuartoWebviewManager, S> { } this.resolveOnShow(); if (options?.preserveFocus) { - preserveEditorFocus(); + activeQuartoEditor()?.preserveEditorFocus(); } }