Skip to content

Commit 69fce35

Browse files
Add Positron help topic provider (#599)
* 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 <davis@posit.co>
1 parent 0bd7478 commit 69fce35

File tree

10 files changed

+103
-35
lines changed

10 files changed

+103
-35
lines changed

apps/vscode/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 1.118.0 (unreleased)
44

5+
- Provide F1 help at cursor in Positron (<https://github.com/quarto-dev/quarto/pull/599>)
6+
57
## 1.117.0 (Release on 2024-11-07)
68

79
- Fix issue with temp files for LSP request virtual documents (<https://github.com/quarto-dev/quarto/pull/585>)

apps/vscode/src/@types/hooks.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ declare module 'positron' {
2525
selector: vscode.DocumentSelector,
2626
provider: StatementRangeProvider
2727
): vscode.Disposable;
28+
registerHelpTopicProvider(
29+
selector: vscode.DocumentSelector,
30+
provider: HelpTopicProvider
31+
): vscode.Disposable;
2832
}
2933

3034
export interface StatementRangeProvider {
@@ -35,6 +39,14 @@ declare module 'positron' {
3539
): vscode.ProviderResult<StatementRange>;
3640
}
3741

42+
export interface HelpTopicProvider {
43+
provideHelpTopic(
44+
document: vscode.TextDocument,
45+
position: vscode.Position,
46+
token: vscode.CancellationToken
47+
): vscode.ProviderResult<string>;
48+
}
49+
3850
export interface StatementRange {
3951
readonly range: vscode.Range;
4052
readonly code?: string;

apps/vscode/src/core/hover.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@
1515

1616

1717
import { Hover, MarkdownString, MarkedString, Position, SignatureHelp, commands } from "vscode";
18-
import { VirtualDocUri, adjustedPosition, unadjustedRange } from "../vdoc/vdoc";
18+
import { adjustedPosition, unadjustedRange } from "../vdoc/vdoc";
1919
import { EmbeddedLanguage } from "../vdoc/languages";
20+
import { Uri } from "vscode";
2021

2122
export async function getHover(
22-
vdocUri: VirtualDocUri,
23+
uri: Uri,
2324
language: EmbeddedLanguage,
2425
position: Position
2526
) {
2627
const hovers = await commands.executeCommand<Hover[]>(
2728
"vscode.executeHoverProvider",
28-
vdocUri.uri,
29+
uri,
2930
adjustedPosition(language, position)
3031
);
3132
if (hovers && hovers.length > 0) {
@@ -45,14 +46,14 @@ export async function getHover(
4546
}
4647

4748
export async function getSignatureHelpHover(
48-
vdocUri: VirtualDocUri,
49+
uri: Uri,
4950
language: EmbeddedLanguage,
5051
position: Position,
5152
triggerCharacter?: string
5253
) {
5354
return await commands.executeCommand<SignatureHelp>(
5455
"vscode.executeSignatureHelpProvider",
55-
vdocUri.uri,
56+
uri,
5657
adjustedPosition(language, position),
5758
triggerCharacter
5859
);

apps/vscode/src/host/hooks.ts

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
import * as vscode from 'vscode';
1717
import * as hooks from 'positron';
1818

19-
import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider } from '.';
19+
import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider, HostHelpTopicProvider } from '.';
2020
import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors';
2121
import { ExecuteQueue } from './execute-queue';
2222
import { MarkdownEngine } from '../markdown/engine';
23-
import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange } from "../vdoc/vdoc";
23+
import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc";
2424
import { EmbeddedLanguage } from '../vdoc/languages';
2525

2626
declare global {
@@ -99,6 +99,15 @@ export function hooksExtensionHost(): ExtensionHost {
9999
return new vscode.Disposable(() => { });
100100
},
101101

102+
registerHelpTopicProvider: (engine: MarkdownEngine): vscode.Disposable => {
103+
const hooks = hooksApi();
104+
if (hooks) {
105+
return hooks.languages.registerHelpTopicProvider('quarto',
106+
new EmbeddedHelpTopicProvider(engine));
107+
}
108+
return new vscode.Disposable(() => { });
109+
},
110+
102111
createPreviewPanel: (
103112
viewType: string,
104113
title: string,
@@ -154,20 +163,13 @@ class EmbeddedStatementRangeProvider implements HostStatementRangeProvider {
154163
token: vscode.CancellationToken): Promise<hooks.StatementRange | undefined> {
155164
const vdoc = await virtualDoc(document, position, this._engine);
156165
if (vdoc) {
157-
const vdocUri = await virtualDocUri(vdoc, document.uri, "statementRange");
158-
try {
166+
return await withVirtualDocUri(vdoc, document.uri, "statementRange", async (uri: vscode.Uri) => {
159167
return getStatementRange(
160-
vdocUri.uri,
168+
uri,
161169
adjustedPosition(vdoc.language, position),
162170
vdoc.language
163171
);
164-
} catch (error) {
165-
return undefined;
166-
} finally {
167-
if (vdocUri.cleanup) {
168-
await vdocUri.cleanup();
169-
}
170-
}
172+
});
171173
} else {
172174
return undefined;
173175
}
@@ -186,3 +188,33 @@ async function getStatementRange(
186188
);
187189
return { range: unadjustedRange(language, result.range), code: result.code };
188190
}
191+
192+
class EmbeddedHelpTopicProvider implements HostHelpTopicProvider {
193+
private readonly _engine: MarkdownEngine;
194+
195+
constructor(
196+
readonly engine: MarkdownEngine,
197+
) {
198+
this._engine = engine;
199+
}
200+
201+
async provideHelpTopic(
202+
document: vscode.TextDocument,
203+
position: vscode.Position,
204+
token: vscode.CancellationToken): Promise<string | undefined> {
205+
const vdoc = await virtualDoc(document, position, this._engine);
206+
207+
if (vdoc) {
208+
return await withVirtualDocUri(vdoc, document.uri, "helpTopic", async (uri: vscode.Uri) => {
209+
return await vscode.commands.executeCommand<string>(
210+
"positron.executeHelpTopicProvider",
211+
uri,
212+
adjustedPosition(vdoc.language, position),
213+
vdoc.language
214+
);
215+
});
216+
} else {
217+
return undefined;
218+
}
219+
};
220+
}

apps/vscode/src/host/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ export interface HostStatementRange {
4747
readonly code?: string;
4848
}
4949

50+
export interface HostHelpTopicProvider {
51+
provideHelpTopic(
52+
document: vscode.TextDocument,
53+
position: vscode.Position,
54+
token: vscode.CancellationToken
55+
): vscode.ProviderResult<string>;
56+
}
57+
5058
export interface ExtensionHost {
5159

5260
// code execution
@@ -63,6 +71,11 @@ export interface ExtensionHost {
6371
engine: MarkdownEngine,
6472
): vscode.Disposable;
6573

74+
// help topic provider
75+
registerHelpTopicProvider(
76+
engine: MarkdownEngine,
77+
): vscode.Disposable;
78+
6679
// preview
6780
createPreviewPanel(
6881
viewType: string,
@@ -99,10 +112,13 @@ function defaultExtensionHost(): ExtensionHost {
99112
return languages.filter(language => knitr || !visualMode || (language !== "python"));
100113
},
101114
cellExecutorForLanguage,
102-
// in the default extension host, this is a noop:
115+
// in the default extension host, both of these are just a noop:
103116
registerStatementRangeProvider: (engine: MarkdownEngine): vscode.Disposable => {
104117
return new vscode.Disposable(() => { });
105118
},
119+
registerHelpTopicProvider: (engine: MarkdownEngine): vscode.Disposable => {
120+
return new vscode.Disposable(() => { });
121+
},
106122
createPreviewPanel,
107123
};
108124
}

apps/vscode/src/lsp/client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export async function activateLsp(
110110
middleware.provideSignatureHelp = embeddedSignatureHelpProvider(engine);
111111
}
112112
extensionHost().registerStatementRangeProvider(engine);
113+
extensionHost().registerHelpTopicProvider(engine);
113114

114115
// create client options
115116
const initializationOptions: LspInitializationOptions = {
@@ -226,7 +227,7 @@ function embeddedHoverProvider(engine: MarkdownEngine) {
226227

227228
// execute hover
228229
try {
229-
return getHover(vdocUri, vdoc.language, position);
230+
return getHover(vdocUri.uri, vdoc.language, position);
230231
} catch (error) {
231232
console.log(error);
232233
} finally {
@@ -253,7 +254,7 @@ function embeddedSignatureHelpProvider(engine: MarkdownEngine) {
253254
if (vdoc) {
254255
const vdocUri = await virtualDocUri(vdoc, document.uri, "signature");
255256
try {
256-
return getSignatureHelpHover(vdocUri, vdoc.language, position, context.triggerCharacter);
257+
return getSignatureHelpHover(vdocUri.uri, vdoc.language, position, context.triggerCharacter);
257258
} catch (error) {
258259
return undefined;
259260
} finally {

apps/vscode/src/providers/assist/render-assist.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,13 @@ export async function renderCodeViewAssist(
110110
const language = embeddedLanguage(context.language);
111111
if (language) {
112112
const vdoc = virtualDocForCode(context.code, language);
113-
const vdocUri = await virtualDocUri(vdoc, Uri.file(context.filepath), "hover");
114-
return await withVirtualDocUri<Assist | undefined>(vdocUri, async () => {
113+
const parentUri = Uri.file(context.filepath);
114+
return await withVirtualDocUri<Assist | undefined>(vdoc, parentUri, "hover", async (uri: Uri) => {
115115
try {
116116
const position = new Position(context.selection.start.line, context.selection.start.character);
117117

118118
// check for hover
119-
const hover = await getHover(vdocUri, language, position);
119+
const hover = await getHover(uri, language, position);
120120
if (hover) {
121121
const assist = getAssistFromHovers([hover], asWebviewUri);
122122
if (assist) {
@@ -129,7 +129,7 @@ export async function renderCodeViewAssist(
129129
}
130130

131131
// check for signature tip
132-
const signatureHover = await getSignatureHelpHover(vdocUri, language, position);
132+
const signatureHover = await getSignatureHelpHover(uri, language, position);
133133
if (signatureHover) {
134134
return getAssistFromSignatureHelp(signatureHover);
135135
}

apps/vscode/src/providers/format.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ async function executeFormatDocumentProvider(
178178
document: TextDocument,
179179
options: FormattingOptions
180180
): Promise<TextEdit[] | undefined> {
181-
const vdocUri = await virtualDocUri(vdoc, document.uri, "format");
182-
const edits = await withVirtualDocUri(vdocUri, async (uri: Uri) => {
181+
const edits = await withVirtualDocUri(vdoc, document.uri, "format", async (uri: Uri) => {
183182
return await commands.executeCommand<TextEdit[]>(
184183
"vscode.executeFormatDocumentProvider",
185184
uri,

apps/vscode/src/vdoc/vdoc-completion.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ export async function vdocCompletions(
2424
language: EmbeddedLanguage,
2525
parentUri: Uri
2626
) {
27-
28-
const vdocUri = await virtualDocUri(vdoc, parentUri, "completion");
29-
30-
const completions = await withVirtualDocUri(vdocUri, async (uri: Uri) => {
27+
const completions = await withVirtualDocUri(vdoc, parentUri, "completion", async (uri: Uri) => {
3128
return await commands.executeCommand<CompletionList>(
3229
"vscode.executeCompletionItemProvider",
3330
uri,

apps/vscode/src/vdoc/vdoc.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,24 @@ export type VirtualDocAction =
117117
"signature" |
118118
"definition" |
119119
"format" |
120-
"statementRange";
120+
"statementRange" |
121+
"helpTopic";
121122

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

124-
export async function withVirtualDocUri<T>(virtualDocUri: VirtualDocUri, f: (uri: Uri) => Promise<T>) {
125+
export async function withVirtualDocUri<T>(
126+
vdoc: VirtualDoc,
127+
parentUri: Uri,
128+
action: VirtualDocAction,
129+
f: (uri: Uri) => Promise<T>
130+
) {
131+
const vdocUri = await virtualDocUri(vdoc, parentUri, action);
132+
125133
try {
126-
return await f(virtualDocUri.uri);
134+
return await f(vdocUri.uri);
127135
} finally {
128-
if (virtualDocUri.cleanup) {
129-
virtualDocUri.cleanup();
136+
if (vdocUri.cleanup) {
137+
vdocUri.cleanup();
130138
}
131139
}
132140
}

0 commit comments

Comments
 (0)