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

Commit 9ded0e2

Browse files
authored
Merge pull request #552 from radu-matei/signature-provider
Add initial tooltip for function signature provider
2 parents 484d12f + 3630a96 commit 9ded0e2

File tree

2 files changed

+141
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)