Skip to content

Commit 3e71cad

Browse files
authored
feat: tooltips for inlay hints (#721)
### Summary of Changes Show tooltips on inlay code hints: * For the corresponding parameter, its documentation is displayed. * For named types, the documentation of the corresponding declaration is used.
1 parent 64b9e07 commit 3e71cad

File tree

2 files changed

+105
-9
lines changed

2 files changed

+105
-9
lines changed

packages/safe-ds-lang/src/language/lsp/safe-ds-inlay-hint-provider.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import { AbstractInlayHintProvider, AstNode, InlayHintAcceptor } from 'langium';
1+
import { AbstractInlayHintProvider, AstNode, DocumentationProvider, InlayHintAcceptor } from 'langium';
22
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
33
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
44
import { SafeDsServices } from '../safe-ds-module.js';
55
import { isSdsArgument, isSdsBlockLambdaResult, isSdsPlaceholder, isSdsYield } from '../generated/ast.js';
66
import { isPositionalArgument } from '../helpers/nodeProperties.js';
7-
import { InlayHintKind } from 'vscode-languageserver';
7+
import { InlayHintKind, MarkupContent } from 'vscode-languageserver';
8+
import { NamedType } from '../typing/model.js';
89

910
export class SafeDsInlayHintProvider extends AbstractInlayHintProvider {
11+
private readonly documentationProvider: DocumentationProvider;
1012
private readonly nodeMapper: SafeDsNodeMapper;
1113
private readonly typeComputer: SafeDsTypeComputer;
1214

1315
constructor(services: SafeDsServices) {
1416
super();
1517

18+
this.documentationProvider = services.documentation.DocumentationProvider;
1619
this.nodeMapper = services.helpers.NodeMapper;
1720
this.typeComputer = services.types.TypeComputer;
1821
}
@@ -32,15 +35,33 @@ export class SafeDsInlayHintProvider extends AbstractInlayHintProvider {
3235
position: cstNode.range.start,
3336
label: `${parameter.name} = `,
3437
kind: InlayHintKind.Parameter,
38+
tooltip: createTooltip(this.documentationProvider.getDocumentation(parameter)),
3539
});
3640
}
3741
} else if (isSdsBlockLambdaResult(node) || isSdsPlaceholder(node) || isSdsYield(node)) {
3842
const type = this.typeComputer.computeType(node);
43+
let tooltip: MarkupContent | undefined = undefined;
44+
if (type instanceof NamedType) {
45+
tooltip = createTooltip(this.documentationProvider.getDocumentation(type.declaration));
46+
}
47+
3948
acceptor({
4049
position: cstNode.range.end,
4150
label: `: ${type}`,
4251
kind: InlayHintKind.Type,
52+
tooltip,
4353
});
4454
}
4555
}
4656
}
57+
58+
const createTooltip = (documentation: string | undefined): MarkupContent | undefined => {
59+
if (!documentation) {
60+
return undefined;
61+
}
62+
63+
return {
64+
kind: 'markdown',
65+
value: documentation,
66+
};
67+
};

packages/safe-ds-lang/tests/language/lsp/safe-ds-inlay-hint-provider.test.ts

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
22
import { clearDocuments, parseHelper } from 'langium/test';
3-
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
4-
import { Position } from 'vscode-languageserver';
3+
import { createSafeDsServices } from '../../../src/language/index.js';
4+
import { InlayHint, Position } from 'vscode-languageserver';
55
import { NodeFileSystem } from 'langium/node';
66
import { findTestChecks } from '../../helpers/testChecks.js';
77
import { URI } from 'langium';
@@ -91,16 +91,91 @@ describe('SafeDsInlayHintProvider', async () => {
9191
`,
9292
},
9393
];
94-
9594
it.each(testCases)('should assign the correct inlay hints ($testName)', async ({ code }) => {
96-
const actualInlayHints = await getActualInlayHints(code);
97-
const expectedInlayHints = getExpectedInlayHints(code);
95+
const actualInlayHints = await getActualSimpleInlayHints(code);
96+
const expectedInlayHints = getExpectedSimpleInlayHints(code);
9897

9998
expect(actualInlayHints).toStrictEqual(expectedInlayHints);
10099
});
100+
101+
it('should set the documentation of parameters as tooltip', async () => {
102+
const code = `
103+
/**
104+
* @param p Lorem ipsum.
105+
*/
106+
fun f(p: Int)
107+
108+
pipeline myPipeline {
109+
f(1);
110+
}
111+
`;
112+
const actualInlayHints = await getActualInlayHints(code);
113+
const firstInlayHint = actualInlayHints?.[0];
114+
115+
expect(firstInlayHint?.tooltip).toStrictEqual({ kind: 'markdown', value: 'Lorem ipsum.' });
116+
});
117+
118+
it.each([
119+
{
120+
testName: 'class',
121+
code: `
122+
/**
123+
* Lorem ipsum.
124+
*/
125+
class C()
126+
127+
pipeline myPipeline {
128+
val a = C();
129+
}
130+
`,
131+
},
132+
{
133+
testName: 'enum',
134+
code: `
135+
/**
136+
* Lorem ipsum.
137+
*/
138+
enum E
139+
140+
fun f() -> e: E
141+
142+
pipeline myPipeline {
143+
val a = f();
144+
}
145+
`,
146+
},
147+
{
148+
testName: 'enum variant',
149+
code: `
150+
enum E {
151+
/**
152+
* Lorem ipsum.
153+
*/
154+
V
155+
}
156+
157+
pipeline myPipeline {
158+
val a = E.V;
159+
}
160+
`,
161+
},
162+
])('should set the documentation of named types as tooltip', async ({ code }) => {
163+
const actualInlayHints = await getActualInlayHints(code);
164+
const firstInlayHint = actualInlayHints?.[0];
165+
166+
expect(firstInlayHint?.tooltip).toStrictEqual({ kind: 'markdown', value: 'Lorem ipsum.' });
167+
});
101168
});
102169

103-
const getActualInlayHints = async (code: string): Promise<SimpleInlayHint[] | undefined> => {
170+
const getActualInlayHints = async (code: string): Promise<InlayHint[] | undefined> => {
171+
const document = await parse(code);
172+
return inlayHintProvider.getInlayHints(document, {
173+
range: document.parseResult.value.$cstNode!.range,
174+
textDocument: { uri: document.textDocument.uri },
175+
});
176+
};
177+
178+
const getActualSimpleInlayHints = async (code: string): Promise<SimpleInlayHint[] | undefined> => {
104179
const document = await parse(code);
105180
const inlayHints = await inlayHintProvider.getInlayHints(document, {
106181
range: document.parseResult.value.$cstNode!.range,
@@ -122,7 +197,7 @@ const getActualInlayHints = async (code: string): Promise<SimpleInlayHint[] | un
122197
});
123198
};
124199

125-
const getExpectedInlayHints = (code: string): SimpleInlayHint[] => {
200+
const getExpectedSimpleInlayHints = (code: string): SimpleInlayHint[] => {
126201
const testChecks = findTestChecks(code, URI.file('file:///test.sdstest'), { failIfFewerRangesThanComments: true });
127202
if (testChecks.isErr) {
128203
throw new Error(testChecks.error.message);

0 commit comments

Comments
 (0)