diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-inlay-hint-provider.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-inlay-hint-provider.ts index 502da3e66..bcc2e26c9 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-inlay-hint-provider.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-inlay-hint-provider.ts @@ -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; } @@ -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, + }; +}; diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds-inlay-hint-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds-inlay-hint-provider.test.ts index 4685f3d58..4f08f7b72 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds-inlay-hint-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds-inlay-hint-provider.test.ts @@ -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'; @@ -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 => { +const getActualInlayHints = async (code: string): Promise => { + 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 => { const document = await parse(code); const inlayHints = await inlayHintProvider.getInlayHints(document, { range: document.parseResult.value.$cstNode!.range, @@ -122,7 +197,7 @@ const getActualInlayHints = async (code: string): Promise { +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);