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: type hierarchy provider #737

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add type hierarchy provider as LSP capability
  • Loading branch information
lars-reimann committed Nov 7, 2023
commit 56b55436cf82c92d552960c3d92791f7966dd767
190 changes: 190 additions & 0 deletions packages/safe-ds-lang/src/language/lsp/safe-ds-language-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* This file can be removed, once Langium supports the TypeHierarchyProvider directly.
*/

import type { LangiumServices, LangiumSharedServices } from 'langium';
import {
addCallHierarchyHandler,
addCodeActionHandler,
addCodeLensHandler,
addCompletionHandler,
addConfigurationChangeHandler,
addDiagnosticsHandler,
addDocumentHighlightsHandler,
addDocumentLinkHandler,
addDocumentsHandler,
addDocumentSymbolHandler,
addExecuteCommandHandler,
addFindReferencesHandler,
addFoldingRangeHandler,
addFormattingHandler,
addGoToDeclarationHandler,
addGotoDefinitionHandler,
addGoToImplementationHandler,
addGoToTypeDefinitionHandler,
addHoverHandler,
addInlayHintHandler,
addRenameHandler,
addSemanticTokenHandler,
addSignatureHelpHandler,
addWorkspaceSymbolHandler,
createServerRequestHandler,
DefaultLanguageServer,
isOperationCancelled,
URI,
} from 'langium';
import {
type CancellationToken,
type Connection,
type HandlerResult,
type InitializeParams,
type InitializeResult,
LSPErrorCodes,
ResponseError,
type ServerRequestHandler,
type TypeHierarchySubtypesParams,
type TypeHierarchySupertypesParams,
} from 'vscode-languageserver';
import { TypeHierarchyProvider } from './safe-ds-type-hierarchy-provider.js';

interface LangiumAddedServices {
lsp: {
TypeHierarchyProvider?: TypeHierarchyProvider;
};
}

export class SafeDsLanguageServer extends DefaultLanguageServer {
protected override hasService(
callback: (language: LangiumServices & LangiumAddedServices) => object | undefined,
): boolean {
return this.services.ServiceRegistry.all.some((language) => callback(language) !== undefined);
}

protected override buildInitializeResult(params: InitializeParams): InitializeResult {
const hasTypeHierarchyProvider = this.hasService((e) => e.lsp.TypeHierarchyProvider);
const otherCapabilities = super.buildInitializeResult(params).capabilities;

return {
capabilities: {
...otherCapabilities,
typeHierarchyProvider: hasTypeHierarchyProvider ? {} : undefined,
},
};
}
}

export const startLanguageServer = (services: LangiumSharedServices): void => {
const connection = services.lsp.Connection;
if (!connection) {
throw new Error('Starting a language server requires the languageServer.Connection service to be set.');
}

addDocumentsHandler(connection, services);
addDiagnosticsHandler(connection, services);
addCompletionHandler(connection, services);
addFindReferencesHandler(connection, services);
addDocumentSymbolHandler(connection, services);
addGotoDefinitionHandler(connection, services);
addGoToTypeDefinitionHandler(connection, services);
addGoToImplementationHandler(connection, services);
addDocumentHighlightsHandler(connection, services);
addFoldingRangeHandler(connection, services);
addFormattingHandler(connection, services);
addCodeActionHandler(connection, services);
addRenameHandler(connection, services);
addHoverHandler(connection, services);
addInlayHintHandler(connection, services);
addSemanticTokenHandler(connection, services);
addExecuteCommandHandler(connection, services);
addSignatureHelpHandler(connection, services);
addCallHierarchyHandler(connection, services);
addTypeHierarchyHandler(connection, services);
addCodeLensHandler(connection, services);
addDocumentLinkHandler(connection, services);
addConfigurationChangeHandler(connection, services);
addGoToDeclarationHandler(connection, services);
addWorkspaceSymbolHandler(connection, services);

connection.onInitialize((params) => {
return services.lsp.LanguageServer.initialize(params);
});
connection.onInitialized((params) => {
return services.lsp.LanguageServer.initialized(params);
});

// Make the text document manager listen on the connection for open, change and close text document events.
const documents = services.workspace.TextDocuments;
documents.listen(connection);

// Start listening for incoming messages from the client.
connection.listen();
};

export const addTypeHierarchyHandler = function (connection: Connection, sharedServices: LangiumSharedServices): void {
connection.languages.typeHierarchy.onPrepare(
createServerRequestHandler((services, document, params, cancelToken) => {
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
if (typeHierarchyProvider) {
return typeHierarchyProvider.prepareTypeHierarchy(document, params, cancelToken) ?? null;
}
return null;
}, sharedServices),
);

connection.languages.typeHierarchy.onSupertypes(
createTypeHierarchyRequestHandler((services, params, cancelToken) => {
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
if (typeHierarchyProvider) {
return typeHierarchyProvider.supertypes(params, cancelToken) ?? null;
}
return null;
}, sharedServices),
);

connection.languages.typeHierarchy.onSubtypes(
createTypeHierarchyRequestHandler((services, params, cancelToken) => {
const typeHierarchyProvider = (<LangiumServices & LangiumAddedServices>services).lsp.TypeHierarchyProvider;
if (typeHierarchyProvider) {
return typeHierarchyProvider.subtypes(params, cancelToken) ?? null;
}
return null;
}, sharedServices),
);
};

export const createTypeHierarchyRequestHandler = function <
P extends TypeHierarchySupertypesParams | TypeHierarchySubtypesParams,
R,
PR,
E = void,
>(
serviceCall: (services: LangiumServices, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
sharedServices: LangiumSharedServices,
): ServerRequestHandler<P, R, PR, E> {
const serviceRegistry = sharedServices.ServiceRegistry;
return async (params: P, cancelToken: CancellationToken) => {
const uri = URI.parse(params.item.uri);
const language = serviceRegistry.getServices(uri);
if (!language) {
const message = `Could not find service instance for uri: '${uri.toString()}'`;
// eslint-disable-next-line no-console
console.error(message);
throw new Error(message);
}
try {
return await serviceCall(language, params, cancelToken);
} catch (err) {
return responseError<E>(err);
}
};
};

const responseError = function <E = void>(err: unknown): ResponseError<E> {
if (isOperationCancelled(err)) {
return new ResponseError(LSPErrorCodes.RequestCancelled, 'The request has been cancelled.');
}
if (err instanceof ResponseError) {
return err;
}
throw err;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { LangiumDocument } from 'langium';
import type {
CancellationToken,
TypeHierarchyItem,
TypeHierarchyPrepareParams,
TypeHierarchySubtypesParams,
TypeHierarchySupertypesParams,
} from 'vscode-languageserver';
import type { SafeDsServices } from '../safe-ds-module.js';

export interface TypeHierarchyProvider {
prepareTypeHierarchy(
document: LangiumDocument,
params: TypeHierarchyPrepareParams,
cancelToken?: CancellationToken,
): TypeHierarchyItem[] | undefined;

supertypes(param: TypeHierarchySupertypesParams, cancelToken?: CancellationToken): TypeHierarchyItem[] | undefined;

subtypes(param: TypeHierarchySubtypesParams, cancelToken?: CancellationToken): TypeHierarchyItem[] | undefined;
}

export abstract class AbstractTypeHierarchyProvider implements TypeHierarchyProvider {
prepareTypeHierarchy(
document: LangiumDocument,
params: TypeHierarchyPrepareParams,
cancelToken?: CancellationToken,
): TypeHierarchyItem[] | undefined {
console.log('prepare');
return undefined;
}

subtypes(param: TypeHierarchySubtypesParams, cancelToken?: CancellationToken): TypeHierarchyItem[] | undefined {
console.log('subtypes');
return undefined;
}

supertypes(param: TypeHierarchySupertypesParams, cancelToken?: CancellationToken): TypeHierarchyItem[] | undefined {
console.log('supertypes');
return undefined;
}
}

export class SafeDsTypeHierarchyProvider extends AbstractTypeHierarchyProvider {
constructor(services: SafeDsServices) {
super();
}
}
2 changes: 1 addition & 1 deletion packages/safe-ds-lang/src/language/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { startLanguageServer as doStartLanguageServer } from 'langium';
import { NodeFileSystem } from 'langium/node';
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js';
import { startLanguageServer as doStartLanguageServer } from './lsp/safe-ds-language-server.js';
import { createSafeDsServices } from './safe-ds-module.js';

/* c8 ignore start */
Expand Down
7 changes: 5 additions & 2 deletions packages/safe-ds-lang/src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import { SafeDsCallHierarchyProvider } from './lsp/safe-ds-call-hierarchy-provid
import { SafeDsDocumentSymbolProvider } from './lsp/safe-ds-document-symbol-provider.js';
import { SafeDsFormatter } from './lsp/safe-ds-formatter.js';
import { SafeDsInlayHintProvider } from './lsp/safe-ds-inlay-hint-provider.js';
import { SafeDsLanguageServer } from './lsp/safe-ds-language-server.js';
import { SafeDsNodeInfoProvider } from './lsp/safe-ds-node-info-provider.js';
import { SafeDsNodeKindProvider } from './lsp/safe-ds-node-kind-provider.js';
import { SafeDsSemanticTokenProvider } from './lsp/safe-ds-semantic-token-provider.js';
import { SafeDsSignatureHelpProvider } from './lsp/safe-ds-signature-help-provider.js';
import { SafeDsTypeHierarchyProvider } from './lsp/safe-ds-type-hierarchy-provider.js';
import { SafeDsPartialEvaluator } from './partialEvaluation/safe-ds-partial-evaluator.js';
import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js';
import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js';
Expand Down Expand Up @@ -62,6 +64,7 @@ export type SafeDsAddedServices = {
};
lsp: {
NodeInfoProvider: SafeDsNodeInfoProvider;
TypeHierarchyProvider: SafeDsTypeHierarchyProvider;
};
types: {
ClassHierarchy: SafeDsClassHierarchy;
Expand Down Expand Up @@ -115,6 +118,7 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
NodeInfoProvider: (services) => new SafeDsNodeInfoProvider(services),
SemanticTokenProvider: (services) => new SafeDsSemanticTokenProvider(services),
SignatureHelp: (services) => new SafeDsSignatureHelpProvider(services),
TypeHierarchyProvider: (services) => new SafeDsTypeHierarchyProvider(services),
},
parser: {
ValueConverter: () => new SafeDsValueConverter(),
Expand All @@ -138,6 +142,7 @@ export type SafeDsSharedServices = LangiumSharedServices;

export const SafeDsSharedModule: Module<SafeDsSharedServices, DeepPartial<SafeDsSharedServices>> = {
lsp: {
LanguageServer: (services) => new SafeDsLanguageServer(services),
NodeKindProvider: () => new SafeDsNodeKindProvider(),
},
workspace: {
Expand Down Expand Up @@ -187,7 +192,6 @@ export const createSafeDsServices = function (context: DefaultSharedModuleContex
* @param context Optional module context with the LSP connection.
* @return An object wrapping the shared services and the language-specific services.
*/
/* c8 ignore start */
export const createSafeDsServicesWithBuiltins = async function (context: DefaultSharedModuleContext): Promise<{
shared: LangiumSharedServices;
SafeDs: SafeDsServices;
Expand All @@ -196,4 +200,3 @@ export const createSafeDsServicesWithBuiltins = async function (context: Default
await shared.workspace.WorkspaceManager.initializeWorkspace([]);
return { shared, SafeDs };
};
/* c8 ignore stop */