Skip to content

Commit 226b26d

Browse files
Handle InlayHint filtering clientside and allow the user to choose InlayHints (#68)
* Add `InlayHintProvider` class to request and filter InlayHints clientside !! This causes duplicates with the old InlayHint implementation * Override `LanguageClient` to disable the automatic duplicate InlayHints * Add a whitespace to restore layout and include markdown in the settings description * Rework the Doc comment and enhance the markdown preview * Apply Padding by parsing the response with `padLeft?` and `padRight?` fields * Change `padRight` to `paddingRight` and remove not implemented left padding * Add left padding according to LSP spec and add a README mention * Register the `InlayHintsProvider` for literate effekt * rename to `inlayHintsProvider` to match conventions * WIP * Rework the InlayHintsProvider by using converters * Send a strongly typed LSP req instead of a raw Sting * Clean up the inlay hints provider * Fix whitespace and remove comments
1 parent 07b504c commit 226b26d

4 files changed

Lines changed: 97 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ With this setup the extension should start the server when an Effekt file is ope
2222
This extension supports inlay hints that can display useful information about Effekt programs that is not explicitly written out in the source code.
2323
For example, this includes showing inferred types and [Effekt captures](https://effekt-lang.org/tour/captures).
2424
You can enable inlay hints in the [VSCode settings](vscode://settings/editor.inlayHints.enabled) at `Editor › Inlay Hints: Enabled`.
25+
Currently you can choose if you want to display the capture inlay hints at `Effekt › Inlay Hints: Captures`.
2526

2627
## License
2728

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@
148148
"type": "boolean",
149149
"default": false,
150150
"markdownDescription": "Connect to a running LSP server in debug mode. This setting is only for development of the VSCode plugin and the Effekt compiler.\n\n⚠️ **WARNING:** For this option to work, you first need to start `effekt --server --debug` as a separate process."
151+
},
152+
"effekt.inlayHints.captures": {
153+
"type": "boolean",
154+
"default": true,
155+
"markdownDescription": "Show capture inlay hints:\n\n ```effekt\n'{io}' def main(): Unit = { println(\" Hello Effekt! \") }\n```"
151156
}
152157
}
153158
},

src/extension.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,32 @@ import {
1010
} from 'vscode-languageclient/node';
1111
import { EffektManager } from './effektManager';
1212
import { EffektIRContentProvider } from './irProvider';
13-
13+
import { InlayHintProvider } from './inlayHintsProvider';
1414
import * as net from 'net';
1515

16-
let client: LanguageClient;
16+
/**
17+
* Overrides the `registerFeature` method to disable the built-in inlay hints feature.
18+
*
19+
* By default the LanguageClient provides inlay hints automatically, which does not allow
20+
* for filtering Inlay Hints based on their 'data'-field. We use the 'data'-field to allow
21+
* the user to select which inlay hints the extension should show.
22+
*
23+
* By doing this, we retain full control over how inlay hints are displayed, allowing us to
24+
* implement custom logic.
25+
*
26+
* Note: This approach relies on identifying the inlay hints feature by its constructor name
27+
* (`InlayHintsFeature`). If the LSP implementation changes, this logic may need to be updated.
28+
*/
29+
class EffektLanguageClient extends LanguageClient {
30+
public registerFeature(feature: any) {
31+
if (feature.constructor.name === 'InlayHintsFeature') {
32+
return;
33+
}
34+
super.registerFeature(feature);
35+
}
36+
}
37+
38+
let client: EffektLanguageClient;
1739
let effektManager: EffektManager;
1840

1941
function registerCommands(context: vscode.ExtensionContext) {
@@ -160,7 +182,7 @@ export async function activate(context: vscode.ExtensionContext) {
160182
diagnosticCollectionName: "effekt"
161183
};
162184

163-
client = new LanguageClient(
185+
client = new EffektLanguageClient(
164186
'effektLanguageServer',
165187
'Effekt Language Server',
166188
serverOptions,
@@ -258,6 +280,16 @@ export async function activate(context: vscode.ExtensionContext) {
258280

259281
await client.start();
260282
context.subscriptions.push(client);
283+
284+
vscode.languages.registerInlayHintsProvider(
285+
{ scheme: 'file', language: 'effekt' },
286+
new InlayHintProvider(client)
287+
);
288+
289+
vscode.languages.registerInlayHintsProvider(
290+
{ scheme: 'file', language: 'literate effekt' },
291+
new InlayHintProvider(client)
292+
);
261293
}
262294

263295
export function deactivate(): Thenable<void> | undefined {

src/inlayHintsProvider.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as vscode from 'vscode';
2+
import { Code2ProtocolConverter, LanguageClient, Protocol2CodeConverter } from 'vscode-languageclient/node';
3+
import {
4+
InlayHintRequest,
5+
InlayHintParams,
6+
InlayHint as LspInlayHint
7+
} from 'vscode-languageserver-protocol';
8+
9+
export class InlayHintProvider implements vscode.InlayHintsProvider {
10+
private client: LanguageClient;
11+
private protocol2code: Protocol2CodeConverter;
12+
private code2protocol : Code2ProtocolConverter;
13+
14+
constructor(client: LanguageClient) {
15+
this.client = client;
16+
this.protocol2code = client.protocol2CodeConverter;
17+
this.code2protocol = client.code2ProtocolConverter;
18+
}
19+
20+
async provideInlayHints(
21+
document: vscode.TextDocument,
22+
range: vscode.Range,
23+
_token: vscode.CancellationToken
24+
): Promise<vscode.InlayHint[]> {
25+
const config = vscode.workspace.getConfiguration('effekt');
26+
const showCaptureHints = config.get<boolean>('inlayHints.captures', true);
27+
28+
const editorHintsEnabled = vscode.workspace
29+
.getConfiguration('editor.inlayHints')
30+
.get<string>('enabled', 'on');
31+
if (editorHintsEnabled === 'off') {
32+
return [];
33+
}
34+
35+
const params: InlayHintParams = {
36+
textDocument: this.code2protocol.asTextDocumentIdentifier(document),
37+
range: this.code2protocol.asRange(range)
38+
};
39+
40+
const response = (await this.client.sendRequest(
41+
InlayHintRequest.type,
42+
params
43+
)) as LspInlayHint[] | null | undefined;
44+
45+
if (!response) {
46+
return [];
47+
}
48+
49+
const filtered = response.filter(h => {
50+
return !(h.data === 'capture' && !showCaptureHints);
51+
});
52+
53+
const hints = await this.protocol2code.asInlayHints(filtered, _token);
54+
return hints || [];
55+
}
56+
}

0 commit comments

Comments
 (0)