UNPKG

langium

Version:

A language engineering tool for the Language Server Protocol

159 lines (141 loc) 6.86 kB
/****************************************************************************** * Copyright 2021 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import type { URI } from '../utils/uri-utils.js'; import type { NameProvider } from '../references/name-provider.js'; import type { LangiumCoreServices } from '../services.js'; import type { AstNode, AstNodeDescription, ReferenceInfo } from '../syntax-tree.js'; import type { AstNodeLocator } from './ast-node-locator.js'; import type { DocumentSegment, LangiumDocument } from './documents.js'; import { CancellationToken } from '../utils/cancellation.js'; import { isMultiReference, isReference } from '../syntax-tree.js'; import { getDocument, streamAst, streamReferences } from '../utils/ast-utils.js'; import { toDocumentSegment } from '../utils/cst-utils.js'; import { interruptAndCheck } from '../utils/promise-utils.js'; import { UriUtils } from '../utils/uri-utils.js'; /** * Language-specific service for creating descriptions of AST nodes to be used for cross-reference resolutions. */ export interface AstNodeDescriptionProvider { /** * Create a description for the given AST node. This service method is typically used while indexing * the contents of a document and during scope computation. * * @param node An AST node. * @param name The name to be used to refer to the AST node. By default, this is determined by the * `NameProvider` service, but alternative names may be provided according to the semantics * of your language. * @param document The document containing the AST node. If omitted, it is taken from the root AST node. */ createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription; } export class DefaultAstNodeDescriptionProvider implements AstNodeDescriptionProvider { protected readonly astNodeLocator: AstNodeLocator; protected readonly nameProvider: NameProvider; constructor(services: LangiumCoreServices) { this.astNodeLocator = services.workspace.AstNodeLocator; this.nameProvider = services.references.NameProvider; } createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription { const doc = document ?? getDocument(node); name ??= this.nameProvider.getName(node); const path = this.astNodeLocator.getAstNodePath(node); if (!name) { throw new Error(`Node at path ${path} has no name.`); } let nameNodeSegment: DocumentSegment | undefined; const nameSegmentGetter = () => nameNodeSegment ??= toDocumentSegment(this.nameProvider.getNameNode(node) ?? node.$cstNode); return { node, name, get nameSegment() { return nameSegmentGetter(); }, selectionSegment: toDocumentSegment(node.$cstNode), type: node.$type, documentUri: doc.uri, path }; } } /** * Describes a cross-reference within a document or between two documents. */ export interface ReferenceDescription { /** URI of the document that holds a reference */ sourceUri: URI /** Path to AstNode that holds a reference */ sourcePath: string /** Target document uri */ targetUri: URI /** Path to the target AstNode inside the document */ targetPath: string /** Segment of the reference text. */ segment: DocumentSegment /** Marks a local reference i.e. a cross reference inside a document. */ local?: boolean } /** * Language-specific service to create descriptions of all cross-references in a document. These are used by the `IndexManager` * to determine which documents are affected and should be rebuilt when a document is changed. */ export interface ReferenceDescriptionProvider { /** * Create descriptions of all cross-references found in the given document. These descriptions are * gathered by the `IndexManager` and stored in the global index so they can be considered when * a document change is reported by the client. * * @param document The document in which to gather cross-references. * @param cancelToken Indicates when to cancel the current operation. * @throws `OperationCanceled` if a user action occurs during execution */ createDescriptions(document: LangiumDocument, cancelToken?: CancellationToken): Promise<ReferenceDescription[]>; } export class DefaultReferenceDescriptionProvider implements ReferenceDescriptionProvider { protected readonly nodeLocator: AstNodeLocator; constructor(services: LangiumCoreServices) { this.nodeLocator = services.workspace.AstNodeLocator; } async createDescriptions(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<ReferenceDescription[]> { const descr: ReferenceDescription[] = []; const rootNode = document.parseResult.value; for (const astNode of streamAst(rootNode)) { await interruptAndCheck(cancelToken); streamReferences(astNode).forEach(refInfo => { if (!refInfo.reference.error) { descr.push(...this.createInfoDescriptions(refInfo)); } }); } return descr; } protected createInfoDescriptions(refInfo: ReferenceInfo): ReferenceDescription[] { const reference = refInfo.reference; if (reference.error || !reference.$refNode) { return []; } let items: AstNodeDescription[] = []; if (isReference(reference) && reference.$nodeDescription) { items = [reference.$nodeDescription]; } else if (isMultiReference(reference)) { items = reference.items.map(e => e.$nodeDescription).filter(e => e !== undefined); } const sourceUri = getDocument(refInfo.container).uri; const sourcePath = this.nodeLocator.getAstNodePath(refInfo.container); const descriptions: ReferenceDescription[] = []; const segment = toDocumentSegment(reference.$refNode); for (const item of items) { descriptions.push({ sourceUri, sourcePath, targetUri: item.documentUri, targetPath: item.path, segment, local: UriUtils.equals(item.documentUri, sourceUri) }); } return descriptions; } }