Skip to content

Commit 9fd5f0c

Browse files
authored
feat: type hierarchy provider (#737)
Closes #681 ### Summary of Changes <!-- Please provide a summary of changes in this pull request, ensuring all changes are explained. -->
1 parent 6c52868 commit 9fd5f0c

6 files changed

+700
-6
lines changed

packages/safe-ds-lang/src/language/lsp/safe-ds-call-hierarchy-provider.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ export class SafeDsCallHierarchyProvider extends AbstractCallHierarchyProvider {
114114
return undefined;
115115
}
116116

117-
const targetNode = findLeafNodeAtOffset(rootNode.$cstNode, it.segment.offset);
118-
if (!targetNode) {
117+
const targetCstNode = findLeafNodeAtOffset(rootNode.$cstNode, it.segment.offset);
118+
if (!targetCstNode) {
119119
/* c8 ignore next 2 */
120120
return undefined;
121121
}
122122

123-
const containingDeclaration = getContainerOfType(targetNode.astNode, isSdsDeclaration);
123+
const containingDeclaration = getContainerOfType(targetCstNode.astNode, isSdsDeclaration);
124124
if (isSdsParameter(containingDeclaration)) {
125125
// For parameters, we return their containing callable instead
126126
return getContainerOfType(containingDeclaration.$container, isSdsDeclaration);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* This file can be removed, once Langium supports the TypeHierarchyProvider directly.
3+
*/
4+
5+
/* c8 ignore start */
6+
import type { LangiumServices, LangiumSharedServices } from 'langium';
7+
import {
8+
addCallHierarchyHandler,
9+
addCodeActionHandler,
10+
addCodeLensHandler,
11+
addCompletionHandler,
12+
addConfigurationChangeHandler,
13+
addDiagnosticsHandler,
14+
addDocumentHighlightsHandler,
15+
addDocumentLinkHandler,
16+
addDocumentsHandler,
17+
addDocumentSymbolHandler,
18+
addExecuteCommandHandler,
19+
addFindReferencesHandler,
20+
addFoldingRangeHandler,
21+
addFormattingHandler,
22+
addGoToDeclarationHandler,
23+
addGotoDefinitionHandler,
24+
addGoToImplementationHandler,
25+
addGoToTypeDefinitionHandler,
26+
addHoverHandler,
27+
addInlayHintHandler,
28+
addRenameHandler,
29+
addSemanticTokenHandler,
30+
addSignatureHelpHandler,
31+
addWorkspaceSymbolHandler,
32+
createServerRequestHandler,
33+
DefaultLanguageServer,
34+
isOperationCancelled,
35+
URI,
36+
} from 'langium';
37+
import {
38+
type CancellationToken,
39+
type Connection,
40+
type HandlerResult,
41+
type InitializeParams,
42+
type InitializeResult,
43+
LSPErrorCodes,
44+
ResponseError,
45+
type ServerRequestHandler,
46+
type TypeHierarchySubtypesParams,
47+
type TypeHierarchySupertypesParams,
48+
} from 'vscode-languageserver';
49+
import { TypeHierarchyProvider } from './safe-ds-type-hierarchy-provider.js';
50+
51+
interface LangiumAddedServices {
52+
lsp: {
53+
TypeHierarchyProvider?: TypeHierarchyProvider;
54+
};
55+
}
56+
57+
export class SafeDsLanguageServer extends DefaultLanguageServer {
58+
protected override hasService(
59+
callback: (language: LangiumServices & LangiumAddedServices) => object | undefined,
60+
): boolean {
61+
return this.services.ServiceRegistry.all.some((language) => callback(language) !== undefined);
62+
}
63+
64+
protected override buildInitializeResult(params: InitializeParams): InitializeResult {
65+
const hasTypeHierarchyProvider = this.hasService((e) => e.lsp.TypeHierarchyProvider);
66+
const otherCapabilities = super.buildInitializeResult(params).capabilities;
67+
68+
return {
69+
capabilities: {
70+
...otherCapabilities,
71+
typeHierarchyProvider: hasTypeHierarchyProvider ? {} : undefined,
72+
},
73+
};
74+
}
75+
}
76+
77+
export const startLanguageServer = (services: LangiumSharedServices): void => {
78+
const connection = services.lsp.Connection;
79+
if (!connection) {
80+
throw new Error('Starting a language server requires the languageServer.Connection service to be set.');
81+
}
82+
83+
addDocumentsHandler(connection, services);
84+
addDiagnosticsHandler(connection, services);
85+
addCompletionHandler(connection, services);
86+
addFindReferencesHandler(connection, services);
87+
addDocumentSymbolHandler(connection, services);
88+
addGotoDefinitionHandler(connection, services);
89+
addGoToTypeDefinitionHandler(connection, services);
90+
addGoToImplementationHandler(connection, services);
91+
addDocumentHighlightsHandler(connection, services);
92+
addFoldingRangeHandler(connection, services);
93+
addFormattingHandler(connection, services);
94+
addCodeActionHandler(connection, services);
95+
addRenameHandler(connection, services);
96+
addHoverHandler(connection, services);
97+
addInlayHintHandler(connection, services);
98+
addSemanticTokenHandler(connection, services);
99+
addExecuteCommandHandler(connection, services);
100+
addSignatureHelpHandler(connection, services);
101+
addCallHierarchyHandler(connection, services);
102+
addTypeHierarchyHandler(connection, services);
103+
addCodeLensHandler(connection, services);
104+
addDocumentLinkHandler(connection, services);
105+
addConfigurationChangeHandler(connection, services);
106+
addGoToDeclarationHandler(connection, services);
107+
addWorkspaceSymbolHandler(connection, services);
108+
109+
connection.onInitialize((params) => {
110+
return services.lsp.LanguageServer.initialize(params);
111+
});
112+
connection.onInitialized((params) => {
113+
return services.lsp.LanguageServer.initialized(params);
114+
});
115+
116+
// Make the text document manager listen on the connection for open, change and close text document events.
117+
const documents = services.workspace.TextDocuments;
118+
documents.listen(connection);
119+
120+
// Start listening for incoming messages from the client.
121+
connection.listen();
122+
};
123+
124+
export const addTypeHierarchyHandler = function (connection: Connection, sharedServices: LangiumSharedServices): void {
125+
connection.languages.typeHierarchy.onPrepare(
126+
createServerRequestHandler((services, document, params, cancelToken) => {
127+
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
128+
if (typeHierarchyProvider) {
129+
return typeHierarchyProvider.prepareTypeHierarchy(document, params, cancelToken) ?? null;
130+
}
131+
return null;
132+
}, sharedServices),
133+
);
134+
135+
connection.languages.typeHierarchy.onSupertypes(
136+
createTypeHierarchyRequestHandler((services, params, cancelToken) => {
137+
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
138+
if (typeHierarchyProvider) {
139+
return typeHierarchyProvider.supertypes(params, cancelToken) ?? null;
140+
}
141+
return null;
142+
}, sharedServices),
143+
);
144+
145+
connection.languages.typeHierarchy.onSubtypes(
146+
createTypeHierarchyRequestHandler((services, params, cancelToken) => {
147+
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
148+
if (typeHierarchyProvider) {
149+
return typeHierarchyProvider.subtypes(params, cancelToken) ?? null;
150+
}
151+
return null;
152+
}, sharedServices),
153+
);
154+
};
155+
156+
export const createTypeHierarchyRequestHandler = function <
157+
P extends TypeHierarchySupertypesParams | TypeHierarchySubtypesParams,
158+
R,
159+
PR,
160+
E = void,
161+
>(
162+
serviceCall: (services: LangiumServices, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
163+
sharedServices: LangiumSharedServices,
164+
): ServerRequestHandler<P, R, PR, E> {
165+
const serviceRegistry = sharedServices.ServiceRegistry;
166+
return async (params: P, cancelToken: CancellationToken) => {
167+
const uri = URI.parse(params.item.uri);
168+
const language = serviceRegistry.getServices(uri);
169+
if (!language) {
170+
const message = `Could not find service instance for uri: '${uri.toString()}'`;
171+
// eslint-disable-next-line no-console
172+
console.error(message);
173+
throw new Error(message);
174+
}
175+
try {
176+
// eslint-disable-next-line @typescript-eslint/return-await
177+
return await serviceCall(language, params, cancelToken);
178+
} catch (err) {
179+
return responseError<E>(err);
180+
}
181+
};
182+
};
183+
184+
const responseError = function <E = void>(err: unknown): ResponseError<E> {
185+
if (isOperationCancelled(err)) {
186+
return new ResponseError(LSPErrorCodes.RequestCancelled, 'The request has been cancelled.');
187+
}
188+
if (err instanceof ResponseError) {
189+
return err;
190+
}
191+
throw err;
192+
};
193+
/* c8 ignore stop */

0 commit comments

Comments
 (0)