diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index 613b9a4f..8e204361 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.118.0 (unreleased) +- Provide F1 help at cursor in Positron () + ## 1.117.0 (Release on 2024-11-07) - Fix issue with temp files for LSP request virtual documents () diff --git a/apps/vscode/src/@types/hooks.d.ts b/apps/vscode/src/@types/hooks.d.ts index 66abfa90..8efba98e 100644 --- a/apps/vscode/src/@types/hooks.d.ts +++ b/apps/vscode/src/@types/hooks.d.ts @@ -25,6 +25,10 @@ declare module 'positron' { selector: vscode.DocumentSelector, provider: StatementRangeProvider ): vscode.Disposable; + registerHelpTopicProvider( + selector: vscode.DocumentSelector, + provider: HelpTopicProvider + ): vscode.Disposable; } export interface StatementRangeProvider { @@ -35,6 +39,14 @@ declare module 'positron' { ): vscode.ProviderResult; } + export interface HelpTopicProvider { + provideHelpTopic( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): vscode.ProviderResult; + } + export interface StatementRange { readonly range: vscode.Range; readonly code?: string; diff --git a/apps/vscode/src/core/hover.ts b/apps/vscode/src/core/hover.ts index b0825488..6f1a484a 100644 --- a/apps/vscode/src/core/hover.ts +++ b/apps/vscode/src/core/hover.ts @@ -15,17 +15,18 @@ import { Hover, MarkdownString, MarkedString, Position, SignatureHelp, commands } from "vscode"; -import { VirtualDocUri, adjustedPosition, unadjustedRange } from "../vdoc/vdoc"; +import { adjustedPosition, unadjustedRange } from "../vdoc/vdoc"; import { EmbeddedLanguage } from "../vdoc/languages"; +import { Uri } from "vscode"; export async function getHover( - vdocUri: VirtualDocUri, + uri: Uri, language: EmbeddedLanguage, position: Position ) { const hovers = await commands.executeCommand( "vscode.executeHoverProvider", - vdocUri.uri, + uri, adjustedPosition(language, position) ); if (hovers && hovers.length > 0) { @@ -45,14 +46,14 @@ export async function getHover( } export async function getSignatureHelpHover( - vdocUri: VirtualDocUri, + uri: Uri, language: EmbeddedLanguage, position: Position, triggerCharacter?: string ) { return await commands.executeCommand( "vscode.executeSignatureHelpProvider", - vdocUri.uri, + uri, adjustedPosition(language, position), triggerCharacter ); diff --git a/apps/vscode/src/host/hooks.ts b/apps/vscode/src/host/hooks.ts index 23ff087c..9c826762 100644 --- a/apps/vscode/src/host/hooks.ts +++ b/apps/vscode/src/host/hooks.ts @@ -16,11 +16,11 @@ import * as vscode from 'vscode'; import * as hooks from 'positron'; -import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider } from '.'; +import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider, HostHelpTopicProvider } from '.'; import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors'; import { ExecuteQueue } from './execute-queue'; import { MarkdownEngine } from '../markdown/engine'; -import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange } from "../vdoc/vdoc"; +import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc"; import { EmbeddedLanguage } from '../vdoc/languages'; declare global { @@ -99,6 +99,15 @@ export function hooksExtensionHost(): ExtensionHost { return new vscode.Disposable(() => { }); }, + registerHelpTopicProvider: (engine: MarkdownEngine): vscode.Disposable => { + const hooks = hooksApi(); + if (hooks) { + return hooks.languages.registerHelpTopicProvider('quarto', + new EmbeddedHelpTopicProvider(engine)); + } + return new vscode.Disposable(() => { }); + }, + createPreviewPanel: ( viewType: string, title: string, @@ -154,20 +163,13 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider { token: vscode.CancellationToken): Promise { const vdoc = await virtualDoc(document, position, this._engine); if (vdoc) { - const vdocUri = await virtualDocUri(vdoc, document.uri, "statementRange"); - try { + return await withVirtualDocUri(vdoc, document.uri, "statementRange", async (uri: vscode.Uri) => { return getStatementRange( - vdocUri.uri, + uri, adjustedPosition(vdoc.language, position), vdoc.language ); - } catch (error) { - return undefined; - } finally { - if (vdocUri.cleanup) { - await vdocUri.cleanup(); - } - } + }); } else { return undefined; } @@ -186,3 +188,33 @@ async function getStatementRange( ); return { range: unadjustedRange(language, result.range), code: result.code }; } + +class EmbeddedHelpTopicProvider implements HostHelpTopicProvider { + private readonly _engine: MarkdownEngine; + + constructor( + readonly engine: MarkdownEngine, + ) { + this._engine = engine; + } + + async provideHelpTopic( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken): Promise { + const vdoc = await virtualDoc(document, position, this._engine); + + if (vdoc) { + return await withVirtualDocUri(vdoc, document.uri, "helpTopic", async (uri: vscode.Uri) => { + return await vscode.commands.executeCommand( + "positron.executeHelpTopicProvider", + uri, + adjustedPosition(vdoc.language, position), + vdoc.language + ); + }); + } else { + return undefined; + } + }; +} diff --git a/apps/vscode/src/host/index.ts b/apps/vscode/src/host/index.ts index bfbd8bf5..929d0f1c 100644 --- a/apps/vscode/src/host/index.ts +++ b/apps/vscode/src/host/index.ts @@ -47,6 +47,14 @@ export interface HostStatementRange { readonly code?: string; } +export interface HostHelpTopicProvider { + provideHelpTopic( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): vscode.ProviderResult; +} + export interface ExtensionHost { // code execution @@ -63,6 +71,11 @@ export interface ExtensionHost { engine: MarkdownEngine, ): vscode.Disposable; + // help topic provider + registerHelpTopicProvider( + engine: MarkdownEngine, + ): vscode.Disposable; + // preview createPreviewPanel( viewType: string, @@ -99,10 +112,13 @@ function defaultExtensionHost(): ExtensionHost { return languages.filter(language => knitr || !visualMode || (language !== "python")); }, cellExecutorForLanguage, - // in the default extension host, this is a noop: + // 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, }; } diff --git a/apps/vscode/src/lsp/client.ts b/apps/vscode/src/lsp/client.ts index d7384000..8d4f8d97 100644 --- a/apps/vscode/src/lsp/client.ts +++ b/apps/vscode/src/lsp/client.ts @@ -110,6 +110,7 @@ export async function activateLsp( middleware.provideSignatureHelp = embeddedSignatureHelpProvider(engine); } extensionHost().registerStatementRangeProvider(engine); + extensionHost().registerHelpTopicProvider(engine); // create client options const initializationOptions: LspInitializationOptions = { @@ -226,7 +227,7 @@ function embeddedHoverProvider(engine: MarkdownEngine) { // execute hover try { - return getHover(vdocUri, vdoc.language, position); + return getHover(vdocUri.uri, vdoc.language, position); } catch (error) { console.log(error); } finally { @@ -253,7 +254,7 @@ function embeddedSignatureHelpProvider(engine: MarkdownEngine) { if (vdoc) { const vdocUri = await virtualDocUri(vdoc, document.uri, "signature"); try { - return getSignatureHelpHover(vdocUri, vdoc.language, position, context.triggerCharacter); + return getSignatureHelpHover(vdocUri.uri, vdoc.language, position, context.triggerCharacter); } catch (error) { return undefined; } finally { diff --git a/apps/vscode/src/providers/assist/render-assist.ts b/apps/vscode/src/providers/assist/render-assist.ts index a067d969..46e0159d 100644 --- a/apps/vscode/src/providers/assist/render-assist.ts +++ b/apps/vscode/src/providers/assist/render-assist.ts @@ -110,13 +110,13 @@ export async function renderCodeViewAssist( const language = embeddedLanguage(context.language); if (language) { const vdoc = virtualDocForCode(context.code, language); - const vdocUri = await virtualDocUri(vdoc, Uri.file(context.filepath), "hover"); - return await withVirtualDocUri(vdocUri, async () => { + const parentUri = Uri.file(context.filepath); + return await withVirtualDocUri(vdoc, parentUri, "hover", async (uri: Uri) => { try { const position = new Position(context.selection.start.line, context.selection.start.character); // check for hover - const hover = await getHover(vdocUri, language, position); + const hover = await getHover(uri, language, position); if (hover) { const assist = getAssistFromHovers([hover], asWebviewUri); if (assist) { @@ -129,7 +129,7 @@ export async function renderCodeViewAssist( } // check for signature tip - const signatureHover = await getSignatureHelpHover(vdocUri, language, position); + const signatureHover = await getSignatureHelpHover(uri, language, position); if (signatureHover) { return getAssistFromSignatureHelp(signatureHover); } diff --git a/apps/vscode/src/providers/format.ts b/apps/vscode/src/providers/format.ts index d36b07be..acb0c691 100644 --- a/apps/vscode/src/providers/format.ts +++ b/apps/vscode/src/providers/format.ts @@ -178,8 +178,7 @@ async function executeFormatDocumentProvider( document: TextDocument, options: FormattingOptions ): Promise { - const vdocUri = await virtualDocUri(vdoc, document.uri, "format"); - const edits = await withVirtualDocUri(vdocUri, async (uri: Uri) => { + const edits = await withVirtualDocUri(vdoc, document.uri, "format", async (uri: Uri) => { return await commands.executeCommand( "vscode.executeFormatDocumentProvider", uri, diff --git a/apps/vscode/src/vdoc/vdoc-completion.ts b/apps/vscode/src/vdoc/vdoc-completion.ts index 84310c74..a8f2b0fd 100644 --- a/apps/vscode/src/vdoc/vdoc-completion.ts +++ b/apps/vscode/src/vdoc/vdoc-completion.ts @@ -24,10 +24,7 @@ export async function vdocCompletions( language: EmbeddedLanguage, parentUri: Uri ) { - - const vdocUri = await virtualDocUri(vdoc, parentUri, "completion"); - - const completions = await withVirtualDocUri(vdocUri, async (uri: Uri) => { + const completions = await withVirtualDocUri(vdoc, parentUri, "completion", async (uri: Uri) => { return await commands.executeCommand( "vscode.executeCompletionItemProvider", uri, diff --git a/apps/vscode/src/vdoc/vdoc.ts b/apps/vscode/src/vdoc/vdoc.ts index 9287c67b..bfe942a9 100644 --- a/apps/vscode/src/vdoc/vdoc.ts +++ b/apps/vscode/src/vdoc/vdoc.ts @@ -117,16 +117,24 @@ export type VirtualDocAction = "signature" | "definition" | "format" | - "statementRange"; + "statementRange" | + "helpTopic"; export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise }; -export async function withVirtualDocUri(virtualDocUri: VirtualDocUri, f: (uri: Uri) => Promise) { +export async function withVirtualDocUri( + vdoc: VirtualDoc, + parentUri: Uri, + action: VirtualDocAction, + f: (uri: Uri) => Promise +) { + const vdocUri = await virtualDocUri(vdoc, parentUri, action); + try { - return await f(virtualDocUri.uri); + return await f(vdocUri.uri); } finally { - if (virtualDocUri.cleanup) { - virtualDocUri.cleanup(); + if (vdocUri.cleanup) { + vdocUri.cleanup(); } } }