Skip to content
Draft
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
146 changes: 8 additions & 138 deletions apps/vscode/src/core/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
}
Comment on lines +56 to 62

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Nit: prefer dog !== undefined over !!doc for messiness of truthiness reasons

  2. Could you add a comment explaining why 'notebookType' in doc is the thing to check for isNotebook? Is notebook that same as ipynb? If we're changing isNotebookUri to isIpynbUri, should we also change isNotebook to isIpynb?



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;
Expand Down Expand Up @@ -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<void>;
slideIndex: () => Promise<number>;
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<vscode.TextDocument | undefined> {
Expand Down
180 changes: 180 additions & 0 deletions apps/vscode/src/core/quartoEditor.ts
Original file line number Diff line number Diff line change
@@ -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<void>;

/**
* 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<number>;

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,
};
}
18 changes: 0 additions & 18 deletions apps/vscode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, []);
};
}
5 changes: 5 additions & 0 deletions apps/vscode/src/markdown/notebook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as vscode from "vscode";

export function notebookFrontMatterYaml(notebook: vscode.NotebookDocument): string {
return notebook.cellAt(0)?.document.getText() || "";
}
Loading
Loading