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

Ensure expected document state in LSP responses #1334

Merged
merged 4 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
139 changes: 101 additions & 38 deletions packages/langium/src/lsp/language-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,110 +313,127 @@ export function addCompletionHandler(connection: Connection, services: LangiumSh
(services, document, params, cancelToken) => {
return services.lsp?.CompletionProvider?.getCompletion(document, params, cancelToken);
},
services
services,
DocumentState.IndexedReferences
));
}

export function addFindReferencesHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onReferences(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.ReferencesProvider?.findReferences(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addCodeActionHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onCodeAction(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.CodeActionProvider?.getCodeActions(document, params, cancelToken),
services
services,
DocumentState.Validated
));
}

export function addDocumentSymbolHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDocumentSymbol(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.DocumentSymbolProvider?.getSymbols(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
}

export function addGotoDefinitionHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDefinition(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.DefinitionProvider?.getDefinition(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addGoToTypeDefinitionHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onTypeDefinition(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.TypeProvider?.getTypeDefinition(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addGoToImplementationHandler(connection: Connection, services: LangiumSharedServices) {
connection.onImplementation(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.ImplementationProvider?.getImplementation(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addGoToDeclarationHandler(connection: Connection, services: LangiumSharedServices) {
connection.onDeclaration(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.DeclarationProvider?.getDeclaration(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addDocumentHighlightsHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDocumentHighlight(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.DocumentHighlightProvider?.getDocumentHighlight(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addHoverHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onHover(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.HoverProvider?.getHoverContent(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addFoldingRangeHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onFoldingRanges(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.FoldingRangeProvider?.getFoldingRanges(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
}

export function addFormattingHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDocumentFormatting(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocument(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
connection.onDocumentRangeFormatting(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentRange(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
connection.onDocumentOnTypeFormatting(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentOnType(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
}

export function addRenameHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onRenameRequest(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.RenameProvider?.rename(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
connection.onPrepareRename(createRequestHandler(
(services, document, params, cancelToken) => services.lsp?.RenameProvider?.prepareRename(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addInlayHintHandler(connection: Connection, services: LangiumSharedServices): void {
connection.languages.inlayHint.on(createServerRequestHandler(
(services, document, params, cancelToken) => services.lsp?.InlayHintProvider?.getInlayHints(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

Expand All @@ -430,7 +447,8 @@ export function addSemanticTokenHandler(connection: Connection, services: Langiu
}
return emptyResult;
},
services
services,
DocumentState.IndexedReferences
));
connection.languages.semanticTokens.onDelta(createServerRequestHandler<SemanticTokensDeltaParams, SemanticTokens | SemanticTokensDelta, SemanticTokensDeltaPartialResult, void>(
(services, document, params, cancelToken) => {
Expand All @@ -439,7 +457,8 @@ export function addSemanticTokenHandler(connection: Connection, services: Langiu
}
return emptyResult;
},
services
services,
DocumentState.IndexedReferences
));
connection.languages.semanticTokens.onRange(createServerRequestHandler<SemanticTokensRangeParams, SemanticTokens, SemanticTokensPartialResult, void>(
(services, document, params, cancelToken) => {
Expand All @@ -448,7 +467,8 @@ export function addSemanticTokenHandler(connection: Connection, services: Langiu
}
return emptyResult;
},
services
services,
DocumentState.IndexedReferences
));
}
export function addConfigurationChangeHandler(connection: Connection, services: LangiumSharedServices): void {
Expand All @@ -475,29 +495,34 @@ export function addExecuteCommandHandler(connection: Connection, services: Langi
export function addDocumentLinkHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDocumentLinks(createServerRequestHandler(
(services, document, params, cancelToken) => services.lsp?.DocumentLinkProvider?.getDocumentLinks(document, params, cancelToken),
services
services,
DocumentState.Parsed
));
}

export function addSignatureHelpHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onSignatureHelp(createServerRequestHandler(
(services, document, params, cancelToken) => services.lsp?.SignatureHelp?.provideSignatureHelp(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addCodeLensHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onCodeLens(createServerRequestHandler(
(services, document, params, cancelToken) => services.lsp?.CodeLensProvider?.provideCodeLens(document, params, cancelToken),
services
services,
DocumentState.IndexedReferences
));
}

export function addWorkspaceSymbolHandler(connection: Connection, services: LangiumSharedServices): void {
const workspaceSymbolProvider = services.lsp.WorkspaceSymbolProvider;
if (workspaceSymbolProvider) {
const documentBuilder = services.workspace.DocumentBuilder;
connection.onWorkspaceSymbol(async (params, token) => {
try {
await documentBuilder.waitUntil(DocumentState.IndexedContent, token);
return await workspaceSymbolProvider.getSymbols(params, token);
} catch (err) {
return responseError(err);
Expand All @@ -507,6 +532,7 @@ export function addWorkspaceSymbolHandler(connection: Connection, services: Lang
if (resolveWorkspaceSymbol) {
connection.onWorkspaceSymbolResolve(async (workspaceSymbol, token) => {
try {
await documentBuilder.waitUntil(DocumentState.IndexedContent, token);
return await resolveWorkspaceSymbol(workspaceSymbol, token);
} catch (err) {
return responseError(err);
Expand All @@ -525,7 +551,8 @@ export function addCallHierarchyHandler(connection: Connection, services: Langiu
}
return null;
},
services
services,
DocumentState.IndexedReferences
));

connection.languages.callHierarchy.onIncomingCalls(createHierarchyRequestHandler(
Expand Down Expand Up @@ -558,24 +585,34 @@ export function addTypeHierarchyHandler(connection: Connection, sharedServices:
}

connection.languages.typeHierarchy.onPrepare(
createServerRequestHandler(async (services, document, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.prepareTypeHierarchy(document, params, cancelToken);
return result ?? null;
}, sharedServices),
createServerRequestHandler(
async (services, document, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.prepareTypeHierarchy(document, params, cancelToken);
return result ?? null;
},
sharedServices,
DocumentState.IndexedReferences
),
);

connection.languages.typeHierarchy.onSupertypes(
createHierarchyRequestHandler(async (services, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.supertypes(params, cancelToken);
return result ?? null;
}, sharedServices),
createHierarchyRequestHandler(
async (services, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.supertypes(params, cancelToken);
return result ?? null;
},
sharedServices
),
);

connection.languages.typeHierarchy.onSubtypes(
createHierarchyRequestHandler(async (services, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.subtypes(params, cancelToken);
return result ?? null;
}, sharedServices),
createHierarchyRequestHandler(
async (services, params, cancelToken) => {
const result = await services.lsp?.TypeHierarchyProvider?.subtypes(params, cancelToken);
return result ?? null;
},
sharedServices
),
);
}

Expand All @@ -586,6 +623,10 @@ export function createHierarchyRequestHandler<P extends TypeHierarchySupertypesP
const serviceRegistry = sharedServices.ServiceRegistry;
return async (params: P, cancelToken: CancellationToken) => {
const uri = URI.parse(params.item.uri);
const cancellationError = await waitUntilPhase<E>(sharedServices, cancelToken, uri, DocumentState.IndexedReferences);
if (cancellationError) {
return cancellationError;
}
const language = serviceRegistry.getServices(uri);
if (!language) {
const message = `Could not find service instance for uri: '${uri.toString()}'`;
Expand All @@ -602,12 +643,17 @@ export function createHierarchyRequestHandler<P extends TypeHierarchySupertypesP

export function createServerRequestHandler<P extends { textDocument: TextDocumentIdentifier }, R, PR, E = void>(
serviceCall: (services: LangiumCoreAndPartialLSPServices, document: LangiumDocument, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
sharedServices: LangiumSharedServices
sharedServices: LangiumSharedServices,
targetState?: DocumentState
): ServerRequestHandler<P, R, PR, E> {
const documents = sharedServices.workspace.LangiumDocuments;
const serviceRegistry = sharedServices.ServiceRegistry;
return async (params: P, cancelToken: CancellationToken) => {
const uri = URI.parse(params.textDocument.uri);
const cancellationError = await waitUntilPhase<E>(sharedServices, cancelToken, uri, targetState);
if (cancellationError) {
return cancellationError;
}
const language = serviceRegistry.getServices(uri);
if (!language) {
const errorText = `Could not find service instance for uri: '${uri}'`;
Expand All @@ -625,12 +671,17 @@ export function createServerRequestHandler<P extends { textDocument: TextDocumen

export function createRequestHandler<P extends { textDocument: TextDocumentIdentifier }, R, E = void>(
serviceCall: (services: LangiumCoreAndPartialLSPServices, document: LangiumDocument, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
sharedServices: LangiumSharedServices
sharedServices: LangiumSharedServices,
targetState?: DocumentState
): RequestHandler<P, R | null, E> {
const documents = sharedServices.workspace.LangiumDocuments;
const serviceRegistry = sharedServices.ServiceRegistry;
return async (params: P, cancelToken: CancellationToken) => {
const uri = URI.parse(params.textDocument.uri);
const cancellationError = await waitUntilPhase<E>(sharedServices, cancelToken, uri, targetState);
if (cancellationError) {
return cancellationError;
}
const language = serviceRegistry.getServices(uri);
if (!language) {
console.error(`Could not find service instance for uri: '${uri.toString()}'`);
Expand All @@ -648,6 +699,18 @@ export function createRequestHandler<P extends { textDocument: TextDocumentIdent
};
}

async function waitUntilPhase<E>(services: LangiumSharedServices, cancelToken: CancellationToken, uri?: URI, targetState?: DocumentState): Promise<ResponseError<E> | undefined> {
if (targetState !== undefined) {
const documentBuilder = services.workspace.DocumentBuilder;
try {
await documentBuilder.waitUntil(targetState, uri, cancelToken);
} catch (err) {
return responseError(err);
}
}
return undefined;
}

function responseError<E = void>(err: unknown): ResponseError<E> {
if (isOperationCancelled(err)) {
return new ResponseError(LSPErrorCodes.RequestCancelled, 'The request has been cancelled.');
Expand Down
Loading
Loading