Skip to content

Commit

Permalink
Add Positron help topic provider (#599)
Browse files Browse the repository at this point in the history
* Add new help topic provider for Quarto files

* Fix up return

* Help topic provider with doc (#601)

* More usefully define `withVirtualDocUri()`

* Use `withVirtualDocUri()` in `provideHelpTopic()`

* Use new version of `withVirtualDocUri()` for statement range provider as well

* Update changelog

---------

Co-authored-by: Davis Vaughan <[email protected]>
  • Loading branch information
juliasilge and DavisVaughan authored Nov 9, 2024
1 parent 0bd7478 commit 69fce35
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 35 deletions.
2 changes: 2 additions & 0 deletions apps/vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 1.118.0 (unreleased)

- Provide F1 help at cursor in Positron (<https://github.com/quarto-dev/quarto/pull/599>)

## 1.117.0 (Release on 2024-11-07)

- Fix issue with temp files for LSP request virtual documents (<https://github.com/quarto-dev/quarto/pull/585>)
Expand Down
12 changes: 12 additions & 0 deletions apps/vscode/src/@types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,6 +39,14 @@ declare module 'positron' {
): vscode.ProviderResult<StatementRange>;
}

export interface HelpTopicProvider {
provideHelpTopic(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): vscode.ProviderResult<string>;
}

export interface StatementRange {
readonly range: vscode.Range;
readonly code?: string;
Expand Down
11 changes: 6 additions & 5 deletions apps/vscode/src/core/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hover[]>(
"vscode.executeHoverProvider",
vdocUri.uri,
uri,
adjustedPosition(language, position)
);
if (hovers && hovers.length > 0) {
Expand All @@ -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<SignatureHelp>(
"vscode.executeSignatureHelpProvider",
vdocUri.uri,
uri,
adjustedPosition(language, position),
triggerCharacter
);
Expand Down
56 changes: 44 additions & 12 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -154,20 +163,13 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider {
token: vscode.CancellationToken): Promise<hooks.StatementRange | undefined> {
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;
}
Expand All @@ -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<string | undefined> {
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<string>(
"positron.executeHelpTopicProvider",
uri,
adjustedPosition(vdoc.language, position),
vdoc.language
);
});
} else {
return undefined;
}
};
}
18 changes: 17 additions & 1 deletion apps/vscode/src/host/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
}

export interface ExtensionHost {

// code execution
Expand All @@ -63,6 +71,11 @@ export interface ExtensionHost {
engine: MarkdownEngine,
): vscode.Disposable;

// help topic provider
registerHelpTopicProvider(
engine: MarkdownEngine,
): vscode.Disposable;

// preview
createPreviewPanel(
viewType: string,
Expand Down Expand Up @@ -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,
};
}
5 changes: 3 additions & 2 deletions apps/vscode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export async function activateLsp(
middleware.provideSignatureHelp = embeddedSignatureHelpProvider(engine);
}
extensionHost().registerStatementRangeProvider(engine);
extensionHost().registerHelpTopicProvider(engine);

// create client options
const initializationOptions: LspInitializationOptions = {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions apps/vscode/src/providers/assist/render-assist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Assist | undefined>(vdocUri, async () => {
const parentUri = Uri.file(context.filepath);
return await withVirtualDocUri<Assist | undefined>(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) {
Expand All @@ -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);
}
Expand Down
3 changes: 1 addition & 2 deletions apps/vscode/src/providers/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ async function executeFormatDocumentProvider(
document: TextDocument,
options: FormattingOptions
): Promise<TextEdit[] | undefined> {
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<TextEdit[]>(
"vscode.executeFormatDocumentProvider",
uri,
Expand Down
5 changes: 1 addition & 4 deletions apps/vscode/src/vdoc/vdoc-completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompletionList>(
"vscode.executeCompletionItemProvider",
uri,
Expand Down
18 changes: 13 additions & 5 deletions apps/vscode/src/vdoc/vdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,24 @@ export type VirtualDocAction =
"signature" |
"definition" |
"format" |
"statementRange";
"statementRange" |
"helpTopic";

export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise<void> };

export async function withVirtualDocUri<T>(virtualDocUri: VirtualDocUri, f: (uri: Uri) => Promise<T>) {
export async function withVirtualDocUri<T>(
vdoc: VirtualDoc,
parentUri: Uri,
action: VirtualDocAction,
f: (uri: Uri) => Promise<T>
) {
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();
}
}
}
Expand Down

0 comments on commit 69fce35

Please sign in to comment.