/******************************************************************************
 * 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 { CancellationToken } from 'vscode-jsonrpc' ; import { LangiumServices } from '../services' ; import { AstNode , AstNodeDescription , AstReflection } from '../syntax-tree' ; import { AstNodeContent , getDocument , streamAllContents } from '../utils/ast-util' ; import { MultiMap } from '../utils/collections' ; import { interruptAndCheck } from '../utils/promise-util' ; import { EMPTY_STREAM , Stream , stream } from '../utils/stream' ; import { AstNodeDescriptionProvider } from '../workspace/ast-descriptions' ; import { LangiumDocument , PrecomputedScopes } from '../workspace/documents' ; import { IndexManager } from '../workspace/index-manager' ; import { NameProvider } from './naming' ; export interface Scope { getElement ( name ) : AstNodeDescription | undefined ; getAllElements ( ) : Stream < AstNodeDescription > ; } export class SimpleScope implements Scope { readonly elements : Stream < AstNodeDescription > ; readonly outerScope ? : Scope ; readonly caseInsensitive ? : boolean ; constructor ( elements , outerScope ? , options ? ) { this . elements = elements ; this . outerScope = outerScope ; this . caseInsensitive = options ?. caseInsensitive ; } getAllElements ( ) { if ( this . outerScope ) { return this . elements . concat ( this . outerScope . getAllElements ( ) ) ; } else { return this . elements ; } } getElement ( name ) { const local = this . caseInsensitive ? this . elements . find ( e => e . name . toLowerCase ( ) === name . toLowerCase ( ) ) : this . elements . find ( e => e . name === name ) ; if ( local ) { return local ; } if ( this . outerScope ) { return this . outerScope . getElement ( name ) ; } return undefined ; } } export const EMPTY_SCOPE = { getElement ( ) { return undefined ; } , getAllElements ( ) { return EMPTY_STREAM ; } } ; export interface ScopeProvider { getScope ( node , referenceId ) : Scope ; } export class DefaultScopeProvider implements ScopeProvider { protected readonly reflection : AstReflection ; protected readonly indexManager : IndexManager ; constructor ( services ) { this . reflection = services . shared . AstReflection ; this . indexManager = services . shared . workspace . IndexManager ; } getScope ( node , referenceId ) { const scopes = [ ] ; const referenceType = this . reflection . getReferenceType ( referenceId ) ; const precomputed = getDocument ( node ) . precomputedScopes ; if ( precomputed ) { let currentNode = node ; do { const allDescriptions = precomputed . get ( currentNode ) ; if ( allDescriptions . length > 0 ) { scopes . push ( stream ( allDescriptions ) . filter ( desc => this . reflection . isSubtype ( desc . type , referenceType ) ) ) ; } currentNode = currentNode . $container ; } while ( currentNode ) ; } let result = this . getGlobalScope ( referenceType ) ; for ( let i = scopes . length - 1 ; i >= 0 ; i -- ) { result = this . createScope ( scopes [ i ] , result ) ; } return result ; } /**
     * Create a scope for the given precomputed stream of elements.
     */ protected createScope ( elements , outerScope ) { return new SimpleScope ( elements , outerScope ) ; } /**
     * Create a global scope filtered for the given reference type.
     */ protected getGlobalScope ( referenceType ) { return new SimpleScope ( this . indexManager . allElements ( referenceType ) ) ; } } export interface ScopeComputation { param document param cancelToken throws computeScope ( document , cancelToken ? ) : Promise < PrecomputedScopes > ; } export class DefaultScopeComputation implements ScopeComputation { protected readonly nameProvider : NameProvider ; protected readonly descriptions : AstNodeDescriptionProvider ; constructor ( services ) { this . nameProvider = services . references . NameProvider ; this . descriptions = services . index . AstNodeDescriptionProvider ; } async computeScope ( document , cancelToken = CancellationToken . None ) { const rootNode = document . parseResult . value ; const scopes = new MultiMap < AstNode , AstNodeDescription > ( ) ; for ( const content of streamAllContents ( rootNode ) ) { interruptAndCheck ( cancelToken ) ; this . processNode ( content , document , scopes ) ; } return scopes ; } protected processNode ( { node } , document , scopes ) { const container = node . $container ; if ( container ) { const name = this . nameProvider . getName ( node ) ; if ( name ) { scopes . add ( container , this . descriptions . createDescription ( node , name , document ) ) ; } } } }