diff --git a/apps/lsp/src/custom.ts b/apps/lsp/src/custom.ts index 726f790b..8606eb41 100644 --- a/apps/lsp/src/custom.ts +++ b/apps/lsp/src/custom.ts @@ -31,7 +31,8 @@ import { Hover, Position, TextDocuments } from "vscode-languageserver"; import { CodeViewCellContext, CodeViewCompletionContext, kCodeViewAssist, kCodeViewGetDiagnostics, kCodeViewGetCompletions, LintItem } from "editor-types"; import { yamlCompletions } from "./service/providers/completion/completion-yaml"; import { yamlHover } from "./service/providers/hover/hover-yaml"; -import { EditorContext, Quarto, codeEditorContext } from "./service/quarto"; +import { EditorContext, codeEditorContext } from "./service/quarto"; +import { Quarto } from "./quarto"; export function registerCustomMethods( quarto: Quarto, diff --git a/apps/lsp/src/index.ts b/apps/lsp/src/index.ts index 72d82e9b..0aabee6a 100644 --- a/apps/lsp/src/index.ts +++ b/apps/lsp/src/index.ts @@ -257,6 +257,7 @@ connection.onInitialized(async () => { const parser = markdownitParser(); // create language service + // note that `mdLs` is referenced in `connection.onInitialize` above mdLs = createLanguageService({ config, quarto, diff --git a/apps/lsp/src/quarto.ts b/apps/lsp/src/quarto.ts index 60a4877f..c1965197 100644 --- a/apps/lsp/src/quarto.ts +++ b/apps/lsp/src/quarto.ts @@ -31,7 +31,6 @@ import { import { QuartoContext } from "quarto-core"; import { - Quarto, CompletionResult, EditorContext, HoverResult, @@ -42,6 +41,16 @@ import { } from "./service/quarto"; import { LintItem } from "editor-types"; +export interface Quarto extends QuartoContext { + getYamlCompletions(context: EditorContext): Promise; + getAttrCompletions( + token: AttrToken, + context: EditorContext + ): Promise; + getYamlDiagnostics(context: EditorContext): Promise; + getHover?: (context: EditorContext) => Promise; +} + export async function initializeQuarto(context: QuartoContext): Promise { const quartoModule = await initializeQuartoYamlModule(context.resourcePath) as QuartoYamlModule; return { diff --git a/apps/lsp/src/service/index.ts b/apps/lsp/src/service/index.ts index d9520f6f..9c842aea 100644 --- a/apps/lsp/src/service/index.ts +++ b/apps/lsp/src/service/index.ts @@ -17,7 +17,7 @@ import type { CancellationToken, CompletionContext, TextDocuments } from 'vscode-languageserver'; import * as lsp from 'vscode-languageserver-types'; import { URI } from 'vscode-uri'; -import { Document, Parser } from "quarto-core" +import { Document, Parser } from "quarto-core"; import { LsConfiguration } from './config'; import { MdDefinitionProvider } from './providers/definitions'; import { DiagnosticComputer, DiagnosticOnSaveComputer, DiagnosticOptions, DiagnosticsManager, IPullDiagnosticsManager } from './providers/diagnostics'; @@ -33,7 +33,7 @@ import { MdTableOfContentsProvider } from './toc'; import { isWorkspaceWithFileWatching, IWorkspace } from './workspace'; import { MdHoverProvider } from './providers/hover/hover'; import { MdCompletionProvider } from './providers/completion/completion'; -import { Quarto } from './quarto'; +import { Quarto } from '../quarto'; export { IncludeWorkspaceHeaderCompletions } from './providers/completion/completion'; export type { MdCompletionProvider } from './providers/completion/completion'; @@ -44,7 +44,7 @@ export { DiagnosticCode, DiagnosticLevel } from './providers/diagnostics'; export type { ResolvedDocumentLinkTarget } from './providers/document-links'; export type { ILogger } from './logging'; export { LogLevel } from './logging'; -export type { ISlugifier } from './slugify' +export type { ISlugifier } from './slugify'; export { Slug, pandocSlugifier } from './slugify'; export type { ContainingDocumentContext, FileStat, FileWatcherOptions, IFileSystemWatcher, IWorkspace, IWorkspaceWithWatching } from './workspace'; @@ -84,7 +84,7 @@ export interface IMdLanguageService { * * @returns The headers and optionally also the link definitions in the file */ - getDocumentSymbols(document: Document, options: { readonly includeLinkDefinitions?: boolean }, token: CancellationToken): Promise; + getDocumentSymbols(document: Document, options: { readonly includeLinkDefinitions?: boolean; }, token: CancellationToken): Promise; /** * Get the folding ranges of a markdown file. @@ -238,7 +238,7 @@ export function createLanguageService(init: LanguageServiceInitialization): IMdL return documentHighlightProvider.getDocumentHighlights(document, position, token); }, computeOnSaveDiagnostics: async (doc: Document) => { - return (await diagnosticOnSaveComputer.compute(doc)) + return (await diagnosticOnSaveComputer.compute(doc)); }, computeDiagnostics: async (doc: Document, options: DiagnosticOptions, token: CancellationToken): Promise => { return (await diagnosticsComputer.compute(doc, options, token))?.diagnostics; diff --git a/apps/lsp/src/service/providers/completion/completion-attrs.ts b/apps/lsp/src/service/providers/completion/completion-attrs.ts index f1a9dcc5..e7927dd7 100644 --- a/apps/lsp/src/service/providers/completion/completion-attrs.ts +++ b/apps/lsp/src/service/providers/completion/completion-attrs.ts @@ -13,7 +13,8 @@ * */ -import { AttrContext, AttrToken, EditorContext, Quarto } from "../../quarto"; +import { AttrContext, AttrToken, EditorContext } from "../../quarto"; +import { Quarto } from "../../../quarto"; export async function attrCompletions(quarto: Quarto, context: EditorContext) { diff --git a/apps/lsp/src/service/providers/completion/completion-yaml.ts b/apps/lsp/src/service/providers/completion/completion-yaml.ts index 72679f30..497021ba 100644 --- a/apps/lsp/src/service/providers/completion/completion-yaml.ts +++ b/apps/lsp/src/service/providers/completion/completion-yaml.ts @@ -25,7 +25,8 @@ import { } from "vscode-languageserver-types"; -import { EditorContext, Quarto } from "../../quarto"; +import { EditorContext } from "../../quarto"; +import { Quarto } from "../../../quarto"; export async function yamlCompletions(quarto: Quarto, context: EditorContext, stripPadding: boolean) { @@ -67,7 +68,7 @@ export async function yamlCompletions(quarto: Quarto, context: EditorContext, st .replace(/(<([^>]+)>)/gi, "") .replace(/\n/g, " ") ) - } + }; } // strip padding if requested (vscode doesn't seem to need indentation padding) diff --git a/apps/lsp/src/service/providers/completion/completion.ts b/apps/lsp/src/service/providers/completion/completion.ts index c15c7149..fe2e1638 100644 --- a/apps/lsp/src/service/providers/completion/completion.ts +++ b/apps/lsp/src/service/providers/completion/completion.ts @@ -23,7 +23,7 @@ import { CompletionTriggerKind, TextDocuments } from "vscode-languageserver"; -import { Quarto } from "../../quarto"; +import { Quarto } from "../../../quarto"; import { attrCompletions } from "./completion-attrs"; import { latexCompletions } from "./completion-latex"; import { yamlCompletions } from "./completion-yaml"; diff --git a/apps/lsp/src/service/providers/completion/refs/completion-biblio.ts b/apps/lsp/src/service/providers/completion/refs/completion-biblio.ts index 934e24a8..31cff820 100644 --- a/apps/lsp/src/service/providers/completion/refs/completion-biblio.ts +++ b/apps/lsp/src/service/providers/completion/refs/completion-biblio.ts @@ -21,7 +21,7 @@ import { import { cslRefs } from "editor-server"; import { Document, Parser, filePathForDoc, documentFrontMatter } from "quarto-core"; -import { Quarto } from "../../../quarto"; +import { Quarto } from "../../../../quarto"; export async function biblioCompletions( quarto: Quarto, diff --git a/apps/lsp/src/service/providers/completion/refs/completion-crossref.ts b/apps/lsp/src/service/providers/completion/refs/completion-crossref.ts index d3856c9f..ba9b1331 100644 --- a/apps/lsp/src/service/providers/completion/refs/completion-crossref.ts +++ b/apps/lsp/src/service/providers/completion/refs/completion-crossref.ts @@ -23,7 +23,7 @@ import { import { XRef } from "editor-types"; import { EditorServerDocuments, xrefsForFile } from "editor-server"; -import { Quarto } from "../../../quarto"; +import { Quarto } from "../../../../quarto"; export async function crossrefCompletions( quarto: Quarto, diff --git a/apps/lsp/src/service/providers/completion/refs/completion-refs.ts b/apps/lsp/src/service/providers/completion/refs/completion-refs.ts index 1f7d2361..7995c26e 100644 --- a/apps/lsp/src/service/providers/completion/refs/completion-refs.ts +++ b/apps/lsp/src/service/providers/completion/refs/completion-refs.ts @@ -18,7 +18,8 @@ import { Range, Position } from "vscode-languageserver-types"; import { CompletionItem, TextDocuments } from "vscode-languageserver"; import { bypassRefIntelligence } from "../../../util/refs"; -import { EditorContext, Quarto } from "../../../quarto"; +import { EditorContext } from "../../../quarto"; +import { Quarto } from "../../../../quarto"; import { projectDirForDocument, filePathForDoc, Document, Parser } from "quarto-core"; import { biblioCompletions } from "./completion-biblio"; import { crossrefCompletions } from "./completion-crossref"; diff --git a/apps/lsp/src/service/providers/diagnostics-yaml.ts b/apps/lsp/src/service/providers/diagnostics-yaml.ts index 766e4f0c..3d84def4 100644 --- a/apps/lsp/src/service/providers/diagnostics-yaml.ts +++ b/apps/lsp/src/service/providers/diagnostics-yaml.ts @@ -24,9 +24,9 @@ import { import { Document } from "quarto-core"; import { - Quarto, docEditorContext } from "../quarto"; +import { Quarto } from "../../quarto"; import { kEndColumn, kEndRow, kStartColumn, kStartRow, LintItem } from "editor-types"; export async function provideYamlDiagnostics( diff --git a/apps/lsp/src/service/providers/diagnostics.ts b/apps/lsp/src/service/providers/diagnostics.ts index ca21ea99..9fd1eae2 100644 --- a/apps/lsp/src/service/providers/diagnostics.ts +++ b/apps/lsp/src/service/providers/diagnostics.ts @@ -31,7 +31,7 @@ import { ResourceMap } from '../util/resource-maps'; import { FileStat, IWorkspace, IWorkspaceWithWatching, statLinkToMarkdownFile } from '../workspace'; import { HrefKind, InternalHref, LinkDefinitionSet, MdLink, MdLinkDefinition, MdLinkKind, MdLinkProvider, MdLinkSource, parseLocationInfoFromFragment, ReferenceLinkMap } from './document-links'; import { ILogger, LogLevel } from '../logging'; -import { Quarto } from '../quarto'; +import { Quarto } from '../../quarto'; import { provideYamlDiagnostics } from './diagnostics-yaml'; /** @@ -208,12 +208,12 @@ export class DiagnosticComputer { ): Promise<{ readonly diagnostics: lsp.Diagnostic[]; readonly links: readonly MdLink[]; - readonly statCache: ResourceMap<{ readonly exists: boolean }>; + readonly statCache: ResourceMap<{ readonly exists: boolean; }>; }> { this.#logger.logDebug('DiagnosticComputer.compute', { document: doc.uri, version: doc.version }); const { links, definitions } = await this.#linkProvider.getLinks(doc); - const statCache = new ResourceMap<{ readonly exists: boolean }>(); + const statCache = new ResourceMap<{ readonly exists: boolean; }>(); if (token.isCancellationRequested) { return { links, diagnostics: [], statCache }; } @@ -381,7 +381,7 @@ export class DiagnosticComputer { async #validateFileLinks( options: DiagnosticOptions, links: readonly MdLink[], - statCache: ResourceMap<{ readonly exists: boolean }>, + statCache: ResourceMap<{ readonly exists: boolean; }>, token: CancellationToken, ): Promise { const pathErrorSeverity = toSeverity(options.validateFileLinks); @@ -551,8 +551,8 @@ class FileLinkState extends Disposable { /** * Set the known links in a markdown document, adding and removing file watchers as needed */ - updateLinksForDocument(document: URI, links: readonly MdLink[], statCache: ResourceMap<{ readonly exists: boolean }>) { - const linkedToResource = new Set<{ path: URI; exists: boolean }>( + updateLinksForDocument(document: URI, links: readonly MdLink[], statCache: ResourceMap<{ readonly exists: boolean; }>) { + const linkedToResource = new Set<{ path: URI; exists: boolean; }>( links .filter(link => link.href.kind === HrefKind.Internal) .map(link => ({ path: (link.href as InternalHref).path, exists: !!(statCache.get((link.href as InternalHref).path)?.exists) }))); @@ -590,7 +590,7 @@ class FileLinkState extends Disposable { this.updateLinksForDocument(resource, [], new ResourceMap()); } - public tryStatFileLink(link: URI): { exists: boolean } | undefined { + public tryStatFileLink(link: URI): { exists: boolean; } | undefined { const entry = this.#linkedToFile.get(link); if (!entry) { return undefined; diff --git a/apps/lsp/src/service/providers/hover/hover-ref.ts b/apps/lsp/src/service/providers/hover/hover-ref.ts index 5f9b8df1..059ecc4a 100644 --- a/apps/lsp/src/service/providers/hover/hover-ref.ts +++ b/apps/lsp/src/service/providers/hover/hover-ref.ts @@ -19,7 +19,7 @@ import { cslRefs, CslRef } from "editor-server"; import { bypassRefIntelligence } from "../../util/refs"; import { Document, Parser, filePathForDoc, documentFrontMatter } from "quarto-core"; -import { Quarto } from "../../quarto"; +import { Quarto } from "../../../quarto"; // cache the last ref lookup diff --git a/apps/lsp/src/service/providers/hover/hover-yaml.ts b/apps/lsp/src/service/providers/hover/hover-yaml.ts index 16be4c84..3d8d527e 100644 --- a/apps/lsp/src/service/providers/hover/hover-yaml.ts +++ b/apps/lsp/src/service/providers/hover/hover-yaml.ts @@ -15,7 +15,8 @@ import { Hover, MarkupKind } from "vscode-languageserver"; -import { EditorContext, Quarto } from "../../quarto"; +import { EditorContext } from "../../quarto"; +import { Quarto } from "../../../quarto"; export async function yamlHover(quarto: Quarto, context: EditorContext): Promise { // bail if no quarto connection diff --git a/apps/lsp/src/service/providers/hover/hover.ts b/apps/lsp/src/service/providers/hover/hover.ts index c0001f4e..903c5388 100644 --- a/apps/lsp/src/service/providers/hover/hover.ts +++ b/apps/lsp/src/service/providers/hover/hover.ts @@ -21,7 +21,7 @@ import { mathHover } from "./hover-math"; import { refHover } from "./hover-ref"; import { Document, Parser } from "quarto-core"; import { LsConfiguration } from "../../config"; -import { Quarto } from "../../quarto"; +import { Quarto } from "../../../quarto"; import { docEditorContext } from "../../quarto"; import { IWorkspace } from "../../workspace"; diff --git a/apps/lsp/src/service/quarto.ts b/apps/lsp/src/service/quarto.ts index 99d541e0..333150f6 100644 --- a/apps/lsp/src/service/quarto.ts +++ b/apps/lsp/src/service/quarto.ts @@ -13,12 +13,11 @@ * */ -import { CompletionItem, Position } from "vscode-languageserver-types"; +import { Position } from "vscode-languageserver-types"; -import { QuartoContext, Document, filePathForDoc, isQuartoDoc, isQuartoRevealDoc, isQuartoYaml, isQuartoDashboardDoc } from "quarto-core"; +import { Document, filePathForDoc, isQuartoDoc, isQuartoRevealDoc, isQuartoYaml, isQuartoDashboardDoc } from "quarto-core"; import { lines } from "core"; -import { LintItem } from "editor-types"; export interface CompletionResult { token: string; @@ -28,7 +27,7 @@ export interface CompletionResult { export interface HoverResult { content: string; - range: { start: Position; end: Position }; + range: { start: Position; end: Position; }; } export interface Completion { @@ -78,17 +77,6 @@ export interface AttrToken { token: string; } -export interface Quarto extends QuartoContext { - getYamlCompletions(context: EditorContext): Promise; - getAttrCompletions( - token: AttrToken, - context: EditorContext - ): Promise; - getYamlDiagnostics(context: EditorContext): Promise; - getHover?: (context: EditorContext) => Promise; -} - - export function codeEditorContext( path: string, filetype: string, @@ -149,5 +137,5 @@ export function docEditorContext( false, explicit, trigger - ) + ); } diff --git a/apps/overview.md b/apps/overview.md new file mode 100644 index 00000000..de303089 --- /dev/null +++ b/apps/overview.md @@ -0,0 +1,167 @@ +## Terminology + +- Source Editor + - controlled by VSCode/Positron + - we add some functionality to the source editor by registering commands and + providing our LSP + +- Visual Editor + - controlled by this repo! See CLIENT below. + +- EXTENSION HOST + - a.k.a. HOST + - code lives in [./vscode](./vscode/) and various packages + - entry point in [main.ts](./vscode/src/main.ts), this is the entry point to + the entire extension. The `activate` function is called by VSCODE/Positron + to start the extension. +- CLIENT + - a.k.a. Visual Editor + - code lives in [./vscode-editor](./vscode-editor/) and various packages + - initialized in the HOST by `VisualEditorProvider` in + [editor.ts](./vscode/src/providers/editor/editor.ts) + - more specifically, the actual html with css and scripts for the Visual + Editor is created in `getHtmlForWebview` + - this is loaded into a webview, a separate process from the HOST, + containing the Visual Editor + - there may be multiple CLIENTs running at the same time (one for every file + open in the Visual Editor). There is code in here to manage and coordinate + from the HOST to multiple CLIENTs. + - entry point in `runEditor` in [index.tsx](./vscode-editor/src/index.tsx) +- LSP + - code lives in [./vscode](./vscode/src/lsp/) + - initialized in the HOST by `activateLSP` in + [client.ts](./vscode/src/lsp/client.ts) + - entry point in [index.ts](./lsp/src/index.ts) + - this runs in a separate process from the HOST + +## Handling User Input + +- VSCODE/POSITRON --commands-> EXTENSION HOST + - see [package.json](./vscode/package.json) for declaration of commands + - see [main.ts](./vscode/src/main.ts) for registration of command + +- Look for "behaviors" in ProseMirror, CodeMirror + - arrow keys, ctrl+z, mouse click, etc. + +- [commands in Ace](packages/editor/src/optional/ace/ace.ts) + - used instead of CodeMirror for code cells in the Visual Editor in RStudio + +## Communication boundaries + +- EXTENSION HOST <-req-> CLIENT + - Set up on the EXTENSION HOST side: + [connection.ts](./vscode/src/providers/editor/connection.ts) + `visualEditorServer` and `visualEditorClient` + - Set up on the CLIENT side: [sync.ts](./vscode-editor/src/sync.ts) + `visualEditorHostServer` and `visualEditorHostClient` + - Communication is sent by using `request: JsonRpcRequestTransport` e.g. + `request(kCodeViewGetDiagnostics, [context])` + +- EXTENSION HOST --req-> LSP + - received by [custom.ts](./lsp/src/custom.ts) + - sent by `lspRequest: JsonRpcRequestTransport` +- EXTENSION HOST <-req-- LSP + - I don't think this happens? + +- EXTENSION HOST / LSP --command-> VSCODE/POSITRON + - sent by `vscode.commands.executeCommand(..)` + +- LSP <-provider-- VSCODE/POSITRON + - How does this work? + +- LSP --req-> Quarto CLI + - [quarto.ts](./lsp/src/quarto.ts) defines the methods that the LSP uses to + call the Quarto CLI. + +## Logging + +You can use `console.log`. When running an extension development host to test +out the extension there are a couple of places where your logs can end up: + +- browser console or `window` output console for [[CLIENT]] and [[EXTENSION + HOST]] code + - logs from these two places will look different. Logs from [[CLIENT]] will + look like normal logs; logs from [[EXTENSION HOST]] will have a blue prefix + that says EXTENSION HOST. +- `Quarto` output console for [[LSP]] code + +## Examples of Controlling the Visual Editor from the server-side of the extension + +### Example: Setting cursor position + +for example in [commands.ts](./vscode/src/providers/cell/commands.ts): + +```ts +const visualEditor = VisualEditorProvider.activeEditor(); +visualEditor.setBlockSelection(blockContext, "nextblock"); +``` + +which passes through `VisualEditorPovider`, `visualEditorClient`, +`visualEditorHostServer`, `Editor`. See the "Communication Boundaries" section. + +## Examples of Getting server-side info from the Visual Editor + +### Example: Getting diagnostics for YAML front matter + +For example in +[diagnostics.ts](../packages/editor-codemirror/src/behaviors/diagnostics.ts) + +```ts +const diagnostics = await getDiagnostics(cellContext, behaviorContext); +if (!diagnostics) return; + +for (const error of diagnostics) { + underline( + cmView, + rowColumnToIndex(code, [error[kStartColumn], error[kStartRow]]), + rowColumnToIndex(code, [error[kEndColumn], error[kEndRow]]), + error.text, + ); +} +``` + +which passes through + +- [[CLIENT]] [services.ts](../packages/editor-core/src/services.ts) function + `editorCodeViewJsonRpcServer` registers `codeViewDiagnostics` calls + `request(kCodeViewGetDiagnostics` + - request seems to communicate from the CLIENT to the EXTENSION HOST? +- [[EXTENSION HOST]] + [codeview.ts](../packages/editor-server/src/services/codeview.ts) function + `codeViewServerMethods` registers `kCodeViewGetDiagnostics` calls + `server.codeViewDiagnostics` +- [[EXTENSION HOST]] + [other codeview.ts](./vscode/src/providers/editor/codeview.ts) function + `vscodeCodeViewServer` return object with prop `codeViewDiagnostics` calls + `lspRequest(kCodeViewGetDiagnostics, [context])` +- [[LSP]] [custom.ts](./lsp/src/custom.ts) `codeViewDiagnostics` + `getYamlDiagnostics` + - `initializeQuartoYamlModule` + +#### Examples providing information to the Source Editor + +### Example: Completions + +- [vdoc-completion.ts](./vscode/src/vdoc/vdoc-completion.ts) + +```ts +await withVirtualDocUri(vdoc, parentUri, "completion", async (uri: Uri) => { + return await commands.executeCommand( + "vscode.executeCompletionItemProvider" + ... +``` + +In the Visual Editor, completions are obtained via +[codeview.ts](./vscode/src/providers/editor/codeview.ts) + +In the Source Editor, completions are obtained `embeddedCodeCompletionProvider` +in [client.ts](./vscode/src/lsp/client.ts) + +### Example: Positron Specific - Help Topic & Statement Range + +`EmbeddedStatementRangeProvider` or `EmbeddedHelpTopicProvider` in +[hooks.ts](./vscode/src/host/hooks.ts) + +- simply executes the command "vscode.executeStatementRangeProvider" or + "positron.executeHelpTopicProvider" respectively inside a virtual doc for a + cell diff --git a/apps/vscode/src/extension.ts b/apps/vscode/src/extension.ts deleted file mode 100644 index b85e7e2b..00000000 --- a/apps/vscode/src/extension.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * extension.ts - * - * Copyright (C) 2022 by Posit Software, PBC - * - * Unless you have received this program directly from Posit Software pursuant - * to the terms of a commercial license agreement with Posit Software, then - * this program is licensed to you under the terms of version 3 of the - * GNU Affero General Public License. This program is distributed WITHOUT - * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the - * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. - * - */ - -import * as vscode from "vscode"; -import { MarkdownEngine } from "./markdown/engine"; -import { activateBackgroundHighlighter } from "./providers/background"; -import { Command, CommandManager } from "./core/command"; -import { newDocumentCommands } from "./providers/newdoc"; -import { insertCommands } from "./providers/insert"; -import { activateDiagram } from "./providers/diagram/diagram"; -import { activateOptionEnterProvider } from "./providers/option"; -import { textFormattingCommands } from "./providers/text-format"; -import { activateCodeFormatting } from "./providers/format"; -import { activateContextKeySetter } from "./providers/context-keys"; -import { ExtensionHost } from "./host"; - -export function activateCommon( - context: vscode.ExtensionContext, - host: ExtensionHost, - engine: MarkdownEngine, - commands?: Command[] -) { - // option enter handler - activateOptionEnterProvider(context, engine); - - // background highlighter - activateBackgroundHighlighter(context, engine); - - // context setter - activateContextKeySetter(context, engine); - - // diagramming - const diagramCommands = activateDiagram(context, host, engine); - - // code formatting - const codeFormattingCommands = activateCodeFormatting(engine); - - // commands (common + passed) - const commandManager = new CommandManager(); - for (const cmd of codeFormattingCommands) { - commandManager.register(cmd); - } - for (const cmd of textFormattingCommands()) { - commandManager.register(cmd); - } - for (const cmd of newDocumentCommands()) { - commandManager.register(cmd); - } - for (const cmd of insertCommands(engine)) { - commandManager.register(cmd); - } - for (const cmd of diagramCommands) { - commandManager.register(cmd); - } - if (commands) { - for (const cmd of commands) { - commandManager.register(cmd); - } - } - context.subscriptions.push(commandManager); -} diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 13bd2708..4208beec 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -1,6 +1,8 @@ /* * hooks.ts * + * Positron-specific functionality. + * * Copyright (C) 2022 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant @@ -21,7 +23,6 @@ import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocu import { ExecuteQueue } from './execute-queue'; import { MarkdownEngine } from '../markdown/engine'; import { virtualDoc, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc"; -import { EmbeddedLanguage } from '../vdoc/languages'; declare global { function acquirePositronApi(): hooks.PositronApi; @@ -76,7 +77,7 @@ export function hooksExtensionHost(): ExtensionHost { for (const block of blocks) { await runtime.executeCode(language, block, false, true); } - } + }; await ExecuteQueue.instance.add(language, callback); }, @@ -165,11 +166,12 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider { const vdoc = await virtualDoc(document, position, this._engine); if (vdoc) { return await withVirtualDocUri(vdoc, document.uri, "statementRange", async (uri: vscode.Uri) => { - return getStatementRange( + const result = await vscode.commands.executeCommand( + "vscode.executeStatementRangeProvider", uri, - adjustedPosition(vdoc.language, position), - vdoc.language + position ); + return { range: unadjustedRange(vdoc.language, result.range), code: result.code }; }); } else { return undefined; @@ -177,19 +179,6 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider { }; } -async function getStatementRange( - uri: vscode.Uri, - position: vscode.Position, - language: EmbeddedLanguage -) { - const result = await vscode.commands.executeCommand( - "vscode.executeStatementRangeProvider", - uri, - position - ); - return { range: unadjustedRange(language, result.range), code: result.code }; -} - class EmbeddedHelpTopicProvider implements HostHelpTopicProvider { private readonly _engine: MarkdownEngine; diff --git a/apps/vscode/src/host/index.ts b/apps/vscode/src/host/index.ts index 929d0f1c..3100c59f 100644 --- a/apps/vscode/src/host/index.ts +++ b/apps/vscode/src/host/index.ts @@ -13,15 +13,16 @@ * */ -import vscode, { DocumentSelector, Disposable, WebviewPanelOptions, WebviewOptions } from "vscode"; +import vscode, { DocumentSelector, Disposable, WebviewPanelOptions, WebviewOptions, window } from "vscode"; import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument } from "./executors"; import { EditorToolbarProvider } from "./toolbar"; -import { createPreviewPanel } from "./preview"; import { hasHooks, hooksExtensionHost } from "./hooks"; import { TextDocument } from "vscode"; import { MarkdownEngine } from "../markdown/engine"; +import { WebviewPanel } from "vscode"; +import { ViewColumn } from "vscode"; export type { CellExecutor }; export type { EditorToolbarProvider, ToolbarItem, ToolbarCommand, ToolbarButton, ToolbarMenu } from './toolbar'; @@ -55,6 +56,11 @@ export interface HostHelpTopicProvider { ): vscode.ProviderResult; } +/** + * There are currently two extension hosts: + * - [`hooksExtensionHost`](./hooks.ts) for Positron + * - [`defaultExtensionHost`](./index.ts) otherwise + */ export interface ExtensionHost { // code execution @@ -112,13 +118,20 @@ function defaultExtensionHost(): ExtensionHost { return languages.filter(language => knitr || !visualMode || (language !== "python")); }, cellExecutorForLanguage, - // in the default extension host, both of these are just a noop: - registerStatementRangeProvider: (engine: MarkdownEngine): vscode.Disposable => { - return new vscode.Disposable(() => { }); - }, - registerHelpTopicProvider: (engine: MarkdownEngine): vscode.Disposable => { - return new vscode.Disposable(() => { }); - }, - createPreviewPanel, + // In contrast to the Positron-specific `hooksExtensionHost`, here in the default host we + // do not have statement range or help topic functionality + registerStatementRangeProvider: doNothing, + registerHelpTopicProvider: doNothing, + createPreviewPanel: ( + viewType: string, + title: string, + preserveFocus?: boolean, + options?: WebviewPanelOptions & WebviewOptions + ): WebviewPanel => { + return window.createWebviewPanel(viewType, title, { viewColumn: ViewColumn.Beside, preserveFocus, }, options); + } }; } + +const doNothing = (engine: MarkdownEngine): vscode.Disposable => + new vscode.Disposable(() => { }); diff --git a/apps/vscode/src/host/preview.ts b/apps/vscode/src/host/preview.ts deleted file mode 100644 index fd81df5b..00000000 --- a/apps/vscode/src/host/preview.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * preview.ts - * - * Copyright (C) 2022 by Posit Software, PBC - * - * Unless you have received this program directly from Posit Software pursuant - * to the terms of a commercial license agreement with Posit Software, then - * this program is licensed to you under the terms of version 3 of the - * GNU Affero General Public License. This program is distributed WITHOUT - * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the - * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. - * - */ - -import { window, WebviewPanel, WebviewOptions, WebviewPanelOptions, ViewColumn } from "vscode"; - -export function createPreviewPanel( - viewType: string, - title: string, - preserveFocus?: boolean, - options?: WebviewPanelOptions & WebviewOptions -): WebviewPanel { - return window.createWebviewPanel( - viewType, - title, - { - viewColumn: ViewColumn.Beside, - preserveFocus, - }, - options - ); -} diff --git a/apps/vscode/src/main.ts b/apps/vscode/src/main.ts index bb7712f6..7150a3ee 100644 --- a/apps/vscode/src/main.ts +++ b/apps/vscode/src/main.ts @@ -22,7 +22,6 @@ import { activateLsp, deactivate as deactivateLsp } from "./lsp/client"; import { cellCommands } from "./providers/cell/commands"; import { quartoCellExecuteCodeLensProvider } from "./providers/cell/codelens"; import { activateQuartoAssistPanel } from "./providers/assist/panel"; -import { activateCommon } from "./extension"; import { activatePreview } from "./providers/preview/preview"; import { activateRender } from "./providers/render"; import { activateStatusBar } from "./providers/statusbar"; @@ -31,12 +30,24 @@ import { activateLuaTypes } from "./providers/lua-types"; import { activateCreate } from "./providers/create/create"; import { activateEditor } from "./providers/editor/editor"; import { activateCopyFiles } from "./providers/copyfiles"; -import { activateZotero } from "./providers/zotero/zotero";; +import { activateZotero } from "./providers/zotero/zotero"; import { extensionHost } from "./host"; import { initQuartoContext } from "quarto-core"; import { configuredQuartoPath } from "./core/quarto"; import { activateDenoConfig } from "./providers/deno-config"; +import { textFormattingCommands } from "./providers/text-format"; +import { newDocumentCommands } from "./providers/newdoc"; +import { insertCommands } from "./providers/insert"; +import { activateDiagram } from "./providers/diagram/diagram"; +import { activateCodeFormatting } from "./providers/format"; +import { activateOptionEnterProvider } from "./providers/option"; +import { activateBackgroundHighlighter } from "./providers/background"; +import { activateContextKeySetter } from "./providers/context-keys"; +import { CommandManager } from "./core/command"; +/** + * Entry point for the entire extension! This initializes the LSP, quartoContext, extension host, and more... + */ export async function activate(context: vscode.ExtensionContext) { // create output channel for extension logs and lsp client logs const outputChannel = vscode.window.createOutputChannel("Quarto", { log: true }); @@ -118,6 +129,16 @@ export async function activate(context: vscode.ExtensionContext) { const createCommands = await activateCreate(context, quartoContext); commands.push(...createCommands); + commands.push(...textFormattingCommands()); + + commands.push(...newDocumentCommands()); + + commands.push(...insertCommands(engine)); + + commands.push(...activateDiagram(context, host, engine)); + + commands.push(...activateCodeFormatting(engine)); + // provide code lens vscode.languages.registerCodeLensProvider( kQuartoDocSelector, @@ -127,8 +148,21 @@ export async function activate(context: vscode.ExtensionContext) { // provide file copy/drop handling activateCopyFiles(context); - // activate providers common to browser/node - activateCommon(context, host, engine, commands); + // option enter handler + activateOptionEnterProvider(context, engine); + + // background highlighter + activateBackgroundHighlighter(context, engine); + + // context setter + activateContextKeySetter(context, engine); + + // commands + const commandManager = new CommandManager(); + for (const cmd of commands) { + commandManager.register(cmd); + } + context.subscriptions.push(commandManager); // Register configuration change listener for Quarto path settings registerQuartoPathConfigListener(context, outputChannel); diff --git a/apps/vscode/src/providers/cell/commands.ts b/apps/vscode/src/providers/cell/commands.ts index a95b1d80..11a07a39 100644 --- a/apps/vscode/src/providers/cell/commands.ts +++ b/apps/vscode/src/providers/cell/commands.ts @@ -1,6 +1,8 @@ /* * commands.ts * + * commands for executing code in cells, both in the Source Editor and Visual Editor. + * * Copyright (C) 2022 by Posit Software, PBC * * Unless you have received this program directly from Posit Software pursuant @@ -73,7 +75,6 @@ abstract class RunCommand { ) { } public async execute(line?: number): Promise { - // see if this is for the visual or the source editor const visualEditor = VisualEditorProvider.activeEditor(); if (visualEditor) { @@ -145,6 +146,8 @@ abstract class RunCommand { } private async hasExecutorForLanguage(language: string, document: TextDocument, engine: MarkdownEngine) { + // TODO: this is incorrect right? `cellExecutorForLanguage` returns a promise, and a promise will always be truthy? + // We should have to await it before doing `!!` return !!this.cellExecutorForLanguage(language, document, engine); } diff --git a/apps/vscode/src/providers/editor/codeview.ts b/apps/vscode/src/providers/editor/codeview.ts index a05d0282..13a990ce 100644 --- a/apps/vscode/src/providers/editor/codeview.ts +++ b/apps/vscode/src/providers/editor/codeview.ts @@ -47,13 +47,14 @@ import { LintItem } from "editor-types"; -import { hasHooks } from "../../host/hooks"; import { embeddedLanguage } from "../../vdoc/languages"; import { virtualDocForCode } from "../../vdoc/vdoc"; import { vdocCompletions } from "../../vdoc/vdoc-completion"; import { MarkdownEngine } from "../../markdown/engine"; - +/** + * In the host, this initializes functions that can be called by the client (the visual editor) for doing code cell things. + */ export function vscodeCodeViewServer(_engine: MarkdownEngine, document: TextDocument, lspRequest: JsonRpcRequestTransport): CodeViewServer { return { async codeViewAssist(context: CodeViewCellContext) { diff --git a/packages/editor-types/src/codeview.ts b/packages/editor-types/src/codeview.ts index 349a961a..2977f404 100644 --- a/packages/editor-types/src/codeview.ts +++ b/packages/editor-types/src/codeview.ts @@ -20,6 +20,9 @@ export const kCodeViewAssist = 'code_view_assist'; export const kCodeViewGetCompletions = 'code_view_get_completions'; export const kCodeViewExecute = 'code_view_execute'; export const kCodeViewPreviewDiagram = 'code_view_preview_diagram'; +/** + * for calling [`codeViewDiagnostics` in custom.ts](../../../apps/lsp/src/custom.ts) + */ export const kCodeViewGetDiagnostics = 'code_view_get_diagnostics'; export type CodeViewExecute = "selection" | "cell" | "cell+advance" | "above" | "below"; @@ -59,6 +62,9 @@ export interface CodeViewCompletionContext extends CodeViewCellContext { explicit: boolean; } +/** + * Constructed by `vscodeCodeViewServer` in [codeview.ts](../../../apps/vscode/src/providers/editor/codeview.ts). + */ export interface CodeViewServer { codeViewAssist: (contxt: CodeViewCellContext) => Promise; codeViewExecute: (execute: CodeViewExecute, context: CodeViewActiveBlockContext) => Promise;