Skip to content

Commit

Permalink
feat: tooltips for inlay hints (#721)
Browse files Browse the repository at this point in the history
### 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.
  • Loading branch information
lars-reimann authored Nov 3, 2023
1 parent 64b9e07 commit 3e71cad
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { AbstractInlayHintProvider, AstNode, InlayHintAcceptor } from 'langium';
import { AbstractInlayHintProvider, AstNode, DocumentationProvider, InlayHintAcceptor } from 'langium';
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { isSdsArgument, isSdsBlockLambdaResult, isSdsPlaceholder, isSdsYield } from '../generated/ast.js';
import { isPositionalArgument } from '../helpers/nodeProperties.js';
import { InlayHintKind } from 'vscode-languageserver';
import { InlayHintKind, MarkupContent } from 'vscode-languageserver';
import { NamedType } from '../typing/model.js';

export class SafeDsInlayHintProvider extends AbstractInlayHintProvider {
private readonly documentationProvider: DocumentationProvider;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
super();

this.documentationProvider = services.documentation.DocumentationProvider;
this.nodeMapper = services.helpers.NodeMapper;
this.typeComputer = services.types.TypeComputer;
}
Expand All @@ -32,15 +35,33 @@ export class SafeDsInlayHintProvider extends AbstractInlayHintProvider {
position: cstNode.range.start,
label: `${parameter.name} = `,
kind: InlayHintKind.Parameter,
tooltip: createTooltip(this.documentationProvider.getDocumentation(parameter)),
});
}
} else if (isSdsBlockLambdaResult(node) || isSdsPlaceholder(node) || isSdsYield(node)) {
const type = this.typeComputer.computeType(node);
let tooltip: MarkupContent | undefined = undefined;
if (type instanceof NamedType) {
tooltip = createTooltip(this.documentationProvider.getDocumentation(type.declaration));
}

acceptor({
position: cstNode.range.end,
label: `: ${type}`,
kind: InlayHintKind.Type,
tooltip,
});
}
}
}

const createTooltip = (documentation: string | undefined): MarkupContent | undefined => {
if (!documentation) {
return undefined;
}

return {
kind: 'markdown',
value: documentation,
};
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { clearDocuments, parseHelper } from 'langium/test';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { Position } from 'vscode-languageserver';
import { createSafeDsServices } from '../../../src/language/index.js';
import { InlayHint, Position } from 'vscode-languageserver';
import { NodeFileSystem } from 'langium/node';
import { findTestChecks } from '../../helpers/testChecks.js';
import { URI } from 'langium';
Expand Down Expand Up @@ -91,16 +91,91 @@ describe('SafeDsInlayHintProvider', async () => {
`,
},
];

it.each(testCases)('should assign the correct inlay hints ($testName)', async ({ code }) => {
const actualInlayHints = await getActualInlayHints(code);
const expectedInlayHints = getExpectedInlayHints(code);
const actualInlayHints = await getActualSimpleInlayHints(code);
const expectedInlayHints = getExpectedSimpleInlayHints(code);

expect(actualInlayHints).toStrictEqual(expectedInlayHints);
});

it('should set the documentation of parameters as tooltip', async () => {
const code = `
/**
* @param p Lorem ipsum.
*/
fun f(p: Int)
pipeline myPipeline {
f(1);
}
`;
const actualInlayHints = await getActualInlayHints(code);
const firstInlayHint = actualInlayHints?.[0];

expect(firstInlayHint?.tooltip).toStrictEqual({ kind: 'markdown', value: 'Lorem ipsum.' });
});

it.each([
{
testName: 'class',
code: `
/**
* Lorem ipsum.
*/
class C()
pipeline myPipeline {
val a = C();
}
`,
},
{
testName: 'enum',
code: `
/**
* Lorem ipsum.
*/
enum E
fun f() -> e: E
pipeline myPipeline {
val a = f();
}
`,
},
{
testName: 'enum variant',
code: `
enum E {
/**
* Lorem ipsum.
*/
V
}
pipeline myPipeline {
val a = E.V;
}
`,
},
])('should set the documentation of named types as tooltip', async ({ code }) => {
const actualInlayHints = await getActualInlayHints(code);
const firstInlayHint = actualInlayHints?.[0];

expect(firstInlayHint?.tooltip).toStrictEqual({ kind: 'markdown', value: 'Lorem ipsum.' });
});
});

const getActualInlayHints = async (code: string): Promise<SimpleInlayHint[] | undefined> => {
const getActualInlayHints = async (code: string): Promise<InlayHint[] | undefined> => {
const document = await parse(code);
return inlayHintProvider.getInlayHints(document, {
range: document.parseResult.value.$cstNode!.range,
textDocument: { uri: document.textDocument.uri },
});
};

const getActualSimpleInlayHints = async (code: string): Promise<SimpleInlayHint[] | undefined> => {
const document = await parse(code);
const inlayHints = await inlayHintProvider.getInlayHints(document, {
range: document.parseResult.value.$cstNode!.range,
Expand All @@ -122,7 +197,7 @@ const getActualInlayHints = async (code: string): Promise<SimpleInlayHint[] | un
});
};

const getExpectedInlayHints = (code: string): SimpleInlayHint[] => {
const getExpectedSimpleInlayHints = (code: string): SimpleInlayHint[] => {
const testChecks = findTestChecks(code, URI.file('file:///test.sdstest'), { failIfFewerRangesThanComments: true });
if (testChecks.isErr) {
throw new Error(testChecks.error.message);
Expand Down

0 comments on commit 3e71cad

Please sign in to comment.