Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.

Commit 3b529cf

Browse files
committed
Add initial tooltip for function signature
1 parent 484d12f commit 3b529cf

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

src/extension.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from 'vscode-languageclient';
2727

2828
import { RLSConfiguration } from './configuration';
29+
import { SignatureHelpProvider } from './providers/signatureHelpProvider';
2930
import { checkForRls, ensureToolchain, rustupUpdate } from './rustup';
3031
import { startSpinner, stopSpinner } from './spinner';
3132
import { activateTaskProvider, Execution, runCargoCommand } from './tasks';
@@ -277,6 +278,14 @@ class ClientWorkspace {
277278
this.registerCommands(context);
278279
this.disposables.push(activateTaskProvider(this.folder));
279280
this.disposables.push(this.lc.start());
281+
this.disposables.push(
282+
languages.registerSignatureHelpProvider(
283+
{ language: 'rust' },
284+
new SignatureHelpProvider(this.lc),
285+
'(',
286+
',',
287+
),
288+
);
280289
}
281290

282291
public async stop() {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import * as vscode from 'vscode';
2+
import { HoverRequest, LanguageClient } from 'vscode-languageclient';
3+
4+
export class SignatureHelpProvider implements vscode.SignatureHelpProvider {
5+
private languageClient: LanguageClient;
6+
private previousFunctionPosition?: vscode.Position;
7+
8+
constructor(lc: LanguageClient) {
9+
this.languageClient = lc;
10+
}
11+
12+
public provideSignatureHelp(
13+
document: vscode.TextDocument,
14+
position: vscode.Position,
15+
token: vscode.CancellationToken,
16+
context: vscode.SignatureHelpContext,
17+
): vscode.ProviderResult<vscode.SignatureHelp> {
18+
// the current signature help provider uses the hover information from RLS
19+
// and it only has a string representation of the function signature.
20+
// This check makes sure we can easily show the tooltip for multiple parameters, separated by `,`
21+
if (context.triggerCharacter === '(') {
22+
this.previousFunctionPosition = position;
23+
return this.provideHover(
24+
this.languageClient,
25+
document,
26+
position,
27+
token,
28+
).then(hover => {
29+
return this.hoverToSignatureHelp(hover);
30+
});
31+
} else if (context.triggerCharacter === ',') {
32+
if (this.previousFunctionPosition) {
33+
return this.provideHover(
34+
this.languageClient,
35+
document,
36+
this.previousFunctionPosition,
37+
token,
38+
).then(hover => {
39+
return this.hoverToSignatureHelp(hover);
40+
});
41+
} else {
42+
return null;
43+
}
44+
} else {
45+
if (context.isRetrigger === false) {
46+
this.previousFunctionPosition = undefined;
47+
}
48+
return null;
49+
}
50+
}
51+
52+
private provideHover(
53+
lc: LanguageClient,
54+
document: vscode.TextDocument,
55+
position: vscode.Position,
56+
token: vscode.CancellationToken,
57+
): Promise<vscode.Hover> {
58+
return new Promise((resolve, reject) => {
59+
lc.sendRequest(
60+
HoverRequest.type,
61+
lc.code2ProtocolConverter.asTextDocumentPositionParams(
62+
document,
63+
position.translate(0, -1),
64+
),
65+
token,
66+
).then(
67+
data => {
68+
resolve(lc.protocol2CodeConverter.asHover(data));
69+
},
70+
error => {
71+
reject(error);
72+
},
73+
);
74+
});
75+
}
76+
77+
private hoverToSignatureHelp(
78+
hover: vscode.Hover,
79+
): vscode.SignatureHelp | undefined {
80+
/*
81+
The contents of a hover result has the following structure:
82+
contents:Array[2]
83+
0:Object
84+
value:"
85+
```rust
86+
pub fn write(output: &mut dyn Write, args: Arguments) -> Result
87+
```
88+
"
89+
1:Object
90+
value:"The `write` function takes an output stream, and an `Arguments` struct
91+
that can be precompiled with the `format_args!` macro.
92+
The arguments will be formatted according to the specified format string
93+
into the output stream provided.
94+
# Examples
95+
RLS uses the function below to create the tooltip contents shown above:
96+
fn create_tooltip(
97+
the_type: String,
98+
doc_url: Option<String>,
99+
context: Option<String>,
100+
docs: Option<String>,
101+
) -> Vec<MarkedString> {}
102+
This means the first object is the type - function signature,
103+
but for the following, there is no way of certainly knowing which is the
104+
function documentation that we want to display in the tooltip.
105+
106+
Assuming the context is never populated for a function definition (this might be wrong
107+
and needs further validation, but initial tests show it to hold true in most cases), and
108+
we also assume that most functions contain rather documentation, than just a URL without
109+
any inline documentation, we check the length of contents, and we assume that if there are:
110+
- two objects, they are the signature and docs, and docs is contents[1]
111+
- three objects, they are the signature, URL and docs, and docs is contents[2]
112+
- four objects -- all of them, docs is contents[3]
113+
See https://github.com/rust-lang/rls/blob/master/rls/src/actions/hover.rs#L487-L508.
114+
*/
115+
116+
// we remove the markdown formatting for the label, as it only accepts strings
117+
const label = (hover.contents[0] as vscode.MarkdownString).value
118+
.replace('```rust', '')
119+
.replace('```', '');
120+
121+
// the signature help tooltip is activated on `(` or `,`
122+
// and without this, it could show the tooltip after non-functions
123+
if (!label.includes('fn')) {
124+
return undefined;
125+
}
126+
127+
let doc: vscode.MarkdownString | undefined;
128+
switch (hover.contents.length) {
129+
case 1:
130+
doc = undefined;
131+
break;
132+
case 2:
133+
doc = hover.contents[1] as vscode.MarkdownString;
134+
break;
135+
case 3:
136+
doc = hover.contents[2] as vscode.MarkdownString;
137+
break;
138+
case 4:
139+
doc = hover.contents[3] as vscode.MarkdownString;
140+
break;
141+
}
142+
143+
const si = new vscode.SignatureInformation(label, doc);
144+
145+
// without parsing the function definition, we don't have a way to get more info on parameters.
146+
// If RLS supports signature help requests in the future, we can update this.
147+
si.parameters = [];
148+
149+
const sh = new vscode.SignatureHelp();
150+
sh.signatures[0] = si;
151+
sh.activeSignature = 0;
152+
153+
return sh;
154+
}
155+
}

0 commit comments

Comments
 (0)