Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: document symbol provider #659

Merged
merged 12 commits into from
Oct 21, 2023
446 changes: 222 additions & 224 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 8 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
}
],
"engines": {
"vscode": "^1.82.0"
"vscode": "^1.83.0"
},
"contributes": {
"languages": [
Expand Down Expand Up @@ -103,25 +103,23 @@
"dependencies": {
"chalk": "^5.3.0",
"chevrotain": "^11.0.3",
"commander": "^11.0.0",
"commander": "^11.1.0",
"glob": "^10.3.10",
"langium": "^2.0.2",
"radash": "^11.0.0",
"true-myth": "^7.1.0",
"vscode-languageclient": "^9.0.1",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-types": "^3.17.5",
"vscode-uri": "^3.0.7"
"vscode-languageserver": "^9.0.1"
},
"devDependencies": {
"@lars-reimann/eslint-config": "^5.1.3",
"@lars-reimann/eslint-config": "^5.1.4",
"@lars-reimann/prettier-config": "^5.0.0",
"@types/node": "^18.18.1",
"@types/vscode": "^1.82.0",
"@types/node": "^18.18.6",
"@types/vscode": "^1.83.1",
"@vitest/coverage-v8": "^0.34.6",
"@vitest/ui": "^0.34.6",
"concurrently": "^8.2.1",
"esbuild": "^0.19.4",
"concurrently": "^8.2.2",
"esbuild": "^0.19.5",
"esbuild-plugin-copy": "^2.1.1",
"langium-cli": "^2.0.1",
"typescript": "^5.2.2",
Expand Down
89 changes: 89 additions & 0 deletions src/language/lsp/safe-ds-document-symbol-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { AstNode, DefaultDocumentSymbolProvider, LangiumDocument } from 'langium';
import { DocumentSymbol, SymbolTag } from 'vscode-languageserver';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js';
import {
isSdsAnnotatedObject,
isSdsAnnotation,
isSdsAttribute,
isSdsClass,
isSdsEnumVariant,
isSdsFunction,
isSdsPipeline,
isSdsSegment,
} from '../generated/ast.js';
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';

export class SafeDsDocumentSymbolProvider extends DefaultDocumentSymbolProvider {
private readonly builtinAnnotations: SafeDsAnnotations;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
super(services);

this.builtinAnnotations = services.builtins.Annotations;
this.typeComputer = services.types.TypeComputer;
}

protected override getSymbol(document: LangiumDocument, node: AstNode): DocumentSymbol[] {
const cstNode = node.$cstNode;
const nameNode = this.nameProvider.getNameNode(node);
if (nameNode && cstNode) {
const name = this.nameProvider.getName(node);
return [
{
name: name ?? nameNode.text,
kind: this.nodeKindProvider.getSymbolKind(node),
tags: this.getTags(node),
detail: this.getDetails(node),
range: cstNode.range,
selectionRange: nameNode.range,
children: this.getChildSymbols(document, node),
},
];
} else {
return this.getChildSymbols(document, node) || [];
}
}

protected override getChildSymbols(document: LangiumDocument, node: AstNode): DocumentSymbol[] | undefined {
if (this.isLeaf(node)) {
return undefined;
} else if (isSdsClass(node)) {
if (node.body) {
return super.getChildSymbols(document, node.body);
} else {
return undefined;
}
} else {
return super.getChildSymbols(document, node);
}
}

private getDetails(node: AstNode): string | undefined {
if (isSdsFunction(node) || isSdsSegment(node)) {
const type = this.typeComputer.computeType(node);
return type?.toString();
}
return undefined;
}

private getTags(node: AstNode): SymbolTag[] | undefined {
if (isSdsAnnotatedObject(node) && this.builtinAnnotations.isDeprecated(node)) {
return [SymbolTag.Deprecated];
} else {
return undefined;
}
}

private isLeaf(node: AstNode): boolean {
return (
isSdsAnnotation(node) ||
isSdsAttribute(node) ||
isSdsEnumVariant(node) ||
isSdsFunction(node) ||
isSdsPipeline(node) ||
isSdsSegment(node)
);
}
}
93 changes: 93 additions & 0 deletions src/language/lsp/safe-ds-node-kind-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { AstNode, AstNodeDescription, hasContainerOfType, isAstNode, NodeKindProvider } from 'langium';
import { CompletionItemKind, SymbolKind } from 'vscode-languageserver';
import {
isSdsClass,
isSdsFunction,
SdsAnnotation,
SdsAttribute,
SdsBlockLambdaResult,
SdsClass,
SdsEnum,
SdsEnumVariant,
SdsFunction,
SdsModule,
SdsParameter,
SdsPipeline,
SdsPlaceholder,
SdsResult,
SdsSegment,
SdsTypeParameter,
} from '../generated/ast.js';

export class SafeDsNodeKindProvider implements NodeKindProvider {
getSymbolKind(nodeOrDescription: AstNode | AstNodeDescription): SymbolKind {
// The WorkspaceSymbolProvider only passes descriptions, where the node might be undefined
const node = this.getNode(nodeOrDescription);
if (isSdsFunction(node) && hasContainerOfType(node, isSdsClass)) {
return SymbolKind.Method;
}

const type = this.getNodeType(nodeOrDescription);
switch (type) {
case SdsAnnotation:
return SymbolKind.Interface;
case SdsAttribute:
return SymbolKind.Property;
/* c8 ignore next 2 */
case SdsBlockLambdaResult:
return SymbolKind.Variable;
case SdsClass:
return SymbolKind.Class;
case SdsEnum:
return SymbolKind.Enum;
case SdsEnumVariant:
return SymbolKind.EnumMember;
case SdsFunction:
return SymbolKind.Function;
case SdsModule:
return SymbolKind.Package;
/* c8 ignore next 2 */
case SdsParameter:
return SymbolKind.Variable;
case SdsPipeline:
return SymbolKind.Function;
/* c8 ignore next 2 */
case SdsPlaceholder:
return SymbolKind.Variable;
/* c8 ignore next 2 */
case SdsResult:
return SymbolKind.Variable;
case SdsSegment:
return SymbolKind.Function;
/* c8 ignore next 2 */
case SdsTypeParameter:
return SymbolKind.TypeParameter;
/* c8 ignore next 2 */
default:
return SymbolKind.Null;
}
}

/* c8 ignore start */
getCompletionItemKind(_nodeOrDescription: AstNode | AstNodeDescription) {
return CompletionItemKind.Reference;
}

/* c8 ignore stop */

private getNode(nodeOrDescription: AstNode | AstNodeDescription): AstNode | undefined {
if (isAstNode(nodeOrDescription)) {
return nodeOrDescription;
} /* c8 ignore start */ else {
return nodeOrDescription.node;
} /* c8 ignore stop */
}

private getNodeType(nodeOrDescription: AstNode | AstNodeDescription): string {
if (isAstNode(nodeOrDescription)) {
return nodeOrDescription.$type;
} /* c8 ignore start */ else {
return nodeOrDescription.type;
} /* c8 ignore stop */
}
}
4 changes: 2 additions & 2 deletions src/language/lsp/safe-ds-semantic-token-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
AbstractSemanticTokenProvider,
AllSemanticTokenTypes,
AstNode,
DefaultSemanticTokenOptions,
hasContainerOfType,
SemanticTokenAcceptor,
DefaultSemanticTokenOptions,
} from 'langium';
import {
isSdsAnnotation,
Expand All @@ -29,7 +29,7 @@ import {
isSdsTypeParameter,
isSdsTypeParameterConstraint,
} from '../generated/ast.js';
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-types';
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';

Expand Down
2 changes: 1 addition & 1 deletion src/language/partialEvaluation/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ class UnknownEvaluatedNodeClass extends EvaluatedNode {
}

override toString(): string {
return '$unknown';
return '?';
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { SafeDsPartialEvaluator } from './partialEvaluation/safe-ds-partial-eval
import { SafeDsSemanticTokenProvider } from './lsp/safe-ds-semantic-token-provider.js';
import { SafeDsTypeChecker } from './typing/safe-ds-type-checker.js';
import { SafeDsCoreTypes } from './typing/safe-ds-core-types.js';
import { SafeDsNodeKindProvider } from './lsp/safe-ds-node-kind-provider.js';
import { SafeDsDocumentSymbolProvider } from './lsp/safe-ds-document-symbol-provider.js';

/**
* Declaration of custom services - add your own service classes here.
Expand Down Expand Up @@ -75,6 +77,7 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
NodeMapper: (services) => new SafeDsNodeMapper(services),
},
lsp: {
DocumentSymbolProvider: (services) => new SafeDsDocumentSymbolProvider(services),
Formatter: () => new SafeDsFormatter(),
SemanticTokenProvider: (services) => new SafeDsSemanticTokenProvider(services),
},
Expand All @@ -99,6 +102,9 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
export type SafeDsSharedServices = LangiumSharedServices;

export const SafeDsSharedModule: Module<SafeDsSharedServices, DeepPartial<SafeDsSharedServices>> = {
lsp: {
NodeKindProvider: () => new SafeDsNodeKindProvider(),
},
workspace: {
WorkspaceManager: (services) => new SafeDsWorkspaceManager(services),
},
Expand Down
2 changes: 1 addition & 1 deletion src/language/typing/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ class UnknownTypeClass extends Type {
}

toString(): string {
return '$Unknown';
return '?';
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/builtins/deprecated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { SafeDsServices } from '../../safe-ds-module.js';
import { isRequiredParameter } from '../../helpers/nodeProperties.js';
import { parameterCanBeAnnotated } from '../other/declarations/annotationCalls.js';
import { DiagnosticTag } from 'vscode-languageserver-types';
import { DiagnosticTag } from 'vscode-languageserver';

export const CODE_DEPRECATED_ASSIGNED_RESULT = 'deprecated/assigned-result';
export const CODE_DEPRECATED_CALLED_ANNOTATION = 'deprecated/called-annotation';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { getContainerOfType, ValidationAcceptor } from 'langium';
import { SafeDsServices } from '../../../safe-ds-module.js';
import { statementsOrEmpty } from '../../../helpers/nodeProperties.js';
import { last } from 'radash';
import { DiagnosticTag } from 'vscode-languageserver-types';
import { DiagnosticTag } from 'vscode-languageserver';

export const CODE_PLACEHOLDER_ALIAS = 'placeholder/alias';
export const CODE_PLACEHOLDER_UNUSED = 'placeholder/unused';
Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/other/declarations/segments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SdsSegment } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { parametersOrEmpty, resultsOrEmpty } from '../../../helpers/nodeProperties.js';
import { SafeDsServices } from '../../../safe-ds-module.js';
import { DiagnosticTag } from 'vscode-languageserver-types';
import { DiagnosticTag } from 'vscode-languageserver';

export const CODE_SEGMENT_DUPLICATE_YIELD = 'segment/duplicate-yield';
export const CODE_SEGMENT_UNASSIGNED_RESULT = 'segment/unassigned-result';
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { parseHelper } from 'langium/test';
import { LangiumServices, URI } from 'langium';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import { TestDescriptionError } from './testDescription.js';

let nextId = 0;
Expand Down
2 changes: 1 addition & 1 deletion tests/language/builtins/builtinFilesCorrectness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { listBuiltinFiles } from '../../../src/language/builtins/fileFinder.js';
import { uriToShortenedResourceName } from '../../../src/helpers/resources.js';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { NodeFileSystem } from 'langium/node';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import { isEmpty } from 'radash';
import { URI } from 'langium';
import { locationToString } from '../../helpers/location.js';
Expand Down
2 changes: 1 addition & 1 deletion tests/language/lsp/formatting/creator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { listTestSafeDsFiles, uriToShortenedTestResourceName } from '../../../helpers/testResources.js';
import fs from 'fs';
import { Diagnostic } from 'vscode-languageserver-types';
import { Diagnostic } from 'vscode-languageserver';
import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js';
import { EmptyFileSystem, URI } from 'langium';
import { getSyntaxErrors } from '../../../helpers/diagnostics.js';
Expand Down
Loading