完成世界书、骰子、apiconfig页面处理
This commit is contained in:
124
frontend/node_modules/langium/src/default-module.ts
generated
vendored
Normal file
124
frontend/node_modules/langium/src/default-module.ts
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/******************************************************************************
|
||||
* 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 { Module } from './dependency-injection.js';
|
||||
import type { LangiumDefaultCoreServices, LangiumDefaultSharedCoreServices, LangiumCoreServices, LangiumSharedCoreServices } from './services.js';
|
||||
import type { FileSystemProvider } from './workspace/file-system-provider.js';
|
||||
import { createGrammarConfig } from './languages/grammar-config.js';
|
||||
import { createCompletionParser } from './parser/completion-parser-builder.js';
|
||||
import { createLangiumParser } from './parser/langium-parser-builder.js';
|
||||
import { DefaultTokenBuilder } from './parser/token-builder.js';
|
||||
import { DefaultValueConverter } from './parser/value-converter.js';
|
||||
import { DefaultLinker } from './references/linker.js';
|
||||
import { DefaultNameProvider } from './references/name-provider.js';
|
||||
import { DefaultReferences } from './references/references.js';
|
||||
import { DefaultScopeComputation } from './references/scope-computation.js';
|
||||
import { DefaultScopeProvider } from './references/scope-provider.js';
|
||||
import { DefaultJsonSerializer } from './serializer/json-serializer.js';
|
||||
import { DefaultServiceRegistry } from './service-registry.js';
|
||||
import { DefaultDocumentValidator } from './validation/document-validator.js';
|
||||
import { ValidationRegistry } from './validation/validation-registry.js';
|
||||
import { DefaultAstNodeDescriptionProvider, DefaultReferenceDescriptionProvider } from './workspace/ast-descriptions.js';
|
||||
import { DefaultAstNodeLocator } from './workspace/ast-node-locator.js';
|
||||
import { DefaultConfigurationProvider } from './workspace/configuration.js';
|
||||
import { DefaultDocumentBuilder } from './workspace/document-builder.js';
|
||||
import { DefaultLangiumDocumentFactory, DefaultLangiumDocuments } from './workspace/documents.js';
|
||||
import { DefaultIndexManager } from './workspace/index-manager.js';
|
||||
import { DefaultWorkspaceManager } from './workspace/workspace-manager.js';
|
||||
import { DefaultLexer, DefaultLexerErrorMessageProvider } from './parser/lexer.js';
|
||||
import { JSDocDocumentationProvider } from './documentation/documentation-provider.js';
|
||||
import { DefaultCommentProvider } from './documentation/comment-provider.js';
|
||||
import { LangiumParserErrorMessageProvider } from './parser/langium-parser.js';
|
||||
import { DefaultAsyncParser } from './parser/async-parser.js';
|
||||
import { DefaultWorkspaceLock } from './workspace/workspace-lock.js';
|
||||
import { DefaultHydrator } from './serializer/hydrator.js';
|
||||
|
||||
/**
|
||||
* Context required for creating the default language-specific dependency injection module.
|
||||
*/
|
||||
export interface DefaultCoreModuleContext {
|
||||
shared: LangiumSharedCoreServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dependency injection module configuring the default core services.
|
||||
* This is a set of services that are dedicated to a specific language.
|
||||
*/
|
||||
export function createDefaultCoreModule(context: DefaultCoreModuleContext): Module<LangiumCoreServices, LangiumDefaultCoreServices> {
|
||||
return {
|
||||
documentation: {
|
||||
CommentProvider: (services) => new DefaultCommentProvider(services),
|
||||
DocumentationProvider: (services) => new JSDocDocumentationProvider(services)
|
||||
},
|
||||
parser: {
|
||||
AsyncParser: (services) => new DefaultAsyncParser(services),
|
||||
GrammarConfig: (services) => createGrammarConfig(services),
|
||||
LangiumParser: (services) => createLangiumParser(services),
|
||||
CompletionParser: (services) => createCompletionParser(services),
|
||||
ValueConverter: () => new DefaultValueConverter(),
|
||||
TokenBuilder: () => new DefaultTokenBuilder(),
|
||||
Lexer: (services) => new DefaultLexer(services),
|
||||
ParserErrorMessageProvider: () => new LangiumParserErrorMessageProvider(),
|
||||
LexerErrorMessageProvider: () => new DefaultLexerErrorMessageProvider()
|
||||
},
|
||||
workspace: {
|
||||
AstNodeLocator: () => new DefaultAstNodeLocator(),
|
||||
AstNodeDescriptionProvider: (services) => new DefaultAstNodeDescriptionProvider(services),
|
||||
ReferenceDescriptionProvider: (services) => new DefaultReferenceDescriptionProvider(services)
|
||||
},
|
||||
references: {
|
||||
Linker: (services) => new DefaultLinker(services),
|
||||
NameProvider: () => new DefaultNameProvider(),
|
||||
ScopeProvider: (services) => new DefaultScopeProvider(services),
|
||||
ScopeComputation: (services) => new DefaultScopeComputation(services),
|
||||
References: (services) => new DefaultReferences(services)
|
||||
},
|
||||
serializer: {
|
||||
Hydrator: (services) => new DefaultHydrator(services),
|
||||
JsonSerializer: (services) => new DefaultJsonSerializer(services)
|
||||
},
|
||||
validation: {
|
||||
DocumentValidator: (services) => new DefaultDocumentValidator(services),
|
||||
ValidationRegistry: (services) => new ValidationRegistry(services)
|
||||
},
|
||||
shared: () => context.shared
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Context required for creating the default shared dependency injection module.
|
||||
*/
|
||||
export interface DefaultSharedCoreModuleContext {
|
||||
/**
|
||||
* Factory function to create a {@link FileSystemProvider}.
|
||||
*
|
||||
* Langium exposes an `EmptyFileSystem` and `NodeFileSystem`, exported through `langium/node`.
|
||||
* When running Langium as part of a vscode language server or a Node.js app, using the `NodeFileSystem` is recommended,
|
||||
* the `EmptyFileSystem` in every other use case.
|
||||
*/
|
||||
fileSystemProvider: (services: LangiumSharedCoreServices) => FileSystemProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dependency injection module configuring the default shared core services.
|
||||
* This is the set of services that are shared between multiple languages.
|
||||
*/
|
||||
export function createDefaultSharedCoreModule(context: DefaultSharedCoreModuleContext): Module<LangiumSharedCoreServices, LangiumDefaultSharedCoreServices> {
|
||||
return {
|
||||
ServiceRegistry: (services) => new DefaultServiceRegistry(services),
|
||||
workspace: {
|
||||
LangiumDocuments: (services) => new DefaultLangiumDocuments(services),
|
||||
LangiumDocumentFactory: (services) => new DefaultLangiumDocumentFactory(services),
|
||||
DocumentBuilder: (services) => new DefaultDocumentBuilder(services),
|
||||
IndexManager: (services) => new DefaultIndexManager(services),
|
||||
WorkspaceManager: (services) => new DefaultWorkspaceManager(services),
|
||||
FileSystemProvider: (services) => context.fileSystemProvider(services),
|
||||
WorkspaceLock: () => new DefaultWorkspaceLock(),
|
||||
ConfigurationProvider: (services) => new DefaultConfigurationProvider(services),
|
||||
},
|
||||
profilers: {}
|
||||
};
|
||||
}
|
||||
9
frontend/node_modules/langium/src/documentation/index.ts
generated
vendored
Normal file
9
frontend/node_modules/langium/src/documentation/index.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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.
|
||||
******************************************************************************/
|
||||
|
||||
export * from './comment-provider.js';
|
||||
export * from './documentation-provider.js';
|
||||
export * from './jsdoc.js';
|
||||
58
frontend/node_modules/langium/src/grammar/lsp/grammar-definition.ts
generated
vendored
Normal file
58
frontend/node_modules/langium/src/grammar/lsp/grammar-definition.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { DefinitionParams } from 'vscode-languageserver';
|
||||
import type { LangiumServices } from '../../lsp/lsp-services.js';
|
||||
import type { AstNode, LeafCstNode, Properties } from '../../syntax-tree.js';
|
||||
import type { MaybePromise } from '../../utils/promise-utils.js';
|
||||
import type { LangiumDocuments } from '../../workspace/documents.js';
|
||||
import type { Grammar, GrammarImport } from '../../languages/generated/ast.js';
|
||||
import { LocationLink, Range } from 'vscode-languageserver';
|
||||
import { DefaultDefinitionProvider } from '../../lsp/index.js';
|
||||
import { streamContents } from '../../utils/ast-utils.js';
|
||||
import { findAssignment } from '../../utils/grammar-utils.js';
|
||||
import { isGrammarImport } from '../../languages/generated/ast.js';
|
||||
import { resolveImport } from '../internal-grammar-util.js';
|
||||
|
||||
export class LangiumGrammarDefinitionProvider extends DefaultDefinitionProvider {
|
||||
|
||||
protected documents: LangiumDocuments;
|
||||
|
||||
constructor(services: LangiumServices) {
|
||||
super(services);
|
||||
this.documents = services.shared.workspace.LangiumDocuments;
|
||||
}
|
||||
|
||||
protected override collectLocationLinks(sourceCstNode: LeafCstNode, _params: DefinitionParams): MaybePromise<LocationLink[] | undefined> {
|
||||
const pathFeature: Properties<GrammarImport> = 'path';
|
||||
if (isGrammarImport(sourceCstNode.astNode) && findAssignment(sourceCstNode)?.feature === pathFeature) {
|
||||
const importedGrammar = resolveImport(this.documents, sourceCstNode.astNode);
|
||||
if (importedGrammar?.$document) {
|
||||
const targetObject = this.findTargetObject(importedGrammar) ?? importedGrammar;
|
||||
const selectionRange = this.nameProvider.getNameNode(targetObject)?.range ?? Range.create(0, 0, 0, 0);
|
||||
const previewRange = targetObject.$cstNode?.range ?? Range.create(0, 0, 0, 0);
|
||||
return [
|
||||
LocationLink.create(
|
||||
importedGrammar.$document.uri.toString(),
|
||||
previewRange,
|
||||
selectionRange,
|
||||
sourceCstNode.range
|
||||
)
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return super.collectLocationLinks(sourceCstNode, _params);
|
||||
}
|
||||
|
||||
protected findTargetObject(importedGrammar: Grammar): AstNode | undefined {
|
||||
// Jump to grammar name or the first element
|
||||
if (importedGrammar.isDeclared) {
|
||||
return importedGrammar;
|
||||
}
|
||||
return streamContents(importedGrammar).head();
|
||||
}
|
||||
}
|
||||
73
frontend/node_modules/langium/src/grammar/lsp/grammar-semantic-tokens.ts
generated
vendored
Normal file
73
frontend/node_modules/langium/src/grammar/lsp/grammar-semantic-tokens.ts
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { AstNode } from '../../syntax-tree.js';
|
||||
import type { SemanticTokenAcceptor } from '../../lsp/semantic-token-provider.js';
|
||||
import { SemanticTokenTypes } from 'vscode-languageserver';
|
||||
import { AbstractSemanticTokenProvider } from '../../lsp/semantic-token-provider.js';
|
||||
import { isAction, isAssignment, isInfixRule, isParameter, isParameterReference, isReturnType, isRuleCall, isSimpleType, isTypeAttribute } from '../../languages/generated/ast.js';
|
||||
|
||||
export class LangiumGrammarSemanticTokenProvider extends AbstractSemanticTokenProvider {
|
||||
|
||||
protected highlightElement(node: AstNode, acceptor: SemanticTokenAcceptor): void {
|
||||
if (isAssignment(node)) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'feature',
|
||||
type: SemanticTokenTypes.property
|
||||
});
|
||||
} else if (isAction(node)) {
|
||||
if (node.feature) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'feature',
|
||||
type: SemanticTokenTypes.property
|
||||
});
|
||||
}
|
||||
} else if (isReturnType(node)) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'name',
|
||||
type: SemanticTokenTypes.type
|
||||
});
|
||||
} else if (isSimpleType(node)) {
|
||||
if (node.primitiveType || node.typeRef) {
|
||||
acceptor({
|
||||
node,
|
||||
property: node.primitiveType ? 'primitiveType' : 'typeRef',
|
||||
type: SemanticTokenTypes.type
|
||||
});
|
||||
}
|
||||
} else if (isParameter(node)) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'name',
|
||||
type: SemanticTokenTypes.parameter
|
||||
});
|
||||
} else if (isParameterReference(node)) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'parameter',
|
||||
type: SemanticTokenTypes.parameter
|
||||
});
|
||||
} else if (isRuleCall(node)) {
|
||||
if (!isInfixRule(node.rule.ref) && node.rule.ref?.fragment) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'rule',
|
||||
type: SemanticTokenTypes.type
|
||||
});
|
||||
}
|
||||
} else if (isTypeAttribute(node)) {
|
||||
acceptor({
|
||||
node,
|
||||
property: 'name',
|
||||
type: SemanticTokenTypes.property
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
84
frontend/node_modules/langium/src/grammar/type-system/type-collector/all-types.ts
generated
vendored
Normal file
84
frontend/node_modules/langium/src/grammar/type-system/type-collector/all-types.ts
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { ParserRule, Interface, Type, Grammar, InfixRule } from '../../../languages/generated/ast.js';
|
||||
import type { URI } from '../../../utils/uri-utils.js';
|
||||
import type { LangiumCoreServices } from '../../../index.js';
|
||||
import type { PlainAstTypes } from './plain-types.js';
|
||||
import type { AstTypes } from './types.js';
|
||||
import { collectInferredTypes } from './inferred-types.js';
|
||||
import { collectDeclaredTypes } from './declared-types.js';
|
||||
import { getDocument } from '../../../utils/ast-utils.js';
|
||||
import { isInfixRule, isParserRule } from '../../../languages/generated/ast.js';
|
||||
import { resolveImport } from '../../internal-grammar-util.js';
|
||||
import { isDataTypeRule } from '../../../utils/grammar-utils.js';
|
||||
|
||||
export interface AstResources {
|
||||
parserRules: ParserRule[]
|
||||
infixRules: InfixRule[]
|
||||
datatypeRules: ParserRule[]
|
||||
interfaces: Interface[]
|
||||
types: Type[]
|
||||
}
|
||||
|
||||
export interface TypeResources {
|
||||
inferred: PlainAstTypes
|
||||
declared: PlainAstTypes
|
||||
astResources: AstResources
|
||||
}
|
||||
|
||||
export interface ValidationAstTypes {
|
||||
inferred: AstTypes
|
||||
declared: AstTypes
|
||||
astResources: AstResources
|
||||
}
|
||||
|
||||
export function collectTypeResources(grammars: Grammar | Grammar[], services?: LangiumCoreServices): TypeResources {
|
||||
const astResources = collectAllAstResources(grammars, undefined, undefined, services);
|
||||
const declared = collectDeclaredTypes(astResources.interfaces, astResources.types, services);
|
||||
const inferred = collectInferredTypes(astResources.parserRules, astResources.datatypeRules, astResources.infixRules, declared, services);
|
||||
|
||||
return {
|
||||
astResources,
|
||||
inferred,
|
||||
declared
|
||||
};
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export function collectAllAstResources(grammars: Grammar | Grammar[], visited: Set<URI> = new Set(),
|
||||
astResources: AstResources = { parserRules: [], infixRules: [], datatypeRules: [], interfaces: [], types: [] }, services?: LangiumCoreServices): AstResources {
|
||||
|
||||
if (!Array.isArray(grammars)) grammars = [grammars];
|
||||
for (const grammar of grammars) {
|
||||
const doc = getDocument(grammar);
|
||||
if (visited.has(doc.uri)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(doc.uri);
|
||||
for (const rule of grammar.rules) {
|
||||
if (isParserRule(rule) && !rule.fragment) {
|
||||
if (isDataTypeRule(rule)) {
|
||||
astResources.datatypeRules.push(rule);
|
||||
} else {
|
||||
astResources.parserRules.push(rule);
|
||||
}
|
||||
} else if (isInfixRule(rule)) {
|
||||
astResources.infixRules.push(rule);
|
||||
}
|
||||
}
|
||||
grammar.interfaces.forEach(e => astResources.interfaces.push(e));
|
||||
grammar.types.forEach(e => astResources.types.push(e));
|
||||
|
||||
const documents = services?.shared.workspace.LangiumDocuments;
|
||||
if (documents) {
|
||||
const importedGrammars = grammar.imports.map(e => resolveImport(documents, e)).filter(e => e !== undefined);
|
||||
collectAllAstResources(importedGrammars, visited, astResources, services);
|
||||
}
|
||||
}
|
||||
return astResources;
|
||||
}
|
||||
125
frontend/node_modules/langium/src/grammar/type-system/type-collector/declared-types.ts
generated
vendored
Normal file
125
frontend/node_modules/langium/src/grammar/type-system/type-collector/declared-types.ts
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { Interface, Type, TypeDefinition, ValueLiteral } from '../../../languages/generated/ast.js';
|
||||
import type { LangiumCoreServices } from '../../../index.js';
|
||||
import { isArrayLiteral, isBooleanLiteral } from '../../../languages/generated/ast.js';
|
||||
import type { PlainAstTypes, PlainInterface, PlainProperty, PlainPropertyDefaultValue, PlainPropertyType, PlainUnion } from './plain-types.js';
|
||||
import { isArrayType, isReferenceType, isUnionType, isSimpleType } from '../../../languages/generated/ast.js';
|
||||
import { getTypeNameWithoutError, isPrimitiveGrammarType } from '../../internal-grammar-util.js';
|
||||
import { getTypeName } from '../../../utils/grammar-utils.js';
|
||||
|
||||
export function collectDeclaredTypes(interfaces: Interface[], unions: Type[], services?: LangiumCoreServices): PlainAstTypes {
|
||||
const commentProvider = services?.documentation.CommentProvider;
|
||||
const declaredTypes: PlainAstTypes = { unions: [], interfaces: [] };
|
||||
|
||||
// add interfaces
|
||||
for (const type of interfaces) {
|
||||
const properties: PlainProperty[] = [];
|
||||
for (const attribute of type.attributes) {
|
||||
const property: PlainProperty = {
|
||||
name: attribute.name,
|
||||
optional: attribute.isOptional,
|
||||
astNodes: new Set([attribute]),
|
||||
type: typeDefinitionToPropertyType(attribute.type),
|
||||
comment: commentProvider?.getComment(attribute)
|
||||
};
|
||||
if (attribute.defaultValue) {
|
||||
property.defaultValue = toPropertyDefaultValue(attribute.defaultValue);
|
||||
}
|
||||
properties.push(property);
|
||||
}
|
||||
const superTypes = new Set<string>();
|
||||
for (const superType of type.superTypes) {
|
||||
if (superType.ref) {
|
||||
superTypes.add(getTypeName(superType.ref));
|
||||
}
|
||||
}
|
||||
const interfaceType: PlainInterface = {
|
||||
name: type.name,
|
||||
declared: true,
|
||||
abstract: false,
|
||||
properties: properties,
|
||||
superTypes: superTypes,
|
||||
subTypes: new Set(),
|
||||
comment: commentProvider?.getComment(type),
|
||||
};
|
||||
declaredTypes.interfaces.push(interfaceType);
|
||||
}
|
||||
|
||||
// add types
|
||||
for (const union of unions) {
|
||||
const unionType: PlainUnion = {
|
||||
name: union.name,
|
||||
declared: true,
|
||||
type: typeDefinitionToPropertyType(union.type),
|
||||
superTypes: new Set(),
|
||||
subTypes: new Set(),
|
||||
comment: commentProvider?.getComment(union),
|
||||
};
|
||||
declaredTypes.unions.push(unionType);
|
||||
}
|
||||
|
||||
return declaredTypes;
|
||||
}
|
||||
|
||||
function toPropertyDefaultValue(literal: ValueLiteral): PlainPropertyDefaultValue {
|
||||
if (isBooleanLiteral(literal)) {
|
||||
return literal.true;
|
||||
} else if (isArrayLiteral(literal)) {
|
||||
return literal.elements.map(toPropertyDefaultValue);
|
||||
} else {
|
||||
return literal.value;
|
||||
}
|
||||
}
|
||||
|
||||
export function typeDefinitionToPropertyType(type: TypeDefinition): PlainPropertyType {
|
||||
if (isArrayType(type)) {
|
||||
return {
|
||||
elementType: typeDefinitionToPropertyType(type.elementType)
|
||||
};
|
||||
} else if (isReferenceType(type)) {
|
||||
return {
|
||||
referenceType: typeDefinitionToPropertyType(type.referenceType),
|
||||
isMulti: type.isMulti,
|
||||
isSingle: !type.isMulti
|
||||
};
|
||||
} else if (isUnionType(type)) {
|
||||
return {
|
||||
types: type.types.map(typeDefinitionToPropertyType)
|
||||
};
|
||||
} else if (isSimpleType(type)) {
|
||||
let value: string | undefined;
|
||||
if (type.primitiveType) {
|
||||
value = type.primitiveType;
|
||||
return {
|
||||
primitive: value
|
||||
};
|
||||
} else if (type.stringType) {
|
||||
value = type.stringType;
|
||||
return {
|
||||
string: value
|
||||
};
|
||||
} else if (type.typeRef) {
|
||||
const ref = type.typeRef.ref;
|
||||
const value = getTypeNameWithoutError(ref);
|
||||
if (value) {
|
||||
if (isPrimitiveGrammarType(value)) {
|
||||
return {
|
||||
primitive: value
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
primitive: 'unknown'
|
||||
};
|
||||
}
|
||||
1463
frontend/node_modules/langium/src/grammar/validation/validator.ts
generated
vendored
Normal file
1463
frontend/node_modules/langium/src/grammar/validation/validator.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
frontend/node_modules/langium/src/index.ts
generated
vendored
Normal file
27
frontend/node_modules/langium/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
*
|
||||
* @module langium
|
||||
*/
|
||||
|
||||
export * from './default-module.js';
|
||||
export * from './dependency-injection.js';
|
||||
export * from './service-registry.js';
|
||||
export * from './services.js';
|
||||
export * from './syntax-tree.js';
|
||||
export * from './documentation/index.js';
|
||||
export * from './languages/index.js';
|
||||
export * from './parser/index.js';
|
||||
export * from './references/index.js';
|
||||
export * from './serializer/index.js';
|
||||
export * from './utils/index.js';
|
||||
export * from './validation/index.js';
|
||||
export * from './workspace/index.js';
|
||||
|
||||
// Export the Langium Grammar AST definitions in the `GrammarAST` namespace
|
||||
import * as GrammarAST from './languages/generated/ast.js';
|
||||
import type { Grammar } from './languages/generated/ast.js';
|
||||
export type { Grammar };
|
||||
export { GrammarAST };
|
||||
134
frontend/node_modules/langium/src/lsp/call-hierarchy-provider.ts
generated
vendored
Normal file
134
frontend/node_modules/langium/src/lsp/call-hierarchy-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, } from 'vscode-languageserver';
|
||||
import type { CancellationToken } from '../utils/cancellation.js';
|
||||
import type { GrammarConfig } from '../languages/grammar-config.js';
|
||||
import type { NameProvider } from '../references/name-provider.js';
|
||||
import type { References } from '../references/references.js';
|
||||
import type { LangiumServices } from './lsp-services.js';
|
||||
import type { AstNode } from '../syntax-tree.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import type { ReferenceDescription } from '../workspace/ast-descriptions.js';
|
||||
import type { LangiumDocument, LangiumDocuments } from '../workspace/documents.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
import { SymbolKind } from 'vscode-languageserver';
|
||||
import { findDeclarationNodeAtOffset } from '../utils/cst-utils.js';
|
||||
import { URI } from '../utils/uri-utils.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for handling call hierarchy requests.
|
||||
*/
|
||||
export interface CallHierarchyProvider {
|
||||
prepareCallHierarchy(document: LangiumDocument, params: CallHierarchyPrepareParams, cancelToken?: CancellationToken): MaybePromise<CallHierarchyItem[] | undefined>;
|
||||
|
||||
incomingCalls(params: CallHierarchyIncomingCallsParams, cancelToken?: CancellationToken): MaybePromise<CallHierarchyIncomingCall[] | undefined>;
|
||||
|
||||
outgoingCalls(params: CallHierarchyOutgoingCallsParams, cancelToken?: CancellationToken): MaybePromise<CallHierarchyOutgoingCall[] | undefined>;
|
||||
}
|
||||
|
||||
export abstract class AbstractCallHierarchyProvider implements CallHierarchyProvider {
|
||||
protected readonly grammarConfig: GrammarConfig;
|
||||
protected readonly nameProvider: NameProvider;
|
||||
protected readonly documents: LangiumDocuments;
|
||||
protected readonly references: References;
|
||||
|
||||
constructor(services: LangiumServices) {
|
||||
this.grammarConfig = services.parser.GrammarConfig;
|
||||
this.nameProvider = services.references.NameProvider;
|
||||
this.documents = services.shared.workspace.LangiumDocuments;
|
||||
this.references = services.references.References;
|
||||
}
|
||||
|
||||
prepareCallHierarchy(document: LangiumDocument<AstNode>, params: CallHierarchyPrepareParams): MaybePromise<CallHierarchyItem[] | undefined> {
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.position),
|
||||
this.grammarConfig.nameRegexp
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const declarationNodes = this.references.findDeclarationNodes(targetNode);
|
||||
if (!declarationNodes) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: CallHierarchyItem[] = [];
|
||||
for (const declarationNode of declarationNodes) {
|
||||
items.push(...(this.getCallHierarchyItems(declarationNode.astNode, document) ?? []));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
protected getCallHierarchyItems(targetNode: AstNode, document: LangiumDocument<AstNode>): CallHierarchyItem[] | undefined {
|
||||
const nameNode = this.nameProvider.getNameNode(targetNode);
|
||||
const name = this.nameProvider.getName(targetNode);
|
||||
if (!nameNode || !targetNode.$cstNode || name === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{
|
||||
kind: SymbolKind.Method,
|
||||
name,
|
||||
range: targetNode.$cstNode.range,
|
||||
selectionRange: nameNode.range,
|
||||
uri: document.uri.toString(),
|
||||
...this.getCallHierarchyItem(targetNode)
|
||||
}];
|
||||
}
|
||||
|
||||
protected getCallHierarchyItem(_targetNode: AstNode): Partial<CallHierarchyItem> | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async incomingCalls(params: CallHierarchyIncomingCallsParams): Promise<CallHierarchyIncomingCall[] | undefined> {
|
||||
const document = await this.documents.getOrCreateDocument(URI.parse(params.item.uri));
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.item.range.start),
|
||||
this.grammarConfig.nameRegexp
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const references = this.references.findReferences(
|
||||
targetNode.astNode,
|
||||
{
|
||||
includeDeclaration: false
|
||||
}
|
||||
);
|
||||
return this.getIncomingCalls(targetNode.astNode, references);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to collect the incoming calls for your language
|
||||
*/
|
||||
protected abstract getIncomingCalls(node: AstNode, references: Stream<ReferenceDescription>): MaybePromise<CallHierarchyIncomingCall[] | undefined>;
|
||||
|
||||
async outgoingCalls(params: CallHierarchyOutgoingCallsParams): Promise<CallHierarchyOutgoingCall[] | undefined> {
|
||||
const document = await this.documents.getOrCreateDocument(URI.parse(params.item.uri));
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.item.range.start),
|
||||
this.grammarConfig.nameRegexp
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getOutgoingCalls(targetNode.astNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to collect the outgoing calls for your language
|
||||
*/
|
||||
protected abstract getOutgoingCalls(node: AstNode): MaybePromise<CallHierarchyOutgoingCall[] | undefined>;
|
||||
}
|
||||
14
frontend/node_modules/langium/src/lsp/code-lens-provider.ts
generated
vendored
Normal file
14
frontend/node_modules/langium/src/lsp/code-lens-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { CodeLens, CodeLensParams } from 'vscode-languageserver';
|
||||
import type { CancellationToken } from '../utils/cancellation.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
import type { LangiumDocument } from '../workspace/documents.js';
|
||||
|
||||
export interface CodeLensProvider {
|
||||
provideCodeLens(document: LangiumDocument, params: CodeLensParams, cancelToken?: CancellationToken): MaybePromise<CodeLens[] | undefined>
|
||||
}
|
||||
22
frontend/node_modules/langium/src/lsp/declaration-provider.ts
generated
vendored
Normal file
22
frontend/node_modules/langium/src/lsp/declaration-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { DeclarationParams, LocationLink } from 'vscode-languageserver';
|
||||
import type { CancellationToken } from '../utils/cancellation.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
import type { LangiumDocument } from '../workspace/documents.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for handling go to declaration requests
|
||||
*/
|
||||
export interface DeclarationProvider {
|
||||
/**
|
||||
* Handle a go to declaration request.
|
||||
* @throws `OperationCancelled` if cancellation is detected during execution
|
||||
* @throws `ResponseError` if an error is detected that should be sent as response to the client
|
||||
*/
|
||||
getDeclaration(document: LangiumDocument, params: DeclarationParams, cancelToken?: CancellationToken): MaybePromise<LocationLink[] | undefined>
|
||||
}
|
||||
71
frontend/node_modules/langium/src/lsp/fuzzy-matcher.ts
generated
vendored
Normal file
71
frontend/node_modules/langium/src/lsp/fuzzy-matcher.ts
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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.
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* This service implements a [fuzzy matching](https://en.wikipedia.org/wiki/Approximate_string_matching) method.
|
||||
*/
|
||||
export interface FuzzyMatcher {
|
||||
/**
|
||||
* Performs [fuzzy matching](https://en.wikipedia.org/wiki/Approximate_string_matching).
|
||||
*
|
||||
* Fuzzy matching improves search/completion user experience by allowing to omit characters.
|
||||
* For example, a query such as `FuMa` matches the text `FuzzyMatcher`.
|
||||
*
|
||||
* @param query The user input search query.
|
||||
* @param text The text that should be matched against the query.
|
||||
* @returns Whether the query matches the text.
|
||||
*/
|
||||
match(query: string, text: string): boolean;
|
||||
}
|
||||
|
||||
export class DefaultFuzzyMatcher implements FuzzyMatcher {
|
||||
|
||||
match(query: string, text: string): boolean {
|
||||
if (query.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let matchedFirstCharacter = false;
|
||||
let previous: number | undefined;
|
||||
let character = 0;
|
||||
const len = text.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const strChar = text.charCodeAt(i);
|
||||
const testChar = query.charCodeAt(character);
|
||||
if (strChar === testChar || this.toUpperCharCode(strChar) === this.toUpperCharCode(testChar)) {
|
||||
matchedFirstCharacter ||=
|
||||
previous === undefined || // Beginning of word
|
||||
this.isWordTransition(previous, strChar);
|
||||
if (matchedFirstCharacter) {
|
||||
character++;
|
||||
}
|
||||
if (character === query.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
previous = strChar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected isWordTransition(previous: number, current: number): boolean {
|
||||
return a <= previous && previous <= z && A <= current && current <= Z || // camelCase transition
|
||||
previous === _ && current !== _; // snake_case transition
|
||||
}
|
||||
|
||||
protected toUpperCharCode(charCode: number) {
|
||||
if (a <= charCode && charCode <= z) {
|
||||
return charCode - 32;
|
||||
}
|
||||
return charCode;
|
||||
}
|
||||
}
|
||||
|
||||
const a = 'a'.charCodeAt(0);
|
||||
const z = 'z'.charCodeAt(0);
|
||||
const A = 'A'.charCodeAt(0);
|
||||
const Z = 'Z'.charCodeAt(0);
|
||||
const _ = '_'.charCodeAt(0);
|
||||
814
frontend/node_modules/langium/src/lsp/language-server.ts
generated
vendored
Normal file
814
frontend/node_modules/langium/src/lsp/language-server.ts
generated
vendored
Normal file
@@ -0,0 +1,814 @@
|
||||
/******************************************************************************
|
||||
* 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 {
|
||||
CallHierarchyIncomingCallsParams,
|
||||
CallHierarchyOutgoingCallsParams,
|
||||
CancellationToken,
|
||||
Connection,
|
||||
Disposable,
|
||||
Event,
|
||||
HandlerResult,
|
||||
InitializedParams,
|
||||
InitializeParams,
|
||||
InitializeResult,
|
||||
RequestHandler,
|
||||
SemanticTokens,
|
||||
SemanticTokensDelta,
|
||||
SemanticTokensDeltaParams,
|
||||
SemanticTokensDeltaPartialResult,
|
||||
SemanticTokensParams,
|
||||
SemanticTokensPartialResult,
|
||||
SemanticTokensRangeParams,
|
||||
ServerRequestHandler,
|
||||
TextDocumentIdentifier,
|
||||
TypeHierarchySubtypesParams,
|
||||
TypeHierarchySupertypesParams
|
||||
} from 'vscode-languageserver';
|
||||
import { DidChangeConfigurationNotification, Emitter, LSPErrorCodes, ResponseError, TextDocumentSyncKind } from 'vscode-languageserver-protocol';
|
||||
import { eagerLoad } from '../dependency-injection.js';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import { isOperationCancelled } from '../utils/promise-utils.js';
|
||||
import { URI } from '../utils/uri-utils.js';
|
||||
import type { ConfigurationInitializedParams } from '../workspace/configuration.js';
|
||||
import { DocumentState, type LangiumDocument } from '../workspace/documents.js';
|
||||
import { mergeCompletionProviderOptions } from './completion/completion-provider.js';
|
||||
import type { LangiumSharedServices, PartialLangiumLSPServices } from './lsp-services.js';
|
||||
import { mergeSemanticTokenProviderOptions } from './semantic-token-provider.js';
|
||||
import { mergeSignatureHelpOptions } from './signature-help-provider.js';
|
||||
|
||||
export interface LanguageServer {
|
||||
initialize(params: InitializeParams): Promise<InitializeResult>
|
||||
initialized(params: InitializedParams): void
|
||||
onInitialize(callback: (params: InitializeParams) => void): Disposable
|
||||
onInitialized(callback: (params: InitializedParams) => void): Disposable
|
||||
}
|
||||
|
||||
/**
|
||||
* Language-specific core and optional LSP services.
|
||||
* To be used while accessing the language-specific services via the service registry without a-priori knowledge about the presence of LSP services for the particular languages.
|
||||
* Shared services should be accessed via the language server's `services` property.
|
||||
*/
|
||||
export type LangiumCoreAndPartialLSPServices = Omit<LangiumCoreServices & PartialLangiumLSPServices, 'shared'>
|
||||
|
||||
export class DefaultLanguageServer implements LanguageServer {
|
||||
|
||||
protected onInitializeEmitter = new Emitter<InitializeParams>();
|
||||
protected onInitializedEmitter = new Emitter<InitializedParams>();
|
||||
|
||||
protected readonly services: LangiumSharedServices;
|
||||
|
||||
constructor(services: LangiumSharedServices) {
|
||||
this.services = services;
|
||||
}
|
||||
|
||||
get onInitialize(): Event<InitializeParams> {
|
||||
return this.onInitializeEmitter.event;
|
||||
}
|
||||
|
||||
get onInitialized(): Event<InitializedParams> {
|
||||
return this.onInitializedEmitter.event;
|
||||
}
|
||||
|
||||
async initialize(params: InitializeParams): Promise<InitializeResult> {
|
||||
this.eagerLoadServices();
|
||||
|
||||
this.fireInitializeOnDefaultServices(params);
|
||||
|
||||
this.onInitializeEmitter.fire(params);
|
||||
this.onInitializeEmitter.dispose();
|
||||
return this.buildInitializeResult(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly loads all services before emitting the `onInitialize` event.
|
||||
* Ensures that all services are able to catch the event.
|
||||
*/
|
||||
protected eagerLoadServices(): void {
|
||||
eagerLoad(this.services);
|
||||
this.services.ServiceRegistry.all.forEach(language => eagerLoad(language));
|
||||
}
|
||||
|
||||
protected hasService(callback: (language: LangiumCoreAndPartialLSPServices) => object | undefined): boolean {
|
||||
const allServices: readonly LangiumCoreAndPartialLSPServices[] = this.services.ServiceRegistry.all;
|
||||
return allServices.some(services => callback(services) !== undefined);
|
||||
}
|
||||
|
||||
protected buildInitializeResult(_params: InitializeParams): InitializeResult {
|
||||
const documentUpdateHandler = this.services.lsp.DocumentUpdateHandler;
|
||||
const fileOperationOptions = this.services.lsp.FileOperationHandler?.fileOperationOptions;
|
||||
const allServices: readonly LangiumCoreAndPartialLSPServices[] = this.services.ServiceRegistry.all;
|
||||
const hasFormattingService = this.hasService(e => e.lsp?.Formatter);
|
||||
const formattingOnTypeOptions = allServices.map(e => e.lsp?.Formatter?.formatOnTypeOptions).find(e => Boolean(e));
|
||||
const hasCodeActionProvider = this.hasService(e => e.lsp?.CodeActionProvider);
|
||||
const hasSemanticTokensProvider = this.hasService(e => e.lsp?.SemanticTokenProvider);
|
||||
const semanticTokensOptions = mergeSemanticTokenProviderOptions(allServices.map(e => e.lsp?.SemanticTokenProvider?.semanticTokensOptions));
|
||||
const commandNames = this.services.lsp?.ExecuteCommandHandler?.commands;
|
||||
const hasDocumentLinkProvider = this.hasService(e => e.lsp?.DocumentLinkProvider);
|
||||
const signatureHelpOptions = mergeSignatureHelpOptions(allServices.map(e => e.lsp?.SignatureHelp?.signatureHelpOptions));
|
||||
const hasGoToTypeProvider = this.hasService(e => e.lsp?.TypeProvider);
|
||||
const hasGoToImplementationProvider = this.hasService(e => e.lsp?.ImplementationProvider);
|
||||
const hasCompletionProvider = this.hasService(e => e.lsp?.CompletionProvider);
|
||||
const completionOptions = mergeCompletionProviderOptions(allServices.map(e => e.lsp?.CompletionProvider?.completionOptions));
|
||||
const hasReferencesProvider = this.hasService(e => e.lsp?.ReferencesProvider);
|
||||
const hasDocumentSymbolProvider = this.hasService(e => e.lsp?.DocumentSymbolProvider);
|
||||
const hasDefinitionProvider = this.hasService(e => e.lsp?.DefinitionProvider);
|
||||
const hasDocumentHighlightProvider = this.hasService(e => e.lsp?.DocumentHighlightProvider);
|
||||
const hasFoldingRangeProvider = this.hasService(e => e.lsp?.FoldingRangeProvider);
|
||||
const hasHoverProvider = this.hasService(e => e.lsp?.HoverProvider);
|
||||
const hasRenameProvider = this.hasService(e => e.lsp?.RenameProvider);
|
||||
const hasCallHierarchyProvider = this.hasService(e => e.lsp?.CallHierarchyProvider);
|
||||
const hasTypeHierarchyProvider = this.hasService((e) => e.lsp?.TypeHierarchyProvider);
|
||||
const hasCodeLensProvider = this.hasService(e => e.lsp?.CodeLensProvider);
|
||||
const hasDeclarationProvider = this.hasService(e => e.lsp?.DeclarationProvider);
|
||||
const hasInlayHintProvider = this.hasService(e => e.lsp?.InlayHintProvider);
|
||||
const workspaceSymbolProvider = this.services.lsp?.WorkspaceSymbolProvider;
|
||||
|
||||
const result: InitializeResult = {
|
||||
capabilities: {
|
||||
workspace: {
|
||||
workspaceFolders: {
|
||||
supported: true
|
||||
},
|
||||
fileOperations: fileOperationOptions
|
||||
},
|
||||
executeCommandProvider: commandNames && {
|
||||
commands: commandNames
|
||||
},
|
||||
textDocumentSync: {
|
||||
change: TextDocumentSyncKind.Incremental,
|
||||
openClose: true,
|
||||
save: Boolean(documentUpdateHandler.didSaveDocument),
|
||||
willSave: Boolean(documentUpdateHandler.willSaveDocument),
|
||||
willSaveWaitUntil: Boolean(documentUpdateHandler.willSaveDocumentWaitUntil)
|
||||
},
|
||||
completionProvider: hasCompletionProvider ? completionOptions : undefined,
|
||||
referencesProvider: hasReferencesProvider,
|
||||
documentSymbolProvider: hasDocumentSymbolProvider,
|
||||
definitionProvider: hasDefinitionProvider,
|
||||
typeDefinitionProvider: hasGoToTypeProvider,
|
||||
documentHighlightProvider: hasDocumentHighlightProvider,
|
||||
codeActionProvider: hasCodeActionProvider,
|
||||
documentFormattingProvider: hasFormattingService,
|
||||
documentRangeFormattingProvider: hasFormattingService,
|
||||
documentOnTypeFormattingProvider: formattingOnTypeOptions,
|
||||
foldingRangeProvider: hasFoldingRangeProvider,
|
||||
hoverProvider: hasHoverProvider,
|
||||
renameProvider: hasRenameProvider ? {
|
||||
prepareProvider: true
|
||||
} : undefined,
|
||||
semanticTokensProvider: hasSemanticTokensProvider
|
||||
? semanticTokensOptions
|
||||
: undefined,
|
||||
signatureHelpProvider: signatureHelpOptions,
|
||||
implementationProvider: hasGoToImplementationProvider,
|
||||
callHierarchyProvider: hasCallHierarchyProvider
|
||||
? {}
|
||||
: undefined,
|
||||
typeHierarchyProvider: hasTypeHierarchyProvider
|
||||
? {}
|
||||
: undefined,
|
||||
documentLinkProvider: hasDocumentLinkProvider
|
||||
? { resolveProvider: false }
|
||||
: undefined,
|
||||
codeLensProvider: hasCodeLensProvider
|
||||
? { resolveProvider: false }
|
||||
: undefined,
|
||||
declarationProvider: hasDeclarationProvider,
|
||||
inlayHintProvider: hasInlayHintProvider
|
||||
? { resolveProvider: false }
|
||||
: undefined,
|
||||
workspaceSymbolProvider: workspaceSymbolProvider
|
||||
? { resolveProvider: Boolean(workspaceSymbolProvider.resolveSymbol) }
|
||||
: undefined
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
initialized(params: InitializedParams): void {
|
||||
this.fireInitializedOnDefaultServices(params);
|
||||
|
||||
this.onInitializedEmitter.fire(params);
|
||||
this.onInitializedEmitter.dispose();
|
||||
}
|
||||
|
||||
protected fireInitializeOnDefaultServices(params: InitializeParams): void {
|
||||
this.services.workspace.ConfigurationProvider.initialize(params);
|
||||
this.services.workspace.WorkspaceManager.initialize(params);
|
||||
}
|
||||
|
||||
protected fireInitializedOnDefaultServices(params: InitializedParams): void {
|
||||
const connection = this.services.lsp.Connection;
|
||||
const configurationParams = connection ? <ConfigurationInitializedParams>{
|
||||
...params,
|
||||
register: params => connection.client.register(DidChangeConfigurationNotification.type, params),
|
||||
fetchConfiguration: params => connection.workspace.getConfiguration(params)
|
||||
} : params;
|
||||
|
||||
// do not await the promises of the following calls, as they must not block the initialization process!
|
||||
// otherwise, there is the danger of out-of-order processing of subsequent incoming messages from the language client
|
||||
// however, awaiting should be possible in general, e.g. in unit test scenarios
|
||||
this.services.workspace.ConfigurationProvider.initialized(configurationParams)
|
||||
.catch(err => console.error('Error in ConfigurationProvider initialization:', err));
|
||||
this.services.workspace.WorkspaceManager.initialized(params)
|
||||
.catch(err => console.error('Error in WorkspaceManager initialization:', err));
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceRequirements {
|
||||
readonly CallHierarchyProvider: ServiceRequirement
|
||||
readonly CodeActionProvider: ServiceRequirement
|
||||
readonly CodeLensProvider: ServiceRequirement
|
||||
readonly CompletionProvider: ServiceRequirement
|
||||
readonly DeclarationProvider: ServiceRequirement
|
||||
readonly DefinitionProvider: ServiceRequirement
|
||||
readonly DocumentHighlightProvider: ServiceRequirement
|
||||
readonly DocumentLinkProvider: ServiceRequirement
|
||||
readonly DocumentSymbolProvider: ServiceRequirement
|
||||
readonly FoldingRangeProvider: ServiceRequirement
|
||||
readonly Formatter: ServiceRequirement
|
||||
readonly HoverProvider: ServiceRequirement
|
||||
readonly ImplementationProvider: ServiceRequirement
|
||||
readonly InlayHintProvider: ServiceRequirement
|
||||
readonly ReferencesProvider: ServiceRequirement
|
||||
readonly RenameProvider: ServiceRequirement
|
||||
readonly SemanticTokenProvider: ServiceRequirement
|
||||
readonly SignatureHelp: ServiceRequirement
|
||||
readonly TypeHierarchyProvider: ServiceRequirement
|
||||
readonly TypeProvider: ServiceRequirement
|
||||
readonly WorkspaceSymbolProvider: ServiceRequirement
|
||||
}
|
||||
|
||||
export type ServiceRequirement = DocumentState | {
|
||||
// Either wait for the specific document or for the whole workspace to arrive at the given state
|
||||
readonly type: 'document' | 'workspace';
|
||||
readonly state: DocumentState;
|
||||
}
|
||||
|
||||
function isDocumentState(obj: ServiceRequirement): obj is DocumentState {
|
||||
return typeof obj === 'number';
|
||||
}
|
||||
|
||||
export namespace WorkspaceState {
|
||||
export const Parsed: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.Parsed });
|
||||
export const IndexedContent: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.IndexedContent });
|
||||
export const ComputedScopes: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.ComputedScopes });
|
||||
export const Linked: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.Linked });
|
||||
export const IndexedReferences: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.IndexedReferences });
|
||||
export const Validated: ServiceRequirement = Object.freeze({ type: 'workspace', state: DocumentState.Validated });
|
||||
}
|
||||
|
||||
export function startLanguageServer(services: LangiumSharedServices, serviceRequirements: Partial<ServiceRequirements> = {}): void {
|
||||
const connection = services.lsp.Connection;
|
||||
if (!connection) {
|
||||
throw new Error('Starting a language server requires the languageServer.Connection service to be set.');
|
||||
}
|
||||
|
||||
addDocumentUpdateHandler(connection, services);
|
||||
addFileOperationHandler(connection, services);
|
||||
addDiagnosticsHandler(connection, services);
|
||||
addCompletionHandler(connection, services, serviceRequirements.CompletionProvider);
|
||||
addFindReferencesHandler(connection, services, serviceRequirements.ReferencesProvider);
|
||||
addDocumentSymbolHandler(connection, services, serviceRequirements.DocumentSymbolProvider);
|
||||
addGotoDefinitionHandler(connection, services, serviceRequirements.DefinitionProvider);
|
||||
addGoToTypeDefinitionHandler(connection, services, serviceRequirements.TypeProvider);
|
||||
addGoToImplementationHandler(connection, services, serviceRequirements.ImplementationProvider);
|
||||
addDocumentHighlightHandler(connection, services, serviceRequirements.DocumentHighlightProvider);
|
||||
addFoldingRangeHandler(connection, services, serviceRequirements.FoldingRangeProvider);
|
||||
addFormattingHandler(connection, services, serviceRequirements.Formatter);
|
||||
addCodeActionHandler(connection, services, serviceRequirements.CodeActionProvider);
|
||||
addRenameHandler(connection, services, serviceRequirements.RenameProvider);
|
||||
addHoverHandler(connection, services, serviceRequirements.HoverProvider);
|
||||
addInlayHintHandler(connection, services, serviceRequirements.InlayHintProvider);
|
||||
addSemanticTokenHandler(connection, services, serviceRequirements.SemanticTokenProvider);
|
||||
addExecuteCommandHandler(connection, services);
|
||||
addSignatureHelpHandler(connection, services, serviceRequirements.SignatureHelp);
|
||||
addCallHierarchyHandler(connection, services, serviceRequirements.CallHierarchyProvider);
|
||||
addTypeHierarchyHandler(connection, services, serviceRequirements.TypeHierarchyProvider);
|
||||
addCodeLensHandler(connection, services, serviceRequirements.CodeLensProvider);
|
||||
addDocumentLinkHandler(connection, services, serviceRequirements.DocumentLinkProvider);
|
||||
addConfigurationChangeHandler(connection, services);
|
||||
addGoToDeclarationHandler(connection, services, serviceRequirements.DeclarationProvider);
|
||||
addWorkspaceSymbolHandler(connection, services, serviceRequirements.WorkspaceSymbolProvider);
|
||||
|
||||
connection.onInitialize(params => {
|
||||
return services.lsp.LanguageServer.initialize(params);
|
||||
});
|
||||
connection.onInitialized(params => {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler for document updates when content changes, or watch catches a change.
|
||||
* In the case there is no handler service registered, this function does nothing.
|
||||
*/
|
||||
export function addDocumentUpdateHandler(connection: Connection, services: LangiumSharedServices): void {
|
||||
const handler = services.lsp.DocumentUpdateHandler;
|
||||
const documents = services.workspace.TextDocuments;
|
||||
if (handler.didOpenDocument) {
|
||||
documents.onDidOpen(change => handler.didOpenDocument!(change));
|
||||
}
|
||||
if (handler.didChangeContent) {
|
||||
documents.onDidChangeContent(change => handler.didChangeContent!(change));
|
||||
}
|
||||
if (handler.didCloseDocument) {
|
||||
documents.onDidClose(change => handler.didCloseDocument!(change));
|
||||
}
|
||||
if (handler.didSaveDocument) {
|
||||
documents.onDidSave(change => handler.didSaveDocument!(change));
|
||||
}
|
||||
if (handler.willSaveDocument) {
|
||||
documents.onWillSave(event => handler.willSaveDocument!(event));
|
||||
}
|
||||
if (handler.willSaveDocumentWaitUntil) {
|
||||
documents.onWillSaveWaitUntil(event => handler.willSaveDocumentWaitUntil!(event));
|
||||
}
|
||||
if (handler.didChangeWatchedFiles) {
|
||||
connection.onDidChangeWatchedFiles(params => handler.didChangeWatchedFiles!(params));
|
||||
}
|
||||
}
|
||||
|
||||
export function addFileOperationHandler(connection: Connection, services: LangiumSharedServices): void {
|
||||
const handler = services.lsp.FileOperationHandler;
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
if (handler.didCreateFiles) {
|
||||
connection.workspace.onDidCreateFiles(params => handler.didCreateFiles!(params));
|
||||
}
|
||||
if (handler.didRenameFiles) {
|
||||
connection.workspace.onDidRenameFiles(params => handler.didRenameFiles!(params));
|
||||
}
|
||||
if (handler.didDeleteFiles) {
|
||||
connection.workspace.onDidDeleteFiles(params => handler.didDeleteFiles!(params));
|
||||
}
|
||||
if (handler.willCreateFiles) {
|
||||
connection.workspace.onWillCreateFiles(params => handler.willCreateFiles!(params));
|
||||
}
|
||||
if (handler.willRenameFiles) {
|
||||
connection.workspace.onWillRenameFiles(params => handler.willRenameFiles!(params));
|
||||
}
|
||||
if (handler.willDeleteFiles) {
|
||||
connection.workspace.onWillDeleteFiles(params => handler.willDeleteFiles!(params));
|
||||
}
|
||||
}
|
||||
|
||||
export function addDiagnosticsHandler(connection: Connection, services: LangiumSharedServices): void {
|
||||
const documentBuilder = services.workspace.DocumentBuilder;
|
||||
documentBuilder.onUpdate(async (_, deleted) => {
|
||||
for (const uri of deleted) {
|
||||
connection.sendDiagnostics({
|
||||
uri: uri.toString(),
|
||||
diagnostics: []
|
||||
});
|
||||
}
|
||||
});
|
||||
documentBuilder.onDocumentPhase(DocumentState.Validated, async (document) => {
|
||||
if (document.diagnostics) {
|
||||
connection.sendDiagnostics({
|
||||
uri: document.uri.toString(),
|
||||
diagnostics: document.diagnostics
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addCompletionHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
connection.onCompletion(createRequestHandler(
|
||||
(services, document, params, cancelToken) => {
|
||||
return services.lsp?.CompletionProvider?.getCompletion(document, params, cancelToken);
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addFindReferencesHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
connection.onReferences(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.ReferencesProvider?.findReferences(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addCodeActionHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Validated): void {
|
||||
connection.onCodeAction(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.CodeActionProvider?.getCodeActions(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addDocumentSymbolHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Parsed): void {
|
||||
connection.onDocumentSymbol(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.DocumentSymbolProvider?.getSymbols(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addGotoDefinitionHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
connection.onDefinition(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.DefinitionProvider?.getDefinition(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addGoToTypeDefinitionHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
connection.onTypeDefinition(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.TypeProvider?.getTypeDefinition(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addGoToImplementationHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
connection.onImplementation(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.ImplementationProvider?.getImplementation(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addGoToDeclarationHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
connection.onDeclaration(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.DeclarationProvider?.getDeclaration(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addDocumentHighlightHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
connection.onDocumentHighlight(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.DocumentHighlightProvider?.getDocumentHighlight(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addHoverHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
connection.onHover(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.HoverProvider?.getHoverContent(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addFoldingRangeHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Parsed): void {
|
||||
connection.onFoldingRanges(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.FoldingRangeProvider?.getFoldingRanges(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addFormattingHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Parsed): void {
|
||||
connection.onDocumentFormatting(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocument(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
connection.onDocumentRangeFormatting(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentRange(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
connection.onDocumentOnTypeFormatting(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentOnType(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addRenameHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
|
||||
connection.onRenameRequest(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.RenameProvider?.rename(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
connection.onPrepareRename(createRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.RenameProvider?.prepareRename(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addInlayHintHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.IndexedReferences): void {
|
||||
connection.languages.inlayHint.on(createServerRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.InlayHintProvider?.getInlayHints(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addSemanticTokenHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Linked): void {
|
||||
// If no semantic token provider is registered that's fine. Just return an empty result
|
||||
const emptyResult: SemanticTokens = { data: [] };
|
||||
connection.languages.semanticTokens.on(createServerRequestHandler<SemanticTokensParams, SemanticTokens, SemanticTokensPartialResult, void>(
|
||||
(services, document, params, cancelToken) => {
|
||||
if (services.lsp?.SemanticTokenProvider) {
|
||||
return services.lsp.SemanticTokenProvider.semanticHighlight(document, params, cancelToken);
|
||||
}
|
||||
return emptyResult;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
connection.languages.semanticTokens.onDelta(createServerRequestHandler<SemanticTokensDeltaParams, SemanticTokens | SemanticTokensDelta, SemanticTokensDeltaPartialResult, void>(
|
||||
(services, document, params, cancelToken) => {
|
||||
if (services.lsp?.SemanticTokenProvider) {
|
||||
return services.lsp.SemanticTokenProvider.semanticHighlightDelta(document, params, cancelToken);
|
||||
}
|
||||
return emptyResult;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
connection.languages.semanticTokens.onRange(createServerRequestHandler<SemanticTokensRangeParams, SemanticTokens, SemanticTokensPartialResult, void>(
|
||||
(services, document, params, cancelToken) => {
|
||||
if (services.lsp?.SemanticTokenProvider) {
|
||||
return services.lsp.SemanticTokenProvider.semanticHighlightRange(document, params, cancelToken);
|
||||
}
|
||||
return emptyResult;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
export function addConfigurationChangeHandler(connection: Connection, services: LangiumSharedServices): void {
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
services.workspace.ConfigurationProvider.updateConfiguration(change);
|
||||
});
|
||||
}
|
||||
|
||||
export function addExecuteCommandHandler(connection: Connection, services: LangiumSharedServices): void {
|
||||
const commandHandler = services.lsp.ExecuteCommandHandler;
|
||||
if (commandHandler) {
|
||||
connection.onExecuteCommand(async (params, token) => {
|
||||
try {
|
||||
return await commandHandler.executeCommand(params.command, params.arguments ?? [], token);
|
||||
} catch (err) {
|
||||
return responseError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function addDocumentLinkHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.Parsed): void {
|
||||
connection.onDocumentLinks(createServerRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.DocumentLinkProvider?.getDocumentLinks(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addSignatureHelpHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.IndexedReferences): void {
|
||||
connection.onSignatureHelp(createServerRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.SignatureHelp?.provideSignatureHelp(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addCodeLensHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = DocumentState.IndexedReferences): void {
|
||||
connection.onCodeLens(createServerRequestHandler(
|
||||
(services, document, params, cancelToken) => services.lsp?.CodeLensProvider?.provideCodeLens(document, params, cancelToken),
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addWorkspaceSymbolHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedContent): void {
|
||||
const workspaceSymbolProvider = services.lsp.WorkspaceSymbolProvider;
|
||||
if (workspaceSymbolProvider) {
|
||||
|
||||
if (isDocumentState(requiredState) || requiredState.type === 'document') {
|
||||
throw new Error('Workspace symbol requests are independent of a certain document, so the given document-specific required state is invalid. Provide a service requirement of type "workspace".');
|
||||
}
|
||||
|
||||
const documentBuilder = services.workspace.DocumentBuilder;
|
||||
connection.onWorkspaceSymbol(async (params, token) => {
|
||||
try {
|
||||
await documentBuilder.waitUntil(requiredState.state, token);
|
||||
return await workspaceSymbolProvider.getSymbols(params, token);
|
||||
} catch (err) {
|
||||
return responseError(err);
|
||||
}
|
||||
});
|
||||
const resolveWorkspaceSymbol = workspaceSymbolProvider.resolveSymbol?.bind(workspaceSymbolProvider);
|
||||
if (resolveWorkspaceSymbol) {
|
||||
connection.onWorkspaceSymbolResolve(async (workspaceSymbol, token) => {
|
||||
try {
|
||||
await documentBuilder.waitUntil(requiredState.state, token);
|
||||
return await resolveWorkspaceSymbol(workspaceSymbol, token);
|
||||
} catch (err) {
|
||||
return responseError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addCallHierarchyHandler(connection: Connection, services: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
connection.languages.callHierarchy.onPrepare(createServerRequestHandler(
|
||||
async (services, document, params, cancelToken) => {
|
||||
if (services.lsp?.CallHierarchyProvider) {
|
||||
const result = await services.lsp.CallHierarchyProvider.prepareCallHierarchy(document, params, cancelToken);
|
||||
return result ?? null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
|
||||
connection.languages.callHierarchy.onIncomingCalls(createHierarchyRequestHandler(
|
||||
async (services, params, cancelToken) => {
|
||||
if (services.lsp?.CallHierarchyProvider) {
|
||||
const result = await services.lsp.CallHierarchyProvider.incomingCalls(params, cancelToken);
|
||||
return result ?? null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
|
||||
connection.languages.callHierarchy.onOutgoingCalls(createHierarchyRequestHandler(
|
||||
async (services, params, cancelToken) => {
|
||||
if (services.lsp?.CallHierarchyProvider) {
|
||||
const result = await services.lsp.CallHierarchyProvider.outgoingCalls(params, cancelToken);
|
||||
return result ?? null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
services,
|
||||
requiredState
|
||||
));
|
||||
}
|
||||
|
||||
export function addTypeHierarchyHandler(connection: Connection, sharedServices: LangiumSharedServices, requiredState: ServiceRequirement = WorkspaceState.IndexedReferences): void {
|
||||
// Don't register type hierarchy handlers if no type hierarchy provider is registered
|
||||
if (!sharedServices.ServiceRegistry.all.some((services: LangiumCoreAndPartialLSPServices) => services.lsp?.TypeHierarchyProvider)) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection.languages.typeHierarchy.onPrepare(
|
||||
createServerRequestHandler(
|
||||
async (services, document, params, cancelToken) => {
|
||||
const result = await services.lsp?.TypeHierarchyProvider?.prepareTypeHierarchy(document, params, cancelToken);
|
||||
return result ?? null;
|
||||
},
|
||||
sharedServices,
|
||||
requiredState
|
||||
),
|
||||
);
|
||||
|
||||
connection.languages.typeHierarchy.onSupertypes(
|
||||
createHierarchyRequestHandler(
|
||||
async (services, params, cancelToken) => {
|
||||
const result = await services.lsp?.TypeHierarchyProvider?.supertypes(params, cancelToken);
|
||||
return result ?? null;
|
||||
},
|
||||
sharedServices,
|
||||
requiredState
|
||||
),
|
||||
);
|
||||
|
||||
connection.languages.typeHierarchy.onSubtypes(
|
||||
createHierarchyRequestHandler(
|
||||
async (services, params, cancelToken) => {
|
||||
const result = await services.lsp?.TypeHierarchyProvider?.subtypes(params, cancelToken);
|
||||
return result ?? null;
|
||||
},
|
||||
sharedServices,
|
||||
requiredState
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function createHierarchyRequestHandler<P extends TypeHierarchySupertypesParams | TypeHierarchySubtypesParams | CallHierarchyIncomingCallsParams | CallHierarchyOutgoingCallsParams, R, PR, E = void>(
|
||||
serviceCall: (services: LangiumCoreAndPartialLSPServices, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
|
||||
sharedServices: LangiumSharedServices,
|
||||
requiredState?: ServiceRequirement
|
||||
): ServerRequestHandler<P, R, PR, E> {
|
||||
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, requiredState);
|
||||
if (cancellationError) {
|
||||
return cancellationError;
|
||||
}
|
||||
if (!serviceRegistry.hasServices(uri)) {
|
||||
const errorText = `Could not find service instance for uri: '${uri}'`;
|
||||
console.debug(errorText);
|
||||
return responseError<E>(new Error(errorText));
|
||||
}
|
||||
const language = serviceRegistry.getServices(uri);
|
||||
try {
|
||||
return await serviceCall(language, params, cancelToken);
|
||||
} catch (err) {
|
||||
return responseError<E>(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
requiredState?: ServiceRequirement
|
||||
): 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, requiredState);
|
||||
if (cancellationError) {
|
||||
return cancellationError;
|
||||
}
|
||||
if (!serviceRegistry.hasServices(uri)) {
|
||||
const errorText = `Could not find service instance for uri: '${uri}'`;
|
||||
console.debug(errorText);
|
||||
return responseError<E>(new Error(errorText));
|
||||
}
|
||||
const language = serviceRegistry.getServices(uri);
|
||||
try {
|
||||
const document = await documents.getOrCreateDocument(uri);
|
||||
return await serviceCall(language, document, params, cancelToken);
|
||||
} catch (err) {
|
||||
return responseError<E>(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createRequestHandler<P extends { textDocument: TextDocumentIdentifier }, R, E = void>(
|
||||
serviceCall: (services: LangiumCoreAndPartialLSPServices, document: LangiumDocument, params: P, cancelToken: CancellationToken) => HandlerResult<R, E>,
|
||||
sharedServices: LangiumSharedServices,
|
||||
requiredState?: ServiceRequirement
|
||||
): 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, requiredState);
|
||||
if (cancellationError) {
|
||||
return cancellationError;
|
||||
}
|
||||
if (!serviceRegistry.hasServices(uri)) {
|
||||
console.debug(`Could not find service instance for uri: '${uri.toString()}'`);
|
||||
return null;
|
||||
}
|
||||
const language = serviceRegistry.getServices(uri);
|
||||
try {
|
||||
const document = await documents.getOrCreateDocument(uri);
|
||||
return await serviceCall(language, document, params, cancelToken);
|
||||
} catch (err) {
|
||||
return responseError<E>(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function waitUntilPhase<E>(services: LangiumSharedServices, cancelToken: CancellationToken, uri?: URI, requiredState?: ServiceRequirement): Promise<ResponseError<E> | undefined> {
|
||||
if (requiredState !== undefined) {
|
||||
const documentBuilder = services.workspace.DocumentBuilder;
|
||||
const workspaceManager = services.workspace.WorkspaceManager;
|
||||
try {
|
||||
await workspaceManager.ready; // mandatory if awaiting the state of a document (uri !== undefined) while the LS is starting
|
||||
if (isDocumentState(requiredState)) {
|
||||
await documentBuilder.waitUntil(requiredState, uri, cancelToken);
|
||||
} else if (requiredState.type === 'document') {
|
||||
await documentBuilder.waitUntil(requiredState.state, uri, cancelToken);
|
||||
} else {
|
||||
await documentBuilder.waitUntil(requiredState.state, 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.');
|
||||
}
|
||||
if (err instanceof ResponseError) {
|
||||
return err;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
45
frontend/node_modules/langium/src/lsp/node-kind-provider.ts
generated
vendored
Normal file
45
frontend/node_modules/langium/src/lsp/node-kind-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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 { AstNode, AstNodeDescription } from '../syntax-tree.js';
|
||||
import { CompletionItemKind, SymbolKind } from 'vscode-languageserver';
|
||||
|
||||
/**
|
||||
* This service consolidates the logic for gathering LSP kind information based on AST nodes or their descriptions.
|
||||
*/
|
||||
export interface NodeKindProvider {
|
||||
/**
|
||||
* Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
|
||||
* @param node AST node or node description.
|
||||
* @returns The corresponding symbol kind.
|
||||
*/
|
||||
getSymbolKind(node: AstNode | AstNodeDescription): SymbolKind;
|
||||
/**
|
||||
* Returns a `CompletionItemKind` as used by the `CompletionProvider`.
|
||||
* @param node AST node or node description.
|
||||
* @returns The corresponding completion item kind.
|
||||
*/
|
||||
getCompletionItemKind(node: AstNode | AstNodeDescription): CompletionItemKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the `NodeKindProvider` interface.
|
||||
* @remarks This implementation returns `SymbolKind.Field` for all nodes and `CompletionItemKind.Reference` for all nodes. Extend this class to customize symbol and completion types your langauge.
|
||||
*/
|
||||
export class DefaultNodeKindProvider implements NodeKindProvider {
|
||||
/**
|
||||
* @remarks The default implementation returns `SymbolKind.Field` for all nodes.
|
||||
*/
|
||||
getSymbolKind(_node: AstNode | AstNodeDescription): SymbolKind {
|
||||
return SymbolKind.Field;
|
||||
}
|
||||
/**
|
||||
* @remarks The default implementation returns `CompletionItemKind.Reference` for all nodes.
|
||||
*/
|
||||
getCompletionItemKind(_node: AstNode | AstNodeDescription): CompletionItemKind {
|
||||
return CompletionItemKind.Reference;
|
||||
}
|
||||
}
|
||||
140
frontend/node_modules/langium/src/lsp/type-hierarchy-provider.ts
generated
vendored
Normal file
140
frontend/node_modules/langium/src/lsp/type-hierarchy-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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 {
|
||||
CancellationToken,
|
||||
TypeHierarchyItem,
|
||||
TypeHierarchyPrepareParams,
|
||||
TypeHierarchySubtypesParams,
|
||||
TypeHierarchySupertypesParams
|
||||
} from 'vscode-languageserver';
|
||||
import { SymbolKind } from 'vscode-languageserver';
|
||||
import type { GrammarConfig } from '../languages/grammar-config.js';
|
||||
import type { NameProvider } from '../references/name-provider.js';
|
||||
import type { References } from '../references/references.js';
|
||||
import type { LangiumServices } from './lsp-services.js';
|
||||
import type { AstNode } from '../syntax-tree.js';
|
||||
import { findDeclarationNodeAtOffset } from '../utils/cst-utils.js';
|
||||
import { URI } from '../utils/uri-utils.js';
|
||||
import type { LangiumDocument, LangiumDocuments } from '../workspace/documents.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for handling type hierarchy requests.
|
||||
*/
|
||||
|
||||
export interface TypeHierarchyProvider {
|
||||
prepareTypeHierarchy(document: LangiumDocument, params: TypeHierarchyPrepareParams, cancelToken?: CancellationToken): MaybePromise<TypeHierarchyItem[] | undefined>;
|
||||
|
||||
supertypes(params: TypeHierarchySupertypesParams, cancelToken?: CancellationToken): MaybePromise<TypeHierarchyItem[] | undefined>;
|
||||
|
||||
subtypes(params: TypeHierarchySubtypesParams, cancelToken?: CancellationToken): MaybePromise<TypeHierarchyItem[] | undefined>;
|
||||
}
|
||||
|
||||
export abstract class AbstractTypeHierarchyProvider implements TypeHierarchyProvider {
|
||||
protected readonly grammarConfig: GrammarConfig;
|
||||
protected readonly nameProvider: NameProvider;
|
||||
protected readonly documents: LangiumDocuments;
|
||||
protected readonly references: References;
|
||||
|
||||
constructor(services: LangiumServices) {
|
||||
this.grammarConfig = services.parser.GrammarConfig;
|
||||
this.nameProvider = services.references.NameProvider;
|
||||
this.documents = services.shared.workspace.LangiumDocuments;
|
||||
this.references = services.references.References;
|
||||
}
|
||||
|
||||
prepareTypeHierarchy(document: LangiumDocument, params: TypeHierarchyPrepareParams, _cancelToken?: CancellationToken): MaybePromise<TypeHierarchyItem[] | undefined> {
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.position),
|
||||
this.grammarConfig.nameRegexp,
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const declarationNodes = this.references.findDeclarationNodes(targetNode);
|
||||
const items: TypeHierarchyItem[] = [];
|
||||
for (const declarationNode of declarationNodes) {
|
||||
items.push(...(this.getTypeHierarchyItems(declarationNode.astNode, document) ?? []));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
protected getTypeHierarchyItems(targetNode: AstNode, document: LangiumDocument): TypeHierarchyItem[] | undefined {
|
||||
const nameNode = this.nameProvider.getNameNode(targetNode);
|
||||
const name = this.nameProvider.getName(targetNode);
|
||||
if (!nameNode || !targetNode.$cstNode || name === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
kind: SymbolKind.Class,
|
||||
name,
|
||||
range: targetNode.$cstNode.range,
|
||||
selectionRange: nameNode.range,
|
||||
uri: document.uri.toString(),
|
||||
...this.getTypeHierarchyItem(targetNode),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to change default properties of the type hierarchy item or add additional ones like `tags`
|
||||
* or `details`.
|
||||
*
|
||||
* @example
|
||||
* // Change the node kind to SymbolKind.Interface
|
||||
* return { kind: SymbolKind.Interface }
|
||||
*
|
||||
* @see NodeKindProvider
|
||||
*/
|
||||
protected getTypeHierarchyItem(_targetNode: AstNode): Partial<TypeHierarchyItem> | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async supertypes(params: TypeHierarchySupertypesParams, _cancelToken?: CancellationToken): Promise<TypeHierarchyItem[] | undefined> {
|
||||
const document = await this.documents.getOrCreateDocument(URI.parse(params.item.uri));
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.item.range.start),
|
||||
this.grammarConfig.nameRegexp,
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getSupertypes(targetNode.astNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to collect the supertypes for your language.
|
||||
*/
|
||||
protected abstract getSupertypes(node: AstNode): MaybePromise<TypeHierarchyItem[] | undefined>;
|
||||
|
||||
async subtypes(params: TypeHierarchySubtypesParams, _cancelToken?: CancellationToken): Promise<TypeHierarchyItem[] | undefined> {
|
||||
const document = await this.documents.getOrCreateDocument(URI.parse(params.item.uri));
|
||||
const rootNode = document.parseResult.value;
|
||||
const targetNode = findDeclarationNodeAtOffset(
|
||||
rootNode.$cstNode,
|
||||
document.textDocument.offsetAt(params.item.range.start),
|
||||
this.grammarConfig.nameRegexp,
|
||||
);
|
||||
if (!targetNode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.getSubtypes(targetNode.astNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to collect the subtypes for your language.
|
||||
*/
|
||||
protected abstract getSubtypes(node: AstNode): MaybePromise<TypeHierarchyItem[] | undefined>;
|
||||
}
|
||||
10
frontend/node_modules/langium/src/node/index.ts
generated
vendored
Normal file
10
frontend/node_modules/langium/src/node/index.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
* @module langium/node
|
||||
*/
|
||||
|
||||
export * from './node-file-system-provider.js';
|
||||
export * from './worker-thread-async-parser.js';
|
||||
433
frontend/node_modules/langium/src/parser/indentation-aware.ts
generated
vendored
Normal file
433
frontend/node_modules/langium/src/parser/indentation-aware.ts
generated
vendored
Normal file
@@ -0,0 +1,433 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 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 { CustomPatternMatcherFunc, TokenType, IToken, IMultiModeLexerDefinition, TokenVocabulary } from 'chevrotain';
|
||||
import type { Grammar, TerminalRule } from '../languages/generated/ast.js';
|
||||
import type { LexingReport, TokenBuilderOptions } from './token-builder.js';
|
||||
import type { LexerResult, TokenizeOptions } from './lexer.js';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import { createToken, createTokenInstance, Lexer } from 'chevrotain';
|
||||
import { DefaultTokenBuilder } from './token-builder.js';
|
||||
import { DEFAULT_TOKENIZE_OPTIONS, DefaultLexer, isTokenTypeArray } from './lexer.js';
|
||||
|
||||
type IndentationAwareDelimiter<TokenName extends string> = [begin: TokenName, end: TokenName];
|
||||
|
||||
export interface IndentationTokenBuilderOptions<TerminalName extends string = string, KeywordName extends string = string> {
|
||||
/**
|
||||
* The name of the token used to denote indentation in the grammar.
|
||||
* A possible definition in the grammar could look like this:
|
||||
* ```langium
|
||||
* terminal INDENT: ':synthetic-indent:';
|
||||
* ```
|
||||
*
|
||||
* @default 'INDENT'
|
||||
*/
|
||||
indentTokenName: TerminalName;
|
||||
/**
|
||||
* The name of the token used to denote deindentation in the grammar.
|
||||
* A possible definition in the grammar could look like this:
|
||||
* ```langium
|
||||
* terminal DEDENT: ':synthetic-dedent:';
|
||||
* ```
|
||||
*
|
||||
* @default 'DEDENT'
|
||||
*/
|
||||
dedentTokenName: TerminalName;
|
||||
/**
|
||||
* The name of the token used to denote whitespace other than indentation and newlines in the grammar.
|
||||
* A possible definition in the grammar could look like this:
|
||||
* ```langium
|
||||
* hidden terminal WS: /[ \t]+/;
|
||||
* ```
|
||||
*
|
||||
* @default 'WS'
|
||||
*/
|
||||
whitespaceTokenName: TerminalName;
|
||||
/**
|
||||
* The delimiter tokens inside of which indentation should be ignored and treated as normal whitespace.
|
||||
* For example, Python doesn't treat any whitespace between `(` and `)` as significant.
|
||||
*
|
||||
* Can be either terminal tokens or keyword tokens.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
ignoreIndentationDelimiters: Array<IndentationAwareDelimiter<TerminalName | KeywordName>>
|
||||
}
|
||||
|
||||
export const indentationBuilderDefaultOptions: IndentationTokenBuilderOptions = {
|
||||
indentTokenName: 'INDENT',
|
||||
dedentTokenName: 'DEDENT',
|
||||
whitespaceTokenName: 'WS',
|
||||
ignoreIndentationDelimiters: [],
|
||||
};
|
||||
|
||||
export enum LexingMode {
|
||||
REGULAR = 'indentation-sensitive',
|
||||
IGNORE_INDENTATION = 'ignore-indentation',
|
||||
}
|
||||
|
||||
export interface IndentationLexingReport extends LexingReport {
|
||||
/** Dedent tokens that are necessary to close the remaining indents. */
|
||||
remainingDedents: IToken[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A token builder that is sensitive to indentation in the input text.
|
||||
* It will generate tokens for indentation and dedentation based on the indentation level.
|
||||
*
|
||||
* The first generic parameter corresponds to the names of terminal tokens,
|
||||
* while the second one corresponds to the names of keyword tokens.
|
||||
* Both parameters are optional and can be imported from `./generated/ast.js`.
|
||||
*
|
||||
* Inspired by https://github.com/chevrotain/chevrotain/blob/master/examples/lexer/python_indentation/python_indentation.js
|
||||
*/
|
||||
export class IndentationAwareTokenBuilder<Terminals extends string = string, KeywordName extends string = string> extends DefaultTokenBuilder {
|
||||
/**
|
||||
* The stack stores all the previously matched indentation levels to understand how deeply the next tokens are nested.
|
||||
* The stack is valid for lexing
|
||||
*/
|
||||
protected indentationStack: number[] = [0];
|
||||
|
||||
readonly options: IndentationTokenBuilderOptions<Terminals, KeywordName>;
|
||||
|
||||
/**
|
||||
* The token type to be used for indentation tokens
|
||||
*/
|
||||
readonly indentTokenType: TokenType;
|
||||
|
||||
/**
|
||||
* The token type to be used for dedentation tokens
|
||||
*/
|
||||
readonly dedentTokenType: TokenType;
|
||||
|
||||
/**
|
||||
* A regular expression to match a series of tabs and/or spaces.
|
||||
* Override this to customize what the indentation is allowed to consist of.
|
||||
*/
|
||||
protected whitespaceRegExp = /[ \t]+/y;
|
||||
|
||||
constructor(options: Partial<IndentationTokenBuilderOptions<NoInfer<Terminals>, NoInfer<KeywordName>>> = indentationBuilderDefaultOptions as IndentationTokenBuilderOptions<Terminals, KeywordName>) {
|
||||
super();
|
||||
this.options = {
|
||||
...indentationBuilderDefaultOptions as IndentationTokenBuilderOptions<Terminals, KeywordName>,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.indentTokenType = createToken({
|
||||
name: this.options.indentTokenName,
|
||||
pattern: this.indentMatcher.bind(this),
|
||||
line_breaks: false,
|
||||
});
|
||||
|
||||
this.dedentTokenType = createToken({
|
||||
name: this.options.dedentTokenName,
|
||||
pattern: this.dedentMatcher.bind(this),
|
||||
line_breaks: false,
|
||||
});
|
||||
}
|
||||
|
||||
override buildTokens(grammar: Grammar, options?: TokenBuilderOptions | undefined): TokenVocabulary {
|
||||
const tokenTypes = super.buildTokens(grammar, options);
|
||||
if (!isTokenTypeArray(tokenTypes)) {
|
||||
throw new Error('Invalid tokens built by default builder');
|
||||
}
|
||||
|
||||
const { indentTokenName, dedentTokenName, whitespaceTokenName, ignoreIndentationDelimiters } = this.options;
|
||||
|
||||
// Rearrange tokens because whitespace (which is ignored) goes to the beginning by default, consuming indentation as well
|
||||
// Order should be: dedent, indent, spaces
|
||||
let dedent: TokenType | undefined;
|
||||
let indent: TokenType | undefined;
|
||||
let ws: TokenType | undefined;
|
||||
const otherTokens: TokenType[] = [];
|
||||
for (const tokenType of tokenTypes) {
|
||||
for (const [begin, end] of ignoreIndentationDelimiters) {
|
||||
if (tokenType.name === begin) {
|
||||
tokenType.PUSH_MODE = LexingMode.IGNORE_INDENTATION;
|
||||
} else if (tokenType.name === end) {
|
||||
tokenType.POP_MODE = true;
|
||||
}
|
||||
}
|
||||
if (tokenType.name === dedentTokenName) {
|
||||
dedent = tokenType;
|
||||
} else if (tokenType.name === indentTokenName) {
|
||||
indent = tokenType;
|
||||
} else if (tokenType.name === whitespaceTokenName) {
|
||||
ws = tokenType;
|
||||
} else {
|
||||
otherTokens.push(tokenType);
|
||||
}
|
||||
}
|
||||
if (!dedent || !indent || !ws) {
|
||||
throw new Error('Some indentation/whitespace tokens not found!');
|
||||
}
|
||||
|
||||
if (ignoreIndentationDelimiters.length > 0) {
|
||||
const multiModeLexerDef: IMultiModeLexerDefinition = {
|
||||
modes: {
|
||||
[LexingMode.REGULAR]: [dedent, indent, ...otherTokens, ws],
|
||||
[LexingMode.IGNORE_INDENTATION]: [...otherTokens, ws],
|
||||
},
|
||||
defaultMode: LexingMode.REGULAR,
|
||||
};
|
||||
return multiModeLexerDef;
|
||||
} else {
|
||||
return [dedent, indent, ws, ...otherTokens];
|
||||
}
|
||||
}
|
||||
|
||||
override flushLexingReport(text: string): IndentationLexingReport {
|
||||
const result = super.flushLexingReport(text);
|
||||
return {
|
||||
...result,
|
||||
remainingDedents: this.flushRemainingDedents(text),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if the current position is the start of a new line.
|
||||
*
|
||||
* @param text The full input string.
|
||||
* @param offset The current position at which to check
|
||||
* @returns Whether the current position is the start of a new line
|
||||
*/
|
||||
protected isStartOfLine(text: string, offset: number): boolean {
|
||||
return offset === 0 || '\r\n'.includes(text[offset - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function used in matching both indents and dedents.
|
||||
*
|
||||
* @param text The full input string.
|
||||
* @param offset The current position at which to attempt a match
|
||||
* @param tokens Previously scanned tokens
|
||||
* @param groups Token Groups
|
||||
* @returns The current and previous indentation levels and the matched whitespace
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected matchWhitespace(text: string, offset: number, tokens: IToken[], groups: Record<string, IToken[]>): { currIndentLevel: number, prevIndentLevel: number, match: RegExpExecArray | null } {
|
||||
this.whitespaceRegExp.lastIndex = offset;
|
||||
const match = this.whitespaceRegExp.exec(text);
|
||||
return {
|
||||
currIndentLevel: match?.[0].length ?? 0,
|
||||
prevIndentLevel: this.indentationStack.at(-1)!,
|
||||
match,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create an instance of an indentation token.
|
||||
*
|
||||
* @param tokenType Indent or dedent token type
|
||||
* @param text Full input string, used to calculate the line number
|
||||
* @param image The original image of the token (tabs or spaces)
|
||||
* @param offset Current position in the input string
|
||||
* @returns The indentation token instance
|
||||
*/
|
||||
protected createIndentationTokenInstance(tokenType: TokenType, text: string, image: string, offset: number): IToken {
|
||||
const lineNumber = this.getLineNumber(text, offset);
|
||||
return createTokenInstance(
|
||||
tokenType,
|
||||
image,
|
||||
offset, offset + image.length,
|
||||
lineNumber, lineNumber,
|
||||
1, image.length,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the line number at a given offset.
|
||||
*
|
||||
* @param text Full input string, used to calculate the line number
|
||||
* @param offset Current position in the input string
|
||||
* @returns The line number at the given offset
|
||||
*/
|
||||
protected getLineNumber(text: string, offset: number): number {
|
||||
return text.substring(0, offset).split(/\r\n|\r|\n/).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom pattern for matching indents
|
||||
*
|
||||
* @param text The full input string.
|
||||
* @param offset The offset at which to attempt a match
|
||||
* @param tokens Previously scanned tokens
|
||||
* @param groups Token Groups
|
||||
*/
|
||||
protected indentMatcher(text: string, offset: number, tokens: IToken[], groups: Record<string, IToken[]>): ReturnType<CustomPatternMatcherFunc> {
|
||||
if (!this.isStartOfLine(text, offset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { currIndentLevel, prevIndentLevel, match } = this.matchWhitespace(text, offset, tokens, groups);
|
||||
|
||||
if (currIndentLevel <= prevIndentLevel) {
|
||||
// shallower indentation (should be matched by dedent)
|
||||
// or same indentation level (should be matched by whitespace and ignored)
|
||||
return null;
|
||||
}
|
||||
|
||||
this.indentationStack.push(currIndentLevel);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom pattern for matching dedents
|
||||
*
|
||||
* @param text The full input string.
|
||||
* @param offset The offset at which to attempt a match
|
||||
* @param tokens Previously scanned tokens
|
||||
* @param groups Token Groups
|
||||
*/
|
||||
protected dedentMatcher(text: string, offset: number, tokens: IToken[], groups: Record<string, IToken[]>): ReturnType<CustomPatternMatcherFunc> {
|
||||
if (!this.isStartOfLine(text, offset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { currIndentLevel, prevIndentLevel, match } = this.matchWhitespace(text, offset, tokens, groups);
|
||||
|
||||
if (currIndentLevel >= prevIndentLevel) {
|
||||
// bigger indentation (should be matched by indent)
|
||||
// or same indentation level (should be matched by whitespace and ignored)
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchIndentIndex = this.indentationStack.lastIndexOf(currIndentLevel);
|
||||
|
||||
// Any dedent must match some previous indentation level.
|
||||
if (matchIndentIndex === -1) {
|
||||
this.diagnostics.push({
|
||||
severity: 'error',
|
||||
message: `Invalid dedent level ${currIndentLevel} at offset: ${offset}. Current indentation stack: ${this.indentationStack}`,
|
||||
offset,
|
||||
length: match?.[0]?.length ?? 0,
|
||||
line: this.getLineNumber(text, offset),
|
||||
column: 1
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const numberOfDedents = this.indentationStack.length - matchIndentIndex - 1;
|
||||
const newlinesBeforeDedent = text.substring(0, offset).match(/[\r\n]+$/)?.[0].length ?? 1;
|
||||
|
||||
for (let i = 0; i < numberOfDedents; i++) {
|
||||
const token = this.createIndentationTokenInstance(
|
||||
this.dedentTokenType,
|
||||
text,
|
||||
'', // Dedents are 0-width tokens
|
||||
offset - (newlinesBeforeDedent - 1), // Place the dedent after the first new line character
|
||||
);
|
||||
tokens.push(token);
|
||||
this.indentationStack.pop();
|
||||
}
|
||||
|
||||
// Token already added, let the dedentation now be consumed as whitespace (if any) and ignored
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override buildTerminalToken(terminal: TerminalRule): TokenType {
|
||||
const tokenType = super.buildTerminalToken(terminal);
|
||||
const { indentTokenName, dedentTokenName, whitespaceTokenName } = this.options;
|
||||
|
||||
if (tokenType.name === indentTokenName) {
|
||||
return this.indentTokenType;
|
||||
} else if (tokenType.name === dedentTokenName) {
|
||||
return this.dedentTokenType;
|
||||
} else if (tokenType.name === whitespaceTokenName) {
|
||||
return createToken({
|
||||
name: whitespaceTokenName,
|
||||
pattern: this.whitespaceRegExp,
|
||||
group: Lexer.SKIPPED,
|
||||
});
|
||||
}
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the indentation stack between different runs of the lexer
|
||||
*
|
||||
* @param text Full text that was tokenized
|
||||
* @returns Remaining dedent tokens to match all previous indents at the end of the file
|
||||
*/
|
||||
flushRemainingDedents(text: string): IToken[] {
|
||||
const remainingDedents: IToken[] = [];
|
||||
while (this.indentationStack.length > 1) {
|
||||
remainingDedents.push(
|
||||
this.createIndentationTokenInstance(this.dedentTokenType, text, '', text.length)
|
||||
);
|
||||
this.indentationStack.pop();
|
||||
}
|
||||
|
||||
this.indentationStack = [0];
|
||||
return remainingDedents;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lexer that is aware of indentation in the input text.
|
||||
* The only purpose of this lexer is to reset the internal state of the {@link IndentationAwareTokenBuilder}
|
||||
* between the tokenization of different text inputs.
|
||||
*
|
||||
* In your module, you can override the default lexer with this one as such:
|
||||
* ```ts
|
||||
* parser: {
|
||||
* TokenBuilder: () => new IndentationAwareTokenBuilder(),
|
||||
* Lexer: (services) => new IndentationAwareLexer(services),
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class IndentationAwareLexer extends DefaultLexer {
|
||||
|
||||
protected readonly indentationTokenBuilder: IndentationAwareTokenBuilder;
|
||||
|
||||
constructor(services: LangiumCoreServices) {
|
||||
super(services);
|
||||
if (services.parser.TokenBuilder instanceof IndentationAwareTokenBuilder) {
|
||||
this.indentationTokenBuilder = services.parser.TokenBuilder;
|
||||
} else {
|
||||
throw new Error('IndentationAwareLexer requires an accompanying IndentationAwareTokenBuilder');
|
||||
}
|
||||
}
|
||||
|
||||
override tokenize(text: string, options: TokenizeOptions = DEFAULT_TOKENIZE_OPTIONS): LexerResult {
|
||||
const result = super.tokenize(text);
|
||||
|
||||
// consuming all remaining dedents and remove them as they might not be serializable
|
||||
const report = result.report as IndentationLexingReport;
|
||||
if (options?.mode === 'full') {
|
||||
// auto-complete document with remaining dedents
|
||||
result.tokens.push(...report.remainingDedents);
|
||||
}
|
||||
report.remainingDedents = [];
|
||||
|
||||
// remove any "indent-dedent" pair with an empty body as these are typically
|
||||
// added by comments or lines with just whitespace but have no real value
|
||||
const { indentTokenType, dedentTokenType } = this.indentationTokenBuilder;
|
||||
// Use tokenTypeIdx for fast comparison
|
||||
const indentTokenIdx = indentTokenType.tokenTypeIdx;
|
||||
const dedentTokenIdx = dedentTokenType.tokenTypeIdx;
|
||||
const cleanTokens: IToken[] = [];
|
||||
const length = result.tokens.length - 1;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const token = result.tokens[i];
|
||||
const nextToken = result.tokens[i + 1];
|
||||
if (token.tokenTypeIdx === indentTokenIdx && nextToken.tokenTypeIdx === dedentTokenIdx) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
cleanTokens.push(token);
|
||||
}
|
||||
// Push last token separately
|
||||
if (length >= 0) {
|
||||
cleanTokens.push(result.tokens[length]);
|
||||
}
|
||||
result.tokens = cleanTokens;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
7
frontend/node_modules/langium/src/parser/parser-config.ts
generated
vendored
Normal file
7
frontend/node_modules/langium/src/parser/parser-config.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
export type { IParserConfig } from 'chevrotain';
|
||||
193
frontend/node_modules/langium/src/references/references.ts
generated
vendored
Normal file
193
frontend/node_modules/langium/src/references/references.ts
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
/******************************************************************************
|
||||
* 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 { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode, CstNode, GenericAstNode } from '../syntax-tree.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import type { ReferenceDescription } from '../workspace/ast-descriptions.js';
|
||||
import type { AstNodeLocator } from '../workspace/ast-node-locator.js';
|
||||
import type { IndexManager } from '../workspace/index-manager.js';
|
||||
import type { NameProvider } from './name-provider.js';
|
||||
import type { URI } from '../utils/uri-utils.js';
|
||||
import { findAssignment } from '../utils/grammar-utils.js';
|
||||
import { isMultiReference, isReference } from '../syntax-tree.js';
|
||||
import { getDocument, getReferenceNodes, streamAst, streamReferences } from '../utils/ast-utils.js';
|
||||
import { isChildNode, toDocumentSegment } from '../utils/cst-utils.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
import { UriUtils } from '../utils/uri-utils.js';
|
||||
import { isCrossReference } from '../languages/generated/ast.js';
|
||||
import type { LangiumDocuments } from '../workspace/documents.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for finding references and declaration of a given `CstNode`.
|
||||
*/
|
||||
export interface References {
|
||||
|
||||
/**
|
||||
* If the CstNode is a reference node the target AstNodes will be returned.
|
||||
* If the CstNode is a significant node of the CstNode this AstNode will be returned.
|
||||
*
|
||||
* @param sourceCstNode CstNode that points to a AstNode
|
||||
*/
|
||||
findDeclarations(sourceCstNode: CstNode): AstNode[];
|
||||
|
||||
/**
|
||||
* If the CstNode is a reference node the target CstNodes will be returned.
|
||||
* If the CstNode is a significant node of the CstNode this CstNode will be returned.
|
||||
*
|
||||
* @param sourceCstNode CstNode that points to a AstNode
|
||||
*/
|
||||
findDeclarationNodes(sourceCstNode: CstNode): CstNode[];
|
||||
|
||||
/**
|
||||
* Finds all references to the target node as references (local references) or reference descriptions.
|
||||
*
|
||||
* @param targetNode Specified target node whose references should be returned
|
||||
*/
|
||||
findReferences(targetNode: AstNode, options: FindReferencesOptions): Stream<ReferenceDescription>;
|
||||
}
|
||||
|
||||
export interface FindReferencesOptions {
|
||||
/**
|
||||
* When set, the `findReferences` method will only return references/declarations from the specified document.
|
||||
*/
|
||||
documentUri?: URI;
|
||||
/**
|
||||
* Whether the returned list of references should include the declaration.
|
||||
*/
|
||||
includeDeclaration?: boolean;
|
||||
}
|
||||
|
||||
export class DefaultReferences implements References {
|
||||
protected readonly nameProvider: NameProvider;
|
||||
protected readonly index: IndexManager;
|
||||
protected readonly nodeLocator: AstNodeLocator;
|
||||
protected readonly documents: LangiumDocuments;
|
||||
protected hasMultiReference: boolean;
|
||||
|
||||
constructor(services: LangiumCoreServices) {
|
||||
this.nameProvider = services.references.NameProvider;
|
||||
this.index = services.shared.workspace.IndexManager;
|
||||
this.nodeLocator = services.workspace.AstNodeLocator;
|
||||
this.documents = services.shared.workspace.LangiumDocuments;
|
||||
this.hasMultiReference = streamAst(services.Grammar).some(node => isCrossReference(node) && node.isMulti);
|
||||
}
|
||||
|
||||
findDeclarations(sourceCstNode: CstNode): AstNode[] {
|
||||
if (sourceCstNode) {
|
||||
const assignment = findAssignment(sourceCstNode);
|
||||
const nodeElem = sourceCstNode.astNode;
|
||||
if (assignment && nodeElem) {
|
||||
const reference = (nodeElem as GenericAstNode)[assignment.feature];
|
||||
|
||||
if (isReference(reference) || isMultiReference(reference)) {
|
||||
return getReferenceNodes(reference);
|
||||
} else if (Array.isArray(reference)) {
|
||||
for (const ref of reference) {
|
||||
if ((isReference(ref) || isMultiReference(ref)) && ref.$refNode
|
||||
&& ref.$refNode.offset <= sourceCstNode.offset
|
||||
&& ref.$refNode.end >= sourceCstNode.end) {
|
||||
return getReferenceNodes(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeElem) {
|
||||
const nameNode = this.nameProvider.getNameNode(nodeElem);
|
||||
// Only return the targeted node in case the targeted cst node is the name node or part of it
|
||||
if (nameNode && (nameNode === sourceCstNode || isChildNode(sourceCstNode, nameNode))) {
|
||||
return this.getSelfNodes(nodeElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all self-references for the specified node.
|
||||
* Since the node can be part of a multi-reference, this method returns all nodes that are part of the same multi-reference.
|
||||
*/
|
||||
protected getSelfNodes(node: AstNode): AstNode[] {
|
||||
if (!this.hasMultiReference) {
|
||||
return [node];
|
||||
} else {
|
||||
// In order to find all nodes that are part of the same multi-reference,
|
||||
// we need to find a reference that points to the node.
|
||||
// It will also point to the logical siblings of the node.
|
||||
const references = this.index.findAllReferences(node, this.nodeLocator.getAstNodePath(node));
|
||||
// We can simply use the first reference to find all logical siblings.
|
||||
// Looking through all references is not necessary and very inefficient.
|
||||
const headNode = this.getNodeFromReferenceDescription(references.head());
|
||||
if (headNode) {
|
||||
// We need to iterate over all references to find the one that points to the node.
|
||||
for (const ref of streamReferences(headNode)) {
|
||||
if (isMultiReference(ref.reference) && ref.reference.items.some(item => item.ref === node)) {
|
||||
// Once we found the reference, simply return all items of the multi-reference.
|
||||
return ref.reference.items.map(item => item.ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [node];
|
||||
}
|
||||
}
|
||||
|
||||
protected getNodeFromReferenceDescription(ref?: ReferenceDescription): AstNode | undefined {
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
const doc = this.documents.getDocument(ref.sourceUri);
|
||||
if (doc) {
|
||||
return this.nodeLocator.getAstNode(doc.parseResult.value, ref.sourcePath);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findDeclarationNodes(sourceCstNode: CstNode): CstNode[] {
|
||||
const astNodes = this.findDeclarations(sourceCstNode);
|
||||
const cstNodes: CstNode[] = [];
|
||||
for (const astNode of astNodes) {
|
||||
const cstNode = this.nameProvider.getNameNode(astNode) ?? astNode.$cstNode;
|
||||
if (cstNode) {
|
||||
cstNodes.push(cstNode);
|
||||
}
|
||||
}
|
||||
return cstNodes;
|
||||
}
|
||||
|
||||
findReferences(targetNode: AstNode, options: FindReferencesOptions): Stream<ReferenceDescription> {
|
||||
const refs: ReferenceDescription[] = [];
|
||||
if (options.includeDeclaration) {
|
||||
refs.push(...this.getSelfReferences(targetNode));
|
||||
}
|
||||
let indexReferences = this.index.findAllReferences(targetNode, this.nodeLocator.getAstNodePath(targetNode));
|
||||
if (options.documentUri) {
|
||||
indexReferences = indexReferences.filter(ref => UriUtils.equals(ref.sourceUri, options.documentUri));
|
||||
}
|
||||
refs.push(...indexReferences);
|
||||
return stream(refs);
|
||||
}
|
||||
|
||||
protected getSelfReferences(targetNode: AstNode): ReferenceDescription[] {
|
||||
const selfNodes = this.getSelfNodes(targetNode);
|
||||
const references: ReferenceDescription[] = [];
|
||||
for (const selfNode of selfNodes) {
|
||||
const nameNode = this.nameProvider.getNameNode(selfNode);
|
||||
if (nameNode) {
|
||||
const doc = getDocument(selfNode);
|
||||
const path = this.nodeLocator.getAstNodePath(selfNode);
|
||||
references.push({
|
||||
sourceUri: doc.uri,
|
||||
sourcePath: path,
|
||||
targetUri: doc.uri,
|
||||
targetPath: path,
|
||||
segment: toDocumentSegment(nameNode),
|
||||
local: true
|
||||
});
|
||||
}
|
||||
}
|
||||
return references;
|
||||
}
|
||||
}
|
||||
143
frontend/node_modules/langium/src/references/scope-computation.ts
generated
vendored
Normal file
143
frontend/node_modules/langium/src/references/scope-computation.ts
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021-2022 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 { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode, AstNodeDescription } from '../syntax-tree.js';
|
||||
import { streamAllContents, streamContents } from '../utils/ast-utils.js';
|
||||
import { CancellationToken } from '../utils/cancellation.js';
|
||||
import { MultiMap } from '../utils/collections.js';
|
||||
import { interruptAndCheck } from '../utils/promise-utils.js';
|
||||
import type { AstNodeDescriptionProvider } from '../workspace/ast-descriptions.js';
|
||||
import type { LangiumDocument, LocalSymbols } from '../workspace/documents.js';
|
||||
import type { NameProvider } from './name-provider.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for precomputing global and local scopes. The service methods are executed
|
||||
* as the first and second phase in the `DocumentBuilder`.
|
||||
*/
|
||||
export interface ScopeComputation {
|
||||
|
||||
/**
|
||||
* Creates descriptions of all AST nodes that shall be exported into the _global_ scope from the given
|
||||
* document. These descriptions are gathered by the `IndexManager` and stored in the global index so
|
||||
* they can be referenced from other documents.
|
||||
*
|
||||
* _Note:_ You should not resolve any cross-references in this service method. Cross-reference resolution
|
||||
* depends on the scope computation phase to be completed (`computeScope` method), which runs after the
|
||||
* initial indexing where this method is used.
|
||||
*
|
||||
* @param document The document from which to gather exported AST nodes.
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
collectExportedSymbols(document: LangiumDocument, cancelToken?: CancellationToken): Promise<AstNodeDescription[]>;
|
||||
|
||||
/**
|
||||
* Creates descriptions of the _local_ symbols being accessible within a document.
|
||||
* The result is a `LocalSymbols` table assigning sets of AST node descriptions to the corresponding
|
||||
* nodes/subtrees within the AST. The descriptions are considered in the default reference resolution
|
||||
* implementation, i.e. they are used by the `ScopeProvider` service to determine which symbols
|
||||
* are visible in the context of a specific cross-reference.
|
||||
*
|
||||
* _Note:_ You should not resolve any cross-references in this service method. Cross-reference
|
||||
* resolution depends on the scope computation phase to be completed.
|
||||
*
|
||||
* @param document The document for which to compute its local symbols.
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
collectLocalSymbols(document: LangiumDocument, cancelToken?: CancellationToken): Promise<LocalSymbols>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default scope computation creates and collects descriptions of the AST nodes to be exported into the
|
||||
* _global_ scope from the given document. By default those are the document's root AST node and its directly
|
||||
* contained child nodes.
|
||||
*
|
||||
* Besides, it gathers all AST nodes that have a name (according to the `NameProvider` service) and that are to be
|
||||
* included in the local scope of their particular container nodes. They are collected in a `DocumentSymbols` table.
|
||||
* As a result, for every cross-reference in the AST, target elements from the same level (siblings) and further up
|
||||
* towards the root (parents and siblings of parents) are visible.
|
||||
* Elements being nested inside lower levels (children, children of siblings and parents' siblings)
|
||||
* are _invisible_ by default, but that can be changed by customizing this service.
|
||||
*/
|
||||
export class DefaultScopeComputation implements ScopeComputation {
|
||||
|
||||
protected readonly nameProvider: NameProvider;
|
||||
protected readonly descriptions: AstNodeDescriptionProvider;
|
||||
|
||||
constructor(services: LangiumCoreServices) {
|
||||
this.nameProvider = services.references.NameProvider;
|
||||
this.descriptions = services.workspace.AstNodeDescriptionProvider;
|
||||
}
|
||||
|
||||
async collectExportedSymbols(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<AstNodeDescription[]> {
|
||||
return this.collectExportedSymbolsForNode(document.parseResult.value, document, undefined, cancelToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link AstNodeDescription AstNodeDescriptions} for the given {@link AstNode parentNode} and its children.
|
||||
* The list of children to be considered is determined by the function parameter {@link children}.
|
||||
* By default only the direct children of {@link parentNode} are visited, nested nodes are not exported.
|
||||
*
|
||||
* @param parentNode AST node to be exported, i.e., of which an {@link AstNodeDescription} shall be added to the returned list.
|
||||
* @param document The document containing the AST node to be exported.
|
||||
* @param children A function called with {@link parentNode} as single argument and returning an {@link Iterable} supplying the children to be visited, which must be directly or transitively contained in {@link parentNode}.
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCancelled` if a user action occurs during execution.
|
||||
* @returns A list of {@link AstNodeDescription AstNodeDescriptions} to be published to index.
|
||||
*/
|
||||
async collectExportedSymbolsForNode(parentNode: AstNode, document: LangiumDocument<AstNode>, children: (root: AstNode) => Iterable<AstNode> = streamContents, cancelToken: CancellationToken = CancellationToken.None): Promise<AstNodeDescription[]> {
|
||||
const exports: AstNodeDescription[] = [];
|
||||
|
||||
this.addExportedSymbol(parentNode, exports, document);
|
||||
for (const node of children(parentNode)) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
this.addExportedSymbol(node, exports, document);
|
||||
}
|
||||
return exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single node to the list of exports if it has a name. Override this method to change how
|
||||
* symbols are exported, e.g. by modifying their exported name.
|
||||
*/
|
||||
protected addExportedSymbol(node: AstNode, exports: AstNodeDescription[], document: LangiumDocument): void {
|
||||
const name = this.nameProvider.getName(node);
|
||||
if (name) {
|
||||
exports.push(this.descriptions.createDescription(node, name, document));
|
||||
}
|
||||
}
|
||||
|
||||
// --- local symbols gathering ---
|
||||
|
||||
async collectLocalSymbols(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<LocalSymbols> {
|
||||
const rootNode = document.parseResult.value;
|
||||
const symbols = new MultiMap<AstNode, AstNodeDescription>();
|
||||
// Here we navigate the full AST - local scopes shall be available in the whole document
|
||||
for (const node of streamAllContents(rootNode)) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
this.addLocalSymbol(node, document, symbols);
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single node to the local symbols of its containing document if it has a name.
|
||||
* The default implementation makes the node visible in the subtree of its container if it does have a container.
|
||||
* Override this method to change this, e.g. by increasing the visibility to a higher level in the AST.
|
||||
*/
|
||||
protected addLocalSymbol(node: AstNode, document: LangiumDocument, symbols: MultiMap<AstNode, AstNodeDescription>): void {
|
||||
const container = node.$container;
|
||||
if (container) {
|
||||
const name = this.nameProvider.getName(node);
|
||||
if (name) {
|
||||
symbols.add(container, this.descriptions.createDescription(node, name, document));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
103
frontend/node_modules/langium/src/references/scope-provider.ts
generated
vendored
Normal file
103
frontend/node_modules/langium/src/references/scope-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021-2022 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 { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode, AstNodeDescription, AstReflection, ReferenceInfo } from '../syntax-tree.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import type { AstNodeDescriptionProvider } from '../workspace/ast-descriptions.js';
|
||||
import type { IndexManager } from '../workspace/index-manager.js';
|
||||
import type { NameProvider } from './name-provider.js';
|
||||
import type { Scope, ScopeOptions} from './scope.js';
|
||||
import { MultiMapScope, StreamScope } from './scope.js';
|
||||
import { getDocument } from '../utils/ast-utils.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
import { WorkspaceCache } from '../utils/caching.js';
|
||||
|
||||
/**
|
||||
* Language-specific service for determining the scope of target elements visible in a specific cross-reference context.
|
||||
*/
|
||||
export interface ScopeProvider {
|
||||
|
||||
/**
|
||||
* Return a scope describing what elements are visible for the given AST node and cross-reference
|
||||
* identifier.
|
||||
*
|
||||
* @param context Information about the reference for which a scope is requested.
|
||||
*/
|
||||
getScope(context: ReferenceInfo): Scope;
|
||||
|
||||
}
|
||||
|
||||
export class DefaultScopeProvider implements ScopeProvider {
|
||||
|
||||
protected readonly reflection: AstReflection;
|
||||
protected readonly nameProvider: NameProvider;
|
||||
protected readonly descriptions: AstNodeDescriptionProvider;
|
||||
protected readonly indexManager: IndexManager;
|
||||
|
||||
protected readonly globalScopeCache: WorkspaceCache<string, Scope>;
|
||||
|
||||
constructor(services: LangiumCoreServices) {
|
||||
this.reflection = services.shared.AstReflection;
|
||||
this.nameProvider = services.references.NameProvider;
|
||||
this.descriptions = services.workspace.AstNodeDescriptionProvider;
|
||||
this.indexManager = services.shared.workspace.IndexManager;
|
||||
this.globalScopeCache = new WorkspaceCache<string, Scope>(services.shared);
|
||||
}
|
||||
|
||||
getScope(context: ReferenceInfo): Scope {
|
||||
const scopes: Array<Stream<AstNodeDescription>> = [];
|
||||
const referenceType = this.reflection.getReferenceType(context);
|
||||
|
||||
const localSymbols = getDocument(context.container).localSymbols;
|
||||
if (localSymbols) {
|
||||
let currentNode: AstNode | undefined = context.container;
|
||||
do {
|
||||
if (localSymbols.has(currentNode)) {
|
||||
scopes.push(localSymbols.getStream(currentNode).filter(
|
||||
desc => this.reflection.isSubtype(desc.type, referenceType)));
|
||||
}
|
||||
currentNode = currentNode.$container;
|
||||
} while (currentNode);
|
||||
}
|
||||
|
||||
let result: Scope = this.getGlobalScope(referenceType, context);
|
||||
for (let i = scopes.length - 1; i >= 0; i--) {
|
||||
result = this.createScope(scopes[i], result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scope for the given collection of AST node descriptions.
|
||||
*/
|
||||
protected createScope(elements: Iterable<AstNodeDescription>, outerScope?: Scope, options?: ScopeOptions): Scope {
|
||||
return new StreamScope(stream(elements), outerScope, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scope for the given collection of AST nodes, which need to be transformed into respective
|
||||
* descriptions first. This is done using the `NameProvider` and `AstNodeDescriptionProvider` services.
|
||||
*/
|
||||
protected createScopeForNodes(elements: Iterable<AstNode>, outerScope?: Scope, options?: ScopeOptions): Scope {
|
||||
const s = stream(elements).map(e => {
|
||||
const name = this.nameProvider.getName(e);
|
||||
if (name) {
|
||||
return this.descriptions.createDescription(e, name);
|
||||
}
|
||||
return undefined;
|
||||
}).nonNullable();
|
||||
return new StreamScope(s, outerScope, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a global scope filtered for the given reference type.
|
||||
*/
|
||||
protected getGlobalScope(referenceType: string, _context: ReferenceInfo): Scope {
|
||||
return this.globalScopeCache.get(referenceType, () => new MultiMapScope(this.indexManager.allElements(referenceType)));
|
||||
}
|
||||
|
||||
}
|
||||
220
frontend/node_modules/langium/src/references/scope.ts
generated
vendored
Normal file
220
frontend/node_modules/langium/src/references/scope.ts
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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 { AstNodeDescription } from '../syntax-tree.js';
|
||||
import { MultiMap } from '../utils/collections.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import { EMPTY_STREAM, stream } from '../utils/stream.js';
|
||||
|
||||
/**
|
||||
* A scope describes what target elements are visible from a specific cross-reference context.
|
||||
*/
|
||||
export interface Scope {
|
||||
|
||||
/**
|
||||
* Find a target element matching the given name. If no element is found, `undefined` is returned.
|
||||
* If multiple matching elements are present, the selection of the returned element should be done
|
||||
* according to the semantics of your language. Usually it is the element that is most closely defined.
|
||||
*
|
||||
* @param name Name of the cross-reference target as it appears in the source text.
|
||||
*/
|
||||
getElement(name: string): AstNodeDescription | undefined;
|
||||
|
||||
/**
|
||||
* Finds all target elements matching the given name. If no element is found, an empty stream is returned.
|
||||
*
|
||||
* @param name Name of the cross-reference target as it appears in the source text.
|
||||
*/
|
||||
getElements(name: string): Stream<AstNodeDescription>;
|
||||
|
||||
/**
|
||||
* Create a stream of all elements in the scope. This is used to compute completion proposals to be
|
||||
* shown in the editor.
|
||||
*/
|
||||
getAllElements(): Stream<AstNodeDescription>;
|
||||
|
||||
}
|
||||
|
||||
export interface ScopeOptions {
|
||||
/**
|
||||
* Whether the scope should be case insensitive.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
caseInsensitive?: boolean;
|
||||
/**
|
||||
* Whether the outer scope should be concatenated with the local scope when calling `getElements`.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
concatOuterScope?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default scope implementation is based on a `Stream`. It has an optional _outer scope_ describing
|
||||
* the next level of elements, which are queried when a target element is not found in the stream provided
|
||||
* to this scope.
|
||||
*/
|
||||
export class StreamScope implements Scope {
|
||||
readonly elements: Stream<AstNodeDescription>;
|
||||
readonly outerScope?: Scope;
|
||||
readonly caseInsensitive: boolean;
|
||||
readonly concatOuterScope: boolean;
|
||||
|
||||
constructor(elements: Stream<AstNodeDescription>, outerScope?: Scope, options?: ScopeOptions) {
|
||||
this.elements = elements;
|
||||
this.outerScope = outerScope;
|
||||
this.caseInsensitive = options?.caseInsensitive ?? false;
|
||||
this.concatOuterScope = options?.concatOuterScope ?? true;
|
||||
}
|
||||
|
||||
getAllElements(): Stream<AstNodeDescription> {
|
||||
if (this.outerScope) {
|
||||
return this.elements.concat(this.outerScope.getAllElements());
|
||||
} else {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
|
||||
getElement(name: string): AstNodeDescription | undefined {
|
||||
const lowerCaseName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.caseInsensitive
|
||||
? this.elements.find(e => e.name.toLowerCase() === lowerCaseName)
|
||||
: this.elements.find(e => e.name === name);
|
||||
if (local) {
|
||||
return local;
|
||||
}
|
||||
if (this.outerScope) {
|
||||
return this.outerScope.getElement(name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getElements(name: string): Stream<AstNodeDescription> {
|
||||
const lowerCaseName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.caseInsensitive
|
||||
? this.elements.filter(e => e.name.toLowerCase() === lowerCaseName)
|
||||
: this.elements.filter(e => e.name === name);
|
||||
if ((this.concatOuterScope || local.isEmpty()) && this.outerScope) {
|
||||
return local.concat(this.outerScope.getElements(name));
|
||||
} else {
|
||||
return local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MapScope implements Scope {
|
||||
readonly elements: Map<string, AstNodeDescription>;
|
||||
readonly outerScope?: Scope;
|
||||
readonly caseInsensitive: boolean;
|
||||
readonly concatOuterScope: boolean;
|
||||
|
||||
constructor(elements: Iterable<AstNodeDescription>, outerScope?: Scope, options?: ScopeOptions) {
|
||||
this.elements = new Map();
|
||||
this.caseInsensitive = options?.caseInsensitive ?? false;
|
||||
this.concatOuterScope = options?.concatOuterScope ?? true;
|
||||
for (const element of elements) {
|
||||
const name = this.caseInsensitive
|
||||
? element.name.toLowerCase()
|
||||
: element.name;
|
||||
this.elements.set(name, element);
|
||||
}
|
||||
this.outerScope = outerScope;
|
||||
}
|
||||
|
||||
getElement(name: string): AstNodeDescription | undefined {
|
||||
const localName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.elements.get(localName);
|
||||
if (local) {
|
||||
return local;
|
||||
}
|
||||
if (this.outerScope) {
|
||||
return this.outerScope.getElement(name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getElements(name: string): Stream<AstNodeDescription> {
|
||||
const localName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.elements.get(localName);
|
||||
const arr = local ? [local] : [];
|
||||
if ((this.concatOuterScope || arr.length > 0) && this.outerScope) {
|
||||
return stream(arr).concat(this.outerScope.getElements(name));
|
||||
} else {
|
||||
return stream(arr);
|
||||
}
|
||||
}
|
||||
|
||||
getAllElements(): Stream<AstNodeDescription> {
|
||||
let elementStream = stream(this.elements.values());
|
||||
if (this.outerScope) {
|
||||
elementStream = elementStream.concat(this.outerScope.getAllElements());
|
||||
}
|
||||
return elementStream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class MultiMapScope implements Scope {
|
||||
readonly elements: MultiMap<string, AstNodeDescription>;
|
||||
readonly outerScope?: Scope;
|
||||
readonly caseInsensitive: boolean;
|
||||
readonly concatOuterScope: boolean;
|
||||
|
||||
constructor(elements: Iterable<AstNodeDescription>, outerScope?: Scope, options?: ScopeOptions) {
|
||||
this.elements = new MultiMap();
|
||||
this.caseInsensitive = options?.caseInsensitive ?? false;
|
||||
this.concatOuterScope = options?.concatOuterScope ?? true;
|
||||
for (const element of elements) {
|
||||
const name = this.caseInsensitive
|
||||
? element.name.toLowerCase()
|
||||
: element.name;
|
||||
this.elements.add(name, element);
|
||||
}
|
||||
this.outerScope = outerScope;
|
||||
}
|
||||
|
||||
getElement(name: string): AstNodeDescription | undefined {
|
||||
const localName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.elements.get(localName)[0];
|
||||
if (local) {
|
||||
return local;
|
||||
}
|
||||
if (this.outerScope) {
|
||||
return this.outerScope.getElement(name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getElements(name: string): Stream<AstNodeDescription> {
|
||||
const localName = this.caseInsensitive ? name.toLowerCase() : name;
|
||||
const local = this.elements.get(localName);
|
||||
if ((this.concatOuterScope || local.length === 0) && this.outerScope) {
|
||||
return stream(local).concat(this.outerScope.getElements(name));
|
||||
} else {
|
||||
return stream(local);
|
||||
}
|
||||
}
|
||||
|
||||
getAllElements(): Stream<AstNodeDescription> {
|
||||
let elementStream = stream(this.elements.values());
|
||||
if (this.outerScope) {
|
||||
elementStream = elementStream.concat(this.outerScope.getAllElements());
|
||||
}
|
||||
return elementStream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const EMPTY_SCOPE: Scope = {
|
||||
getElement(): undefined {
|
||||
return undefined;
|
||||
},
|
||||
getElements(): Stream<AstNodeDescription> {
|
||||
return EMPTY_STREAM;
|
||||
},
|
||||
getAllElements(): Stream<AstNodeDescription> {
|
||||
return EMPTY_STREAM;
|
||||
}
|
||||
};
|
||||
326
frontend/node_modules/langium/src/serializer/hydrator.ts
generated
vendored
Normal file
326
frontend/node_modules/langium/src/serializer/hydrator.ts
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 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.
|
||||
******************************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { TokenType } from 'chevrotain';
|
||||
import { CompositeCstNodeImpl, LeafCstNodeImpl, RootCstNodeImpl } from '../parser/cst-node-builder.js';
|
||||
import { isAbstractElement, type AbstractElement, type Grammar } from '../languages/generated/ast.js';
|
||||
import type { Linker } from '../references/linker.js';
|
||||
import type { Lexer } from '../parser/lexer.js';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import type { ParseResult } from '../parser/langium-parser.js';
|
||||
import type { Reference, AstNode, CstNode, LeafCstNode, GenericAstNode, Mutable, RootCstNode } from '../syntax-tree.js';
|
||||
import { isRootCstNode, isCompositeCstNode, isLeafCstNode, isAstNode, isReference } from '../syntax-tree.js';
|
||||
import { streamAst } from '../utils/ast-utils.js';
|
||||
import { BiMap } from '../utils/collections.js';
|
||||
import { streamCst } from '../utils/cst-utils.js';
|
||||
import type { LexingReport } from '../parser/token-builder.js';
|
||||
|
||||
/**
|
||||
* The hydrator service is responsible for allowing AST parse results to be sent across worker threads.
|
||||
*/
|
||||
export interface Hydrator {
|
||||
/**
|
||||
* Converts a parse result to a plain object. The resulting object can be sent across worker threads.
|
||||
*/
|
||||
dehydrate(result: ParseResult<AstNode>): ParseResult<object>;
|
||||
/**
|
||||
* Converts a plain object to a parse result. The included AST node can then be used in the main thread.
|
||||
* Calling this method on objects that have not been dehydrated first will result in undefined behavior.
|
||||
*/
|
||||
hydrate<T extends AstNode = AstNode>(result: ParseResult<object>): ParseResult<T>;
|
||||
}
|
||||
|
||||
export interface DehydrateContext {
|
||||
astNodes: Map<AstNode, any>;
|
||||
cstNodes: Map<CstNode, any>;
|
||||
}
|
||||
|
||||
export interface HydrateContext {
|
||||
astNodes: Map<any, AstNode>;
|
||||
cstNodes: Map<any, CstNode>;
|
||||
}
|
||||
|
||||
export class DefaultHydrator implements Hydrator {
|
||||
|
||||
protected readonly grammar: Grammar;
|
||||
protected readonly lexer: Lexer;
|
||||
protected readonly linker: Linker;
|
||||
|
||||
protected readonly grammarElementIdMap = new BiMap<AbstractElement, number>();
|
||||
protected readonly tokenTypeIdMap = new BiMap<number, TokenType>();
|
||||
|
||||
constructor(services: LangiumCoreServices) {
|
||||
this.grammar = services.Grammar;
|
||||
this.lexer = services.parser.Lexer;
|
||||
this.linker = services.references.Linker;
|
||||
}
|
||||
|
||||
dehydrate(result: ParseResult<AstNode>): ParseResult<object> {
|
||||
return {
|
||||
lexerErrors: result.lexerErrors,
|
||||
lexerReport: result.lexerReport ? this.dehydrateLexerReport(result.lexerReport) : undefined,
|
||||
// We need to create shallow copies of the errors
|
||||
// The original errors inherit from the `Error` class, which is not transferable across worker threads
|
||||
parserErrors: result.parserErrors.map(e => ({ ...e, message: e.message })),
|
||||
value: this.dehydrateAstNode(result.value, this.createDehyrationContext(result.value))
|
||||
};
|
||||
}
|
||||
|
||||
protected dehydrateLexerReport(lexerReport: LexingReport): LexingReport {
|
||||
// By default, lexer reports are serializable
|
||||
return lexerReport;
|
||||
}
|
||||
|
||||
protected createDehyrationContext(node: AstNode): DehydrateContext {
|
||||
const astNodes = new Map<AstNode, any>();
|
||||
const cstNodes = new Map<CstNode, any>();
|
||||
for (const astNode of streamAst(node)) {
|
||||
astNodes.set(astNode, {});
|
||||
}
|
||||
if (node.$cstNode) {
|
||||
for (const cstNode of streamCst(node.$cstNode)) {
|
||||
cstNodes.set(cstNode, {});
|
||||
}
|
||||
}
|
||||
return {
|
||||
astNodes,
|
||||
cstNodes
|
||||
};
|
||||
}
|
||||
|
||||
protected dehydrateAstNode(node: AstNode, context: DehydrateContext): object {
|
||||
const obj = context.astNodes.get(node) as Record<string, any>;
|
||||
obj.$type = node.$type;
|
||||
obj.$containerIndex = node.$containerIndex;
|
||||
obj.$containerProperty = node.$containerProperty;
|
||||
if (node.$cstNode !== undefined) {
|
||||
obj.$cstNode = this.dehydrateCstNode(node.$cstNode, context);
|
||||
}
|
||||
for (const [name, value] of Object.entries(node)) {
|
||||
if (name.startsWith('$')) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const arr: any[] = [];
|
||||
obj[name] = arr;
|
||||
for (const item of value) {
|
||||
if (isAstNode(item)) {
|
||||
arr.push(this.dehydrateAstNode(item, context));
|
||||
} else if (isReference(item)) {
|
||||
arr.push(this.dehydrateReference(item, context));
|
||||
} else {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
} else if (isAstNode(value)) {
|
||||
obj[name] = this.dehydrateAstNode(value, context);
|
||||
} else if (isReference(value)) {
|
||||
obj[name] = this.dehydrateReference(value, context);
|
||||
} else if (value !== undefined) {
|
||||
obj[name] = value;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
protected dehydrateReference(reference: Reference, context: DehydrateContext): any {
|
||||
const obj: Record<string, unknown> = {};
|
||||
obj.$refText = reference.$refText;
|
||||
if (reference.$refNode) {
|
||||
obj.$refNode = context.cstNodes.get(reference.$refNode);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
protected dehydrateCstNode(node: CstNode, context: DehydrateContext): any {
|
||||
const cstNode = context.cstNodes.get(node) as Record<string, any>;
|
||||
if (isRootCstNode(node)) {
|
||||
cstNode.fullText = node.fullText;
|
||||
} else {
|
||||
// Note: This returns undefined for hidden nodes (i.e. comments)
|
||||
cstNode.grammarSource = this.getGrammarElementId(node.grammarSource);
|
||||
}
|
||||
cstNode.hidden = node.hidden;
|
||||
cstNode.astNode = context.astNodes.get(node.astNode);
|
||||
if (isCompositeCstNode(node)) {
|
||||
cstNode.content = node.content.map(child => this.dehydrateCstNode(child, context));
|
||||
} else if (isLeafCstNode(node)) {
|
||||
cstNode.tokenType = node.tokenType.name;
|
||||
cstNode.offset = node.offset;
|
||||
cstNode.length = node.length;
|
||||
cstNode.startLine = node.range.start.line;
|
||||
cstNode.startColumn = node.range.start.character;
|
||||
cstNode.endLine = node.range.end.line;
|
||||
cstNode.endColumn = node.range.end.character;
|
||||
}
|
||||
return cstNode;
|
||||
}
|
||||
|
||||
hydrate<T extends AstNode = AstNode>(result: ParseResult<object>): ParseResult<T> {
|
||||
const node = result.value;
|
||||
const context = this.createHydrationContext(node);
|
||||
if ('$cstNode' in node) {
|
||||
this.hydrateCstNode(node.$cstNode, context);
|
||||
}
|
||||
return {
|
||||
lexerErrors: result.lexerErrors,
|
||||
lexerReport: result.lexerReport,
|
||||
parserErrors: result.parserErrors,
|
||||
value: this.hydrateAstNode(node, context) as T
|
||||
};
|
||||
}
|
||||
|
||||
protected createHydrationContext(node: any): HydrateContext {
|
||||
const astNodes = new Map<any, AstNode>();
|
||||
const cstNodes = new Map<any, CstNode>();
|
||||
for (const astNode of streamAst(node)) {
|
||||
astNodes.set(astNode, {} as AstNode);
|
||||
}
|
||||
let root: RootCstNode;
|
||||
if (node.$cstNode) {
|
||||
for (const cstNode of streamCst(node.$cstNode)) {
|
||||
let cst: Mutable<CstNode> | undefined;
|
||||
if ('fullText' in cstNode) {
|
||||
cst = new RootCstNodeImpl(cstNode.fullText as string);
|
||||
root = cst as RootCstNode;
|
||||
} else if ('content' in cstNode) {
|
||||
cst = new CompositeCstNodeImpl();
|
||||
} else if ('tokenType' in cstNode) {
|
||||
cst = this.hydrateCstLeafNode(cstNode);
|
||||
}
|
||||
if (cst) {
|
||||
cstNodes.set(cstNode, cst);
|
||||
cst.root = root!;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
astNodes,
|
||||
cstNodes
|
||||
};
|
||||
}
|
||||
|
||||
protected hydrateAstNode(node: any, context: HydrateContext): AstNode {
|
||||
const astNode = context.astNodes.get(node) as Mutable<GenericAstNode>;
|
||||
astNode.$type = node.$type;
|
||||
astNode.$containerIndex = node.$containerIndex;
|
||||
astNode.$containerProperty = node.$containerProperty;
|
||||
if (node.$cstNode) {
|
||||
astNode.$cstNode = context.cstNodes.get(node.$cstNode);
|
||||
}
|
||||
for (const [name, value] of Object.entries(node)) {
|
||||
if (name.startsWith('$')) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const arr: unknown[] = [];
|
||||
astNode[name] = arr;
|
||||
for (const item of value) {
|
||||
if (isAstNode(item)) {
|
||||
arr.push(this.setParent(this.hydrateAstNode(item, context), astNode));
|
||||
} else if (isReference(item)) {
|
||||
arr.push(this.hydrateReference(item, astNode, name, context));
|
||||
} else {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
} else if (isAstNode(value)) {
|
||||
astNode[name] = this.setParent(this.hydrateAstNode(value, context), astNode);
|
||||
} else if (isReference(value)) {
|
||||
astNode[name] = this.hydrateReference(value, astNode, name, context);
|
||||
} else if (value !== undefined) {
|
||||
astNode[name] = value;
|
||||
}
|
||||
}
|
||||
return astNode;
|
||||
}
|
||||
|
||||
protected setParent(node: any, parent: any): any {
|
||||
node.$container = parent as AstNode;
|
||||
return node;
|
||||
}
|
||||
|
||||
protected hydrateReference(reference: any, node: AstNode, name: string, context: HydrateContext): Reference {
|
||||
return this.linker.buildReference(node, name, context.cstNodes.get(reference.$refNode)!, reference.$refText);
|
||||
}
|
||||
|
||||
protected hydrateCstNode(cstNode: any, context: HydrateContext, num = 0): CstNode {
|
||||
const cstNodeObj = context.cstNodes.get(cstNode) as Mutable<CstNode>;
|
||||
if (typeof cstNode.grammarSource === 'number') {
|
||||
cstNodeObj.grammarSource = this.getGrammarElement(cstNode.grammarSource);
|
||||
}
|
||||
cstNodeObj.astNode = context.astNodes.get(cstNode.astNode)!;
|
||||
if (isCompositeCstNode(cstNodeObj)) {
|
||||
for (const child of cstNode.content) {
|
||||
const hydrated = this.hydrateCstNode(child, context, num++);
|
||||
cstNodeObj.content.push(hydrated);
|
||||
}
|
||||
}
|
||||
return cstNodeObj;
|
||||
}
|
||||
|
||||
protected hydrateCstLeafNode(cstNode: any): LeafCstNode {
|
||||
const tokenType = this.getTokenType(cstNode.tokenType);
|
||||
const offset = cstNode.offset;
|
||||
const length = cstNode.length;
|
||||
const startLine = cstNode.startLine;
|
||||
const startColumn = cstNode.startColumn;
|
||||
const endLine = cstNode.endLine;
|
||||
const endColumn = cstNode.endColumn;
|
||||
const hidden = cstNode.hidden;
|
||||
const node = new LeafCstNodeImpl(
|
||||
offset,
|
||||
length,
|
||||
{
|
||||
start: {
|
||||
line: startLine,
|
||||
character: startColumn
|
||||
},
|
||||
end: {
|
||||
line: endLine,
|
||||
character: endColumn
|
||||
}
|
||||
},
|
||||
tokenType,
|
||||
hidden
|
||||
);
|
||||
return node;
|
||||
}
|
||||
|
||||
protected getTokenType(name: string): TokenType {
|
||||
return this.lexer.definition[name];
|
||||
}
|
||||
|
||||
protected getGrammarElementId(node: AbstractElement | undefined): number | undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.grammarElementIdMap.size === 0) {
|
||||
this.createGrammarElementIdMap();
|
||||
}
|
||||
return this.grammarElementIdMap.get(node);
|
||||
}
|
||||
|
||||
protected getGrammarElement(id: number): AbstractElement | undefined {
|
||||
if (this.grammarElementIdMap.size === 0) {
|
||||
this.createGrammarElementIdMap();
|
||||
}
|
||||
const element = this.grammarElementIdMap.getKey(id);
|
||||
return element;
|
||||
}
|
||||
|
||||
protected createGrammarElementIdMap(): void {
|
||||
let id = 0;
|
||||
for (const element of streamAst(this.grammar)) {
|
||||
if (isAbstractElement(element)) {
|
||||
this.grammarElementIdMap.set(element, id++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
10
frontend/node_modules/langium/src/test/index.ts
generated
vendored
Normal file
10
frontend/node_modules/langium/src/test/index.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
*
|
||||
* @module langium/test
|
||||
*/
|
||||
|
||||
export * from './langium-test.js';
|
||||
export * from './virtual-file-system.js';
|
||||
224
frontend/node_modules/langium/src/utils/collections.ts
generated
vendored
Normal file
224
frontend/node_modules/langium/src/utils/collections.ts
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
/******************************************************************************
|
||||
* 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 { Stream } from './stream.js';
|
||||
import { EMPTY_STREAM, Reduction, stream } from './stream.js';
|
||||
|
||||
/**
|
||||
* A multimap is a variation of a Map that has potentially multiple values for every key.
|
||||
*/
|
||||
export class MultiMap<K, V> {
|
||||
|
||||
private map = new Map<K, V[]>();
|
||||
|
||||
constructor()
|
||||
constructor(elements: Iterable<[K, V]>)
|
||||
constructor(elements?: Iterable<[K, V]>) {
|
||||
if (elements) {
|
||||
for (const [key, value] of elements) {
|
||||
this.add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of values in the multimap.
|
||||
*/
|
||||
get size(): number {
|
||||
return Reduction.sum(stream(this.map.values()).map(a => a.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entries in the multimap.
|
||||
*/
|
||||
clear(): void {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method deletes the specific key / value pair from the multimap.
|
||||
* * Without a value, all values associated with the given key are deleted.
|
||||
*
|
||||
* @returns `true` if a value existed and has been removed, or `false` if the specified
|
||||
* key / value does not exist.
|
||||
*/
|
||||
delete(key: K, value?: V): boolean {
|
||||
if (value === undefined) {
|
||||
return this.map.delete(key);
|
||||
} else {
|
||||
const values = this.map.get(key);
|
||||
if (values) {
|
||||
const index = values.indexOf(value);
|
||||
if (index >= 0) {
|
||||
if (values.length === 1) {
|
||||
this.map.delete(key);
|
||||
} else {
|
||||
values.splice(index, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all values associated with the given key. If no value exists,
|
||||
* an empty array is returned.
|
||||
*
|
||||
* _Note:_ The returned array is assumed not to be modified. Use the `set` method to add a
|
||||
* value and `delete` to remove a value from the multimap.
|
||||
*/
|
||||
get(key: K): readonly V[] {
|
||||
return this.map.get(key) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of all values associated with the given key. If no value exists,
|
||||
* {@link EMPTY_STREAM} is returned.
|
||||
*/
|
||||
getStream(key: K): Stream<V> {
|
||||
const values = this.map.get(key);
|
||||
return values ? stream(values) : EMPTY_STREAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method returns `true` if the specific key / value pair is present in the multimap.
|
||||
* * Without a value, this method returns `true` if the given key is present in the multimap.
|
||||
*/
|
||||
has(key: K, value?: V): boolean {
|
||||
if (value === undefined) {
|
||||
return this.map.has(key);
|
||||
} else {
|
||||
const values = this.map.get(key);
|
||||
if (values) {
|
||||
return values.indexOf(value) >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given key / value pair to the multimap.
|
||||
*/
|
||||
add(key: K, value: V): this {
|
||||
if (this.map.has(key)) {
|
||||
this.map.get(key)!.push(value);
|
||||
} else {
|
||||
this.map.set(key, [value]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given set of key / value pairs to the multimap.
|
||||
*/
|
||||
addAll(key: K, values: Iterable<V>): this {
|
||||
if (this.map.has(key)) {
|
||||
this.map.get(key)!.push(...values);
|
||||
} else {
|
||||
this.map.set(key, Array.from(values));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given callback function for every key / value pair in the multimap.
|
||||
*/
|
||||
forEach(callbackfn: (value: V, key: K, map: this) => void): void {
|
||||
this.map.forEach((array, key) =>
|
||||
array.forEach(value => callbackfn(value, key, this))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of key, value pairs for every entry in the map.
|
||||
*/
|
||||
[Symbol.iterator](): Iterator<[K, V]> {
|
||||
return this.entries().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of key, value pairs for every entry in the map.
|
||||
*/
|
||||
entries(): Stream<[K, V]> {
|
||||
return stream(this.map.entries())
|
||||
.flatMap(([key, array]) => array.map(value => [key, value] as [K, V]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of keys in the map.
|
||||
*/
|
||||
keys(): Stream<K> {
|
||||
return stream(this.map.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of values in the map.
|
||||
*/
|
||||
values(): Stream<V> {
|
||||
return stream(this.map.values()).flat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of key, value set pairs for every key in the map.
|
||||
*/
|
||||
entriesGroupedByKey(): Stream<[K, V[]]> {
|
||||
return stream(this.map.entries());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class BiMap<K, V> {
|
||||
|
||||
private map = new Map<K, V>();
|
||||
private inverse = new Map<V, K>();
|
||||
|
||||
get size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
|
||||
constructor()
|
||||
constructor(elements: Array<[K, V]>)
|
||||
constructor(elements?: Array<[K, V]>) {
|
||||
if (elements) {
|
||||
for (const [key, value] of elements) {
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.map.clear();
|
||||
this.inverse.clear();
|
||||
}
|
||||
|
||||
set(key: K, value: V): this {
|
||||
this.map.set(key, value);
|
||||
this.inverse.set(value, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
getKey(value: V): K | undefined {
|
||||
return this.inverse.get(value);
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
const value = this.map.get(key);
|
||||
if (value !== undefined) {
|
||||
this.map.delete(key);
|
||||
this.inverse.delete(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
frontend/node_modules/langium/src/utils/disposable.ts
generated
vendored
Normal file
29
frontend/node_modules/langium/src/utils/disposable.ts
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
export interface Disposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface AsyncDisposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
|
||||
export namespace Disposable {
|
||||
export function create(callback: () => Promise<void>): AsyncDisposable;
|
||||
export function create(callback: () => void): Disposable;
|
||||
export function create(callback: () => void | Promise<void>): Disposable | AsyncDisposable {
|
||||
return {
|
||||
dispose: async () => await callback()
|
||||
};
|
||||
}
|
||||
}
|
||||
22
frontend/node_modules/langium/src/utils/index.ts
generated
vendored
Normal file
22
frontend/node_modules/langium/src/utils/index.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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.
|
||||
******************************************************************************/
|
||||
|
||||
export * from './caching.js';
|
||||
export * from './event.js';
|
||||
export * from './collections.js';
|
||||
export * from './disposable.js';
|
||||
export * from './errors.js';
|
||||
export * from './grammar-loader.js';
|
||||
export * from './promise-utils.js';
|
||||
export * from './stream.js';
|
||||
export * from './uri-utils.js';
|
||||
|
||||
import * as AstUtils from './ast-utils.js';
|
||||
import * as Cancellation from './cancellation.js';
|
||||
import * as CstUtils from './cst-utils.js';
|
||||
import * as GrammarUtils from './grammar-utils.js';
|
||||
import * as RegExpUtils from './regexp-utils.js';
|
||||
export { AstUtils, Cancellation, CstUtils, GrammarUtils, RegExpUtils };
|
||||
8
frontend/node_modules/langium/src/validation/index.ts
generated
vendored
Normal file
8
frontend/node_modules/langium/src/validation/index.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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.
|
||||
******************************************************************************/
|
||||
|
||||
export * from './document-validator.js';
|
||||
export * from './validation-registry.js';
|
||||
179
frontend/node_modules/langium/src/workspace/configuration.ts
generated
vendored
Normal file
179
frontend/node_modules/langium/src/workspace/configuration.ts
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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 { Emitter } from '../utils/event.js';
|
||||
import type {
|
||||
ConfigurationItem,
|
||||
DidChangeConfigurationParams,
|
||||
DidChangeConfigurationRegistrationOptions,
|
||||
Disposable,
|
||||
Event,
|
||||
InitializeParams,
|
||||
InitializedParams
|
||||
} from 'vscode-languageserver-protocol';
|
||||
import type { ServiceRegistry } from '../service-registry.js';
|
||||
import type { LangiumSharedCoreServices } from '../services.js';
|
||||
import { Deferred } from '../utils/promise-utils.js';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface ConfigurationProvider {
|
||||
|
||||
/**
|
||||
* A promise that resolves when the configuration provider is ready to be used.
|
||||
*/
|
||||
readonly ready: Promise<void>;
|
||||
|
||||
/**
|
||||
* When used in a language server context, this method is called when the server receives
|
||||
* the `initialize` request.
|
||||
*/
|
||||
initialize(params: InitializeParams): void;
|
||||
|
||||
/**
|
||||
* When used in a language server context, this method is called when the server receives
|
||||
* the `initialized` notification.
|
||||
*/
|
||||
initialized(params: ConfigurationInitializedParams): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns a configuration value stored for the given language.
|
||||
*
|
||||
* @param language The language id
|
||||
* @param configuration Configuration name
|
||||
*/
|
||||
getConfiguration(language: string, configuration: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* Updates the cached configurations using the `change` notification parameters.
|
||||
*
|
||||
* @param change The parameters of a change configuration notification.
|
||||
* `settings` property of the change object could be expressed as `Record<string, Record<string, any>>`
|
||||
*/
|
||||
updateConfiguration(change: DidChangeConfigurationParams): void;
|
||||
|
||||
/**
|
||||
* Get notified after a configuration section has been updated.
|
||||
*/
|
||||
onConfigurationSectionUpdate(callback: ConfigurationSectionUpdateListener): Disposable
|
||||
}
|
||||
|
||||
export interface ConfigurationInitializedParams extends InitializedParams {
|
||||
register?: (params: DidChangeConfigurationRegistrationOptions) => void,
|
||||
fetchConfiguration?: (configuration: ConfigurationItem[]) => Promise<any>
|
||||
}
|
||||
|
||||
export interface ConfigurationSectionUpdate {
|
||||
/**
|
||||
* The name of the configuration section that has been updated.
|
||||
*/
|
||||
section: string;
|
||||
|
||||
/**
|
||||
* The updated configuration section.
|
||||
*/
|
||||
configuration: any;
|
||||
}
|
||||
|
||||
export type ConfigurationSectionUpdateListener = (update: ConfigurationSectionUpdate) => void;
|
||||
|
||||
/**
|
||||
* Base configuration provider for building up other configuration providers
|
||||
*/
|
||||
export class DefaultConfigurationProvider implements ConfigurationProvider {
|
||||
|
||||
protected readonly serviceRegistry: ServiceRegistry;
|
||||
protected readonly _ready = new Deferred<void>();
|
||||
protected readonly onConfigurationSectionUpdateEmitter = new Emitter<ConfigurationSectionUpdate>();
|
||||
protected settings: Record<string, Record<string, any>> = {};
|
||||
protected workspaceConfig = false;
|
||||
|
||||
constructor(services: LangiumSharedCoreServices) {
|
||||
this.serviceRegistry = services.ServiceRegistry;
|
||||
}
|
||||
|
||||
get ready(): Promise<void> {
|
||||
return this._ready.promise;
|
||||
}
|
||||
|
||||
initialize(params: InitializeParams): void {
|
||||
this.workspaceConfig = params.capabilities.workspace?.configuration ?? false;
|
||||
}
|
||||
|
||||
async initialized(params: ConfigurationInitializedParams): Promise<void> {
|
||||
if (this.workspaceConfig) {
|
||||
if (params.register) {
|
||||
// params.register(...) is a function to be provided by the calling language server for the sake of
|
||||
// decoupling this implementation from the concrete LSP implementations, specifically the LSP Connection
|
||||
|
||||
const languages = this.serviceRegistry.all;
|
||||
params.register({
|
||||
// Listen to configuration changes for all languages
|
||||
section: languages.map(lang => this.toSectionName(lang.LanguageMetaData.languageId))
|
||||
});
|
||||
}
|
||||
|
||||
if (params.fetchConfiguration) {
|
||||
// params.fetchConfiguration(...) is a function to be provided by the calling language server for the sake of
|
||||
// decoupling this implementation from the concrete LSP implementations, specifically the LSP Connection
|
||||
const configToUpdate = this.serviceRegistry.all.map(lang => <ConfigurationItem>{
|
||||
// Fetch the configuration changes for all languages
|
||||
section: this.toSectionName(lang.LanguageMetaData.languageId)
|
||||
});
|
||||
|
||||
// get workspace configurations (default scope URI)
|
||||
const configs = await params.fetchConfiguration(configToUpdate);
|
||||
configToUpdate.forEach((conf, idx) => {
|
||||
this.updateSectionConfiguration(conf.section!, configs[idx]);
|
||||
});
|
||||
}
|
||||
}
|
||||
this._ready.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cached configurations using the `change` notification parameters.
|
||||
*
|
||||
* @param change The parameters of a change configuration notification.
|
||||
* `settings` property of the change object could be expressed as `Record<string, Record<string, any>>`
|
||||
*/
|
||||
updateConfiguration(change: DidChangeConfigurationParams): void {
|
||||
if (typeof change.settings !== 'object' || change.settings === null) {
|
||||
return;
|
||||
}
|
||||
Object.entries(change.settings).forEach(([section, configuration]) => {
|
||||
this.updateSectionConfiguration(section, configuration);
|
||||
this.onConfigurationSectionUpdateEmitter.fire({ section, configuration });
|
||||
});
|
||||
}
|
||||
|
||||
protected updateSectionConfiguration(section: string, configuration: any): void {
|
||||
this.settings[section] = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration value stored for the given language.
|
||||
*
|
||||
* @param language The language id
|
||||
* @param configuration Configuration name
|
||||
*/
|
||||
async getConfiguration(language: string, configuration: string): Promise<any> {
|
||||
await this.ready;
|
||||
|
||||
const sectionName = this.toSectionName(language);
|
||||
if (this.settings[sectionName]) {
|
||||
return this.settings[sectionName][configuration];
|
||||
}
|
||||
}
|
||||
|
||||
protected toSectionName(languageId: string): string {
|
||||
return `${languageId}`;
|
||||
}
|
||||
|
||||
get onConfigurationSectionUpdate(): Event<ConfigurationSectionUpdate> {
|
||||
return this.onConfigurationSectionUpdateEmitter.event;
|
||||
}
|
||||
}
|
||||
687
frontend/node_modules/langium/src/workspace/document-builder.ts
generated
vendored
Normal file
687
frontend/node_modules/langium/src/workspace/document-builder.ts
generated
vendored
Normal file
@@ -0,0 +1,687 @@
|
||||
/******************************************************************************
|
||||
* 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 { LSPErrorCodes, ResponseError } from 'vscode-languageserver-protocol';
|
||||
import { CancellationToken } from '../utils/cancellation.js';
|
||||
import { Disposable } from '../utils/disposable.js';
|
||||
import type { ServiceRegistry } from '../service-registry.js';
|
||||
import type { LangiumSharedCoreServices } from '../services.js';
|
||||
import type { AstNode } from '../syntax-tree.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
import type { Deferred } from '../utils/promise-utils.js';
|
||||
import type { ValidationOptions } from '../validation/document-validator.js';
|
||||
import type { IndexManager } from '../workspace/index-manager.js';
|
||||
import type { LangiumDocument, LangiumDocuments, LangiumDocumentFactory, TextDocumentProvider } from './documents.js';
|
||||
import { MultiMap } from '../utils/collections.js';
|
||||
import { OperationCancelled, interruptAndCheck, isOperationCancelled } from '../utils/promise-utils.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
import { UriUtils, type URI } from '../utils/uri-utils.js';
|
||||
import type { ValidationCategory } from '../validation/validation-registry.js';
|
||||
import { DocumentState } from './documents.js';
|
||||
import type { FileSystemProvider } from './file-system-provider.js';
|
||||
import type { WorkspaceManager } from './workspace-manager.js';
|
||||
|
||||
export interface BuildOptions {
|
||||
/**
|
||||
* Control the linking and references indexing phase with this option. The default if not specified is `true`.
|
||||
* If set to `false`, references can still be resolved - that's done lazily when you access the `ref` property of
|
||||
* a reference. But you won't get any diagnostics for linking errors and the references won't be considered
|
||||
* when updating other documents.
|
||||
*/
|
||||
eagerLinking?: boolean
|
||||
|
||||
/**
|
||||
* Control the validation phase with this option:
|
||||
* - `true` enables all validation checks and forces revalidating the documents
|
||||
* In order to include additional, custom validation categories, override `DefaultDocumentBuilder.getAllValidationCategories(...)`.
|
||||
* - `false` or `undefined` disables all validation checks
|
||||
* - An object runs only the necessary validation checks; the `categories` property restricts this to a specific subset (which might include custom categories as well).
|
||||
*/
|
||||
validation?: boolean | ValidationOptions
|
||||
}
|
||||
|
||||
export interface DocumentBuildState {
|
||||
/** Whether a document has completed its last build process. */
|
||||
completed: boolean
|
||||
/** The options used for the last build process. */
|
||||
options: BuildOptions
|
||||
/** Additional information about the last build result. */
|
||||
result?: {
|
||||
validationChecks?: ValidationCategory[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared-service for building and updating `LangiumDocument`s.
|
||||
*/
|
||||
export interface DocumentBuilder {
|
||||
|
||||
/** The options used for rebuilding documents after an update. */
|
||||
updateBuildOptions: BuildOptions;
|
||||
|
||||
/**
|
||||
* Execute all necessary build steps for the given documents.
|
||||
*
|
||||
* @param documents Set of documents to be built.
|
||||
* @param options Options for the document builder.
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
build<T extends AstNode>(documents: Array<LangiumDocument<T>>, options?: BuildOptions, cancelToken?: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* This method is called when a document change is detected. It updates the state of all
|
||||
* affected documents, including those with references to the changed ones, so they are rebuilt.
|
||||
*
|
||||
* @param changed URIs of changed or created documents
|
||||
* @param deleted URIs of deleted documents
|
||||
* @param cancelToken allows to cancel the current operation
|
||||
* @throws `OperationCancelled` if cancellation is detected during execution
|
||||
*/
|
||||
update(changed: URI[], deleted: URI[], cancelToken?: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Notify the given callback when a document update was triggered, but before any document
|
||||
* is rebuilt. Listeners to this event should not perform any long-running task.
|
||||
*/
|
||||
onUpdate(callback: DocumentUpdateListener): Disposable;
|
||||
|
||||
/**
|
||||
* Reset the state of a document to the specified state, removing any derived data as needed.
|
||||
*
|
||||
* @param document The document to reset.
|
||||
* @param state The state to reset the document to.
|
||||
*/
|
||||
resetToState<T extends AstNode>(document: LangiumDocument<T>, state: DocumentState): void;
|
||||
|
||||
/**
|
||||
* Notify the given callback when a set of documents has been built reaching the specified target state.
|
||||
*/
|
||||
onBuildPhase(targetState: DocumentState, callback: DocumentBuildListener): Disposable;
|
||||
|
||||
/**
|
||||
* Notify the specified callback when a document has been built reaching the specified target state.
|
||||
* Unlike {@link onBuildPhase} the listener is called for every single document.
|
||||
*
|
||||
* There are two main advantages compared to {@link onBuildPhase}:
|
||||
* 1. If the build is cancelled, {@link onDocumentPhase} will still fire for documents that have reached a specific state.
|
||||
* Meanwhile, {@link onBuildPhase} won't fire for that state.
|
||||
* 2. The {@link DocumentBuilder} ensures that all {@link DocumentPhaseListener} instances are called for a built document.
|
||||
* Even if the build is cancelled before those listeners were called.
|
||||
*/
|
||||
onDocumentPhase(targetState: DocumentState, callback: DocumentPhaseListener): Disposable;
|
||||
|
||||
/**
|
||||
* Wait until the workspace has reached the specified state for all documents.
|
||||
*
|
||||
* @param state The desired state. The promise won't resolve until all documents have reached this state
|
||||
* @param cancelToken Optionally allows to cancel the wait operation, disposing any listeners in the process
|
||||
* @throws `OperationCancelled` if cancellation has been requested before the state has been reached
|
||||
*/
|
||||
waitUntil(state: DocumentState, cancelToken?: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Wait until the document specified by the {@link uri} has reached the specified state.
|
||||
*
|
||||
* @param state The desired state. The promise won't resolve until the document has reached this state.
|
||||
* @param uri The specified URI that points to the document. If the URI does not exist, the promise will resolve once the workspace has reached the specified state.
|
||||
* @param cancelToken Optionally allows to cancel the wait operation, disposing any listeners in the process.
|
||||
* @return The URI of the document that has reached the desired state, or `undefined` if the document does not exist.
|
||||
* @throws `OperationCancelled` if cancellation has been requested before the state has been reached
|
||||
*/
|
||||
waitUntil(state: DocumentState, uri?: URI, cancelToken?: CancellationToken): Promise<URI | undefined>;
|
||||
}
|
||||
|
||||
export type DocumentUpdateListener = (changed: URI[], deleted: URI[]) => void | Promise<void>
|
||||
export type DocumentBuildListener = (built: LangiumDocument[], cancelToken: CancellationToken) => void | Promise<void>
|
||||
export type DocumentPhaseListener = (built: LangiumDocument, cancelToken: CancellationToken) => void | Promise<void>
|
||||
export class DefaultDocumentBuilder implements DocumentBuilder {
|
||||
|
||||
updateBuildOptions: BuildOptions = {
|
||||
// Default: run only the built-in validation checks and those in the _fast_ category (includes those without category)
|
||||
validation: {
|
||||
categories: ['built-in', 'fast']
|
||||
}
|
||||
};
|
||||
|
||||
protected readonly langiumDocuments: LangiumDocuments;
|
||||
protected readonly langiumDocumentFactory: LangiumDocumentFactory;
|
||||
protected readonly textDocuments: TextDocumentProvider | undefined;
|
||||
protected readonly indexManager: IndexManager;
|
||||
protected readonly fileSystemProvider: FileSystemProvider;
|
||||
protected readonly workspaceManager: () => WorkspaceManager;
|
||||
protected readonly serviceRegistry: ServiceRegistry;
|
||||
|
||||
protected readonly updateListeners: DocumentUpdateListener[] = [];
|
||||
protected readonly buildPhaseListeners = new MultiMap<DocumentState, DocumentBuildListener>();
|
||||
protected readonly documentPhaseListeners = new MultiMap<DocumentState, DocumentPhaseListener>();
|
||||
protected readonly buildState = new Map<string, DocumentBuildState>();
|
||||
protected readonly documentBuildWaiters = new Map<string, Deferred<void>>();
|
||||
protected currentState = DocumentState.Changed;
|
||||
|
||||
constructor(services: LangiumSharedCoreServices) {
|
||||
this.langiumDocuments = services.workspace.LangiumDocuments;
|
||||
this.langiumDocumentFactory = services.workspace.LangiumDocumentFactory;
|
||||
this.textDocuments = services.workspace.TextDocuments;
|
||||
this.indexManager = services.workspace.IndexManager;
|
||||
this.fileSystemProvider = services.workspace.FileSystemProvider;
|
||||
this.workspaceManager = () => services.workspace.WorkspaceManager;
|
||||
this.serviceRegistry = services.ServiceRegistry;
|
||||
}
|
||||
|
||||
async build<T extends AstNode>(documents: Array<LangiumDocument<T>>, options: BuildOptions = {}, cancelToken = CancellationToken.None): Promise<void> {
|
||||
for (const document of documents) {
|
||||
const key = document.uri.toString();
|
||||
if (document.state === DocumentState.Validated) {
|
||||
if (typeof options.validation === 'boolean' && options.validation) {
|
||||
// Force re-running all validation checks
|
||||
this.resetToState(document, DocumentState.IndexedReferences);
|
||||
} else if (typeof options.validation === 'object') {
|
||||
// Validation with explicit options was requested for a document that has already been partly validated.
|
||||
// In this case, we need to execute only the missing validation categories.
|
||||
const categories = this.findMissingValidationCategories(document, options);
|
||||
if (categories.length > 0) {
|
||||
// Validate this document, since some of the requested validation categories are not executed yet.
|
||||
// In all other cases/else-branches, the document is not build at all.
|
||||
this.buildState.set(key, {
|
||||
completed: false,
|
||||
options: {
|
||||
validation: {
|
||||
categories
|
||||
}
|
||||
},
|
||||
result: this.buildState.get(key)?.result,
|
||||
});
|
||||
// Reset the state, but keep the existing validation markers of the already completed validation categories.
|
||||
document.state = DocumentState.IndexedReferences;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default: forget any previous build options
|
||||
this.buildState.delete(key);
|
||||
}
|
||||
}
|
||||
this.currentState = DocumentState.Changed;
|
||||
await this.emitUpdate(documents.map(e => e.uri), []);
|
||||
await this.buildDocuments(documents, options, cancelToken);
|
||||
}
|
||||
|
||||
async update(changed: URI[], deleted: URI[], cancelToken = CancellationToken.None): Promise<void> {
|
||||
this.currentState = DocumentState.Changed;
|
||||
// Remove all metadata of documents that are reported as deleted
|
||||
const deletedUris: URI[] = [];
|
||||
for (const deletedUri of deleted) {
|
||||
// Since the deleted URI might point to a directory, we delete all documents within
|
||||
const deletedDocs = this.langiumDocuments.deleteDocuments(deletedUri);
|
||||
for (const doc of deletedDocs) {
|
||||
deletedUris.push(doc.uri);
|
||||
this.cleanUpDeleted(doc);
|
||||
}
|
||||
}
|
||||
// Since the changed URI might point to a directory, we need to check all (nested) documents in that directory
|
||||
const changedUris = (await Promise.all(changed.map(uri => this.findChangedUris(uri)))).flat();
|
||||
// Set the state of all changed documents to `Changed` so they are completely rebuilt
|
||||
for (const changedUri of changedUris) {
|
||||
let changedDocument = this.langiumDocuments.getDocument(changedUri);
|
||||
if (changedDocument === undefined) {
|
||||
// We create an unparsed, invalid document.
|
||||
// This will be parsed as soon as we reach the first document builder phase.
|
||||
// This allows to cancel the parsing process later in case we need it.
|
||||
changedDocument = this.langiumDocumentFactory.fromModel({ $type: 'INVALID' }, changedUri);
|
||||
changedDocument.state = DocumentState.Changed; // required, since `langiumDocumentFactory.fromModel` marks the new document as `DocumentState.Parsed`
|
||||
this.langiumDocuments.addDocument(changedDocument);
|
||||
}
|
||||
this.resetToState(changedDocument, DocumentState.Changed);
|
||||
}
|
||||
// Set the state of all documents that should be relinked to `ComputedScopes` (if not already lower)
|
||||
const allChangedUris = stream(changedUris).concat(deletedUris).map(uri => uri.toString()).toSet();
|
||||
this.langiumDocuments.all
|
||||
.filter(doc => !allChangedUris.has(doc.uri.toString()) && this.shouldRelink(doc, allChangedUris))
|
||||
.forEach(doc => this.resetToState(doc, DocumentState.ComputedScopes));
|
||||
// Notify listeners of the update
|
||||
await this.emitUpdate(changedUris, deletedUris);
|
||||
// Only allow interrupting the execution after all state changes are done
|
||||
await interruptAndCheck(cancelToken);
|
||||
|
||||
// Collect and sort all documents that we should rebuild
|
||||
const rebuildDocuments = this.sortDocuments(
|
||||
this.langiumDocuments.all
|
||||
.filter(doc =>
|
||||
// This includes those that were reported as changed and those that we selected for relinking
|
||||
doc.state < DocumentState.Validated
|
||||
// This includes those for which a previous build has been cancelled
|
||||
|| !this.buildState.get(doc.uri.toString())?.completed
|
||||
// `updateBuildOptions` changed between the last build (which is completed) and the current build,
|
||||
// leading to incomplete results, e.g. some validation categories are requested, which are not executed during the last build
|
||||
|| this.resultsAreIncomplete(doc, this.updateBuildOptions)
|
||||
)
|
||||
.toArray()
|
||||
);
|
||||
await this.buildDocuments(rebuildDocuments, this.updateBuildOptions, cancelToken);
|
||||
}
|
||||
|
||||
protected resultsAreIncomplete(document: LangiumDocument, options: BuildOptions | undefined): boolean {
|
||||
return this.findMissingValidationCategories(document, options).length >= 1;
|
||||
}
|
||||
|
||||
protected findMissingValidationCategories(document: LangiumDocument, options: BuildOptions | undefined): ValidationCategory[] {
|
||||
const state = this.buildState.get(document.uri.toString());
|
||||
const allCategories = this.serviceRegistry.getServices(document.uri).validation.ValidationRegistry.getAllValidationCategories(document);
|
||||
const executedCategories = state?.result?.validationChecks ? new Set(state?.result?.validationChecks) : state?.completed ? allCategories : new Set();
|
||||
const requestedCategories = (options === undefined || options.validation === true) ? allCategories
|
||||
: typeof options.validation === 'object' ? (options.validation.categories ?? allCategories) : [];
|
||||
return stream(requestedCategories).filter(requested => !executedCategories.has(requested)).toArray();
|
||||
}
|
||||
|
||||
protected async findChangedUris(changed: URI): Promise<URI[]> {
|
||||
// Most common case is that the document/textDocument at the specified URI has changed
|
||||
const document = this.langiumDocuments.getDocument(changed) ?? this.textDocuments?.get(changed);
|
||||
if (document) {
|
||||
return [changed];
|
||||
}
|
||||
// If the document doesn't exist yet, we need to check what kind of file has changed
|
||||
try {
|
||||
const stat = await this.fileSystemProvider.stat(changed);
|
||||
if (stat.isDirectory) {
|
||||
// If a directory has changed, we need to check all documents in that directory
|
||||
const uris = await this.workspaceManager().searchFolder(changed);
|
||||
return uris;
|
||||
} else if (this.workspaceManager().shouldIncludeEntry(stat)) {
|
||||
// Return the changed URI if it's a file that we can handle
|
||||
return [changed];
|
||||
}
|
||||
} catch {
|
||||
// If we can't determine the file type, we discard the change
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected async emitUpdate(changed: URI[], deleted: URI[]): Promise<void> {
|
||||
await Promise.all(this.updateListeners.map(listener => listener(changed, deleted)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the given documents by priority. By default, documents with an open text document are prioritized.
|
||||
* This is useful to ensure that visible documents show their diagnostics before all other documents.
|
||||
*
|
||||
* This improves the responsiveness in large workspaces as users usually don't care about diagnostics
|
||||
* in files that are currently not opened in the editor.
|
||||
*/
|
||||
protected sortDocuments(documents: LangiumDocument[]): LangiumDocument[] {
|
||||
let left = 0;
|
||||
let right = documents.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
while (left < documents.length && this.hasTextDocument(documents[left])) {
|
||||
left++;
|
||||
}
|
||||
|
||||
while (right >= 0 && !this.hasTextDocument(documents[right])) {
|
||||
right--;
|
||||
}
|
||||
|
||||
if (left < right) {
|
||||
[documents[left], documents[right]] = [documents[right], documents[left]];
|
||||
}
|
||||
}
|
||||
|
||||
return documents;
|
||||
}
|
||||
|
||||
private hasTextDocument(doc: LangiumDocument): boolean {
|
||||
return Boolean(this.textDocuments?.get(doc.uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given document should be relinked after changes were found in the given URIs.
|
||||
*/
|
||||
protected shouldRelink(document: LangiumDocument, changedUris: Set<string>): boolean {
|
||||
// Relink documents with linking errors -- maybe those references can be resolved now
|
||||
if (document.references.some(ref => ref.error !== undefined)) {
|
||||
return true;
|
||||
}
|
||||
// Check whether the document is affected by any of the changed URIs
|
||||
return this.indexManager.isAffected(document, changedUris);
|
||||
}
|
||||
|
||||
onUpdate(callback: DocumentUpdateListener): Disposable {
|
||||
this.updateListeners.push(callback);
|
||||
return Disposable.create(() => {
|
||||
const index = this.updateListeners.indexOf(callback);
|
||||
if (index >= 0) {
|
||||
this.updateListeners.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetToState<T extends AstNode>(document: LangiumDocument<T>, state: DocumentState): void {
|
||||
switch (state) {
|
||||
case DocumentState.Changed: {
|
||||
// Fall through
|
||||
}
|
||||
case DocumentState.Parsed:
|
||||
this.indexManager.removeContent(document.uri);
|
||||
// Fall through
|
||||
case DocumentState.IndexedContent:
|
||||
document.localSymbols = undefined;
|
||||
// Fall through
|
||||
case DocumentState.ComputedScopes: {
|
||||
const linker = this.serviceRegistry.getServices(document.uri).references.Linker;
|
||||
linker.unlink(document);
|
||||
// Fall through
|
||||
}
|
||||
case DocumentState.Linked:
|
||||
this.indexManager.removeReferences(document.uri);
|
||||
// Fall through
|
||||
case DocumentState.IndexedReferences:
|
||||
document.diagnostics = undefined;
|
||||
this.buildState.delete(document.uri.toString());
|
||||
// Fall through
|
||||
case DocumentState.Validated:
|
||||
// do nothing and keep the buildState
|
||||
}
|
||||
if (document.state > state) {
|
||||
document.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
protected cleanUpDeleted<T extends AstNode>(document: LangiumDocument<T>): void {
|
||||
this.buildState.delete(document.uri.toString());
|
||||
this.indexManager.remove(document.uri);
|
||||
// Since this method `cleanUpDeleted` is not available from outside, the following line is not necessary, since the state is already set before.
|
||||
// This line does not hurt and makes the code to be in sync with `resetToState`.
|
||||
// If `cleanUpDeleted` is called in custom document builders at some more places, this line becomes necessary.
|
||||
document.state = DocumentState.Changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the given documents by stepping through all build phases. If a document's state indicates
|
||||
* that a certain build phase is already done, the phase is skipped for that document.
|
||||
*
|
||||
* @param documents The documents to build.
|
||||
* @param options the {@link BuildOptions} to use.
|
||||
* @param cancelToken A cancellation token that can be used to cancel the build.
|
||||
* @returns A promise that resolves when the build is done.
|
||||
*/
|
||||
protected async buildDocuments(documents: LangiumDocument[], options: BuildOptions, cancelToken: CancellationToken): Promise<void> {
|
||||
this.prepareBuild(documents, options);
|
||||
// 0. Parse content
|
||||
await this.runCancelable(documents, DocumentState.Parsed, cancelToken, doc =>
|
||||
this.langiumDocumentFactory.update(doc, cancelToken)
|
||||
);
|
||||
// 1. Index content: collect the documents' symbols being accessible by other documents
|
||||
await this.runCancelable(documents, DocumentState.IndexedContent, cancelToken, doc =>
|
||||
this.indexManager.updateContent(doc, cancelToken)
|
||||
);
|
||||
// 2. Local symbols: collect each documents' symbols being accessible within the document (only)
|
||||
await this.runCancelable(documents, DocumentState.ComputedScopes, cancelToken, async doc => {
|
||||
const scopeComputation = this.serviceRegistry.getServices(doc.uri).references.ScopeComputation;
|
||||
doc.localSymbols = await scopeComputation.collectLocalSymbols(doc, cancelToken);
|
||||
});
|
||||
// 3. Linking
|
||||
const toBeLinked = documents.filter(doc => this.shouldLink(doc));
|
||||
await this.runCancelable(toBeLinked, DocumentState.Linked, cancelToken, doc => {
|
||||
const linker = this.serviceRegistry.getServices(doc.uri).references.Linker;
|
||||
return linker.link(doc, cancelToken);
|
||||
});
|
||||
// 4. Index references
|
||||
await this.runCancelable(toBeLinked, DocumentState.IndexedReferences, cancelToken, doc =>
|
||||
this.indexManager.updateReferences(doc, cancelToken)
|
||||
);
|
||||
// 5. Validation
|
||||
const toBeValidated = documents.filter(doc => {
|
||||
if (this.shouldValidate(doc)) {
|
||||
return true; // the build state is marked as completed after finishing the validation for the current document
|
||||
} else {
|
||||
this.markAsCompleted(doc); // since the validation is skipped for this document, it is already completed now
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await this.runCancelable(toBeValidated, DocumentState.Validated, cancelToken, async doc => {
|
||||
await this.validate(doc, cancelToken);
|
||||
this.markAsCompleted(doc);
|
||||
});
|
||||
}
|
||||
|
||||
protected markAsCompleted(document: LangiumDocument): void {
|
||||
const state = this.buildState.get(document.uri.toString());
|
||||
if (state) {
|
||||
state.completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs prior to beginning the build process to update the {@link DocumentBuildState} for each document
|
||||
*
|
||||
* @param documents collection of documents to be built
|
||||
* @param options the {@link BuildOptions} to use
|
||||
*/
|
||||
protected prepareBuild(documents: LangiumDocument[], options: BuildOptions): void {
|
||||
for (const doc of documents) {
|
||||
const key = doc.uri.toString();
|
||||
const state = this.buildState.get(key);
|
||||
if (
|
||||
!state // If the document has no previous build state, we set it.
|
||||
|| state.completed // If it has one, but it's already marked as completed, we overwrite it.
|
||||
) {
|
||||
this.buildState.set(key, {
|
||||
completed: false,
|
||||
options,
|
||||
result: state?.result
|
||||
});
|
||||
} else {
|
||||
// If the previous build was not completed, we keep its DocumentState and continue from the DocumentState where it was cancelled,
|
||||
// e.g. the previous build options are used, including the previously requested validation categories.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a cancelable operation on a set of documents to bring them to a specified {@link DocumentState}.
|
||||
*
|
||||
* @param documents The array of documents to process.
|
||||
* @param targetState The target {@link DocumentState} to bring the documents to.
|
||||
* @param cancelToken A token that can be used to cancel the operation.
|
||||
* @param callback A function to be called for each document.
|
||||
* @returns A promise that resolves when all documents have been processed or the operation is canceled.
|
||||
* @throws Will throw `OperationCancelled` if the operation is canceled via a `CancellationToken`.
|
||||
*/
|
||||
protected async runCancelable(documents: LangiumDocument[], targetState: DocumentState, cancelToken: CancellationToken,
|
||||
callback: (document: LangiumDocument) => MaybePromise<unknown>): Promise<void> {
|
||||
for (const document of documents) {
|
||||
if (document.state < targetState) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
await callback(document);
|
||||
document.state = targetState;
|
||||
await this.notifyDocumentPhase(document, targetState, cancelToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use `filtered` here, as that will miss documents that have previously reached the current target state.
|
||||
// For example, this happens in case the cancellation triggers between the processing of two documents
|
||||
// or files that were picked up during the workspace initialization.
|
||||
const targetStateDocs = documents.filter(doc => doc.state === targetState);
|
||||
await this.notifyBuildPhase(targetStateDocs, targetState, cancelToken);
|
||||
this.currentState = targetState;
|
||||
}
|
||||
|
||||
onBuildPhase(targetState: DocumentState, callback: DocumentBuildListener): Disposable {
|
||||
this.buildPhaseListeners.add(targetState, callback);
|
||||
return Disposable.create(() => {
|
||||
this.buildPhaseListeners.delete(targetState, callback);
|
||||
});
|
||||
}
|
||||
|
||||
onDocumentPhase(targetState: DocumentState, callback: DocumentPhaseListener): Disposable {
|
||||
this.documentPhaseListeners.add(targetState, callback);
|
||||
return Disposable.create(() => {
|
||||
this.documentPhaseListeners.delete(targetState, callback);
|
||||
});
|
||||
}
|
||||
|
||||
waitUntil(state: DocumentState, cancelToken?: CancellationToken): Promise<void>;
|
||||
waitUntil(state: DocumentState, uri?: URI, cancelToken?: CancellationToken): Promise<URI>;
|
||||
waitUntil(state: DocumentState, uriOrToken?: URI | CancellationToken, cancelToken?: CancellationToken): Promise<URI | void> {
|
||||
let uri: URI | undefined = undefined;
|
||||
if (uriOrToken && 'path' in uriOrToken) {
|
||||
uri = uriOrToken;
|
||||
} else {
|
||||
cancelToken = uriOrToken;
|
||||
}
|
||||
cancelToken ??= CancellationToken.None;
|
||||
if (uri) {
|
||||
return this.awaitDocumentState(state, uri, cancelToken);
|
||||
|
||||
} else {
|
||||
return this.awaitBuilderState(state, cancelToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected awaitDocumentState(state: DocumentState, uri: URI, cancelToken: CancellationToken): Promise<URI> {
|
||||
const document = this.langiumDocuments.getDocument(uri);
|
||||
if (!document) {
|
||||
return Promise.reject(
|
||||
new ResponseError(
|
||||
LSPErrorCodes.ServerCancelled,
|
||||
`No document found for URI: ${uri.toString()}`
|
||||
)
|
||||
);
|
||||
|
||||
} else if (document.state >= state) {
|
||||
return Promise.resolve(uri);
|
||||
|
||||
} else if (cancelToken.isCancellationRequested) {
|
||||
return Promise.reject(OperationCancelled);
|
||||
|
||||
} else if (this.currentState >= state && state > document.state) {
|
||||
// this would imply that the document has been excluded from linking or validation, for example;
|
||||
// this should never occur, the LS need to make sure that the affected document is properly built,
|
||||
// alternatively, the build state requirement need to be relaxed.
|
||||
return Promise.reject(
|
||||
new ResponseError(
|
||||
LSPErrorCodes.RequestFailed,
|
||||
`Document state of ${uri.toString()} is ${DocumentState[document.state]}, requiring ${DocumentState[state]}, but workspace state is already ${DocumentState[this.currentState]}. Returning undefined.`
|
||||
)
|
||||
);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildDisposable = this.onDocumentPhase(state, (doc) => {
|
||||
if (UriUtils.equals(doc.uri, uri)) {
|
||||
buildDisposable.dispose();
|
||||
cancelDisposable.dispose();
|
||||
resolve(doc.uri);
|
||||
}
|
||||
});
|
||||
const cancelDisposable = cancelToken!.onCancellationRequested(() => {
|
||||
buildDisposable.dispose();
|
||||
cancelDisposable.dispose();
|
||||
reject(OperationCancelled);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected awaitBuilderState(state: DocumentState, cancelToken: CancellationToken): Promise<void> {
|
||||
if (this.currentState >= state) {
|
||||
return Promise.resolve();
|
||||
} else if (cancelToken.isCancellationRequested) {
|
||||
return Promise.reject(OperationCancelled);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildDisposable = this.onBuildPhase(state, () => {
|
||||
buildDisposable.dispose();
|
||||
cancelDisposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
const cancelDisposable = cancelToken!.onCancellationRequested(() => {
|
||||
buildDisposable.dispose();
|
||||
cancelDisposable.dispose();
|
||||
reject(OperationCancelled);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async notifyDocumentPhase(document: LangiumDocument, state: DocumentState, cancelToken: CancellationToken): Promise<void> {
|
||||
const listeners = this.documentPhaseListeners.get(state);
|
||||
const listenersCopy = listeners.slice();
|
||||
for (const listener of listenersCopy) {
|
||||
try {
|
||||
await interruptAndCheck(cancelToken);
|
||||
await listener(document, cancelToken);
|
||||
} catch (err) {
|
||||
// Ignore cancellation errors
|
||||
// We want to finish the listeners before throwing
|
||||
if (!isOperationCancelled(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async notifyBuildPhase(documents: LangiumDocument[], state: DocumentState, cancelToken: CancellationToken): Promise<void> {
|
||||
if (documents.length === 0) {
|
||||
// Don't notify when no document has been processed
|
||||
return;
|
||||
}
|
||||
const listeners = this.buildPhaseListeners.get(state);
|
||||
const listenersCopy = listeners.slice();
|
||||
for (const listener of listenersCopy) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
await listener(documents, cancelToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given document should be linked during a build. The default
|
||||
* implementation checks the `eagerLinking` property of the build options. If it's set to `true`
|
||||
* or `undefined`, the document is included in the linking phase. This also affects the
|
||||
* references indexing phase, which depends on eager linking.
|
||||
*/
|
||||
protected shouldLink(document: LangiumDocument): boolean {
|
||||
return this.getBuildOptions(document).eagerLinking ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given document should be validated during a build. The default
|
||||
* implementation checks the `validation` property of the build options. If it's set to `true`
|
||||
* or a `ValidationOptions` object, the document is included in the validation phase.
|
||||
*/
|
||||
protected shouldValidate(document: LangiumDocument): boolean {
|
||||
return Boolean(this.getBuildOptions(document).validation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run validation checks on the given document and store the resulting diagnostics in the document.
|
||||
* If the document already contains diagnostics, the new ones are added to the list.
|
||||
*/
|
||||
protected async validate(document: LangiumDocument, cancelToken: CancellationToken): Promise<void> {
|
||||
const validator = this.serviceRegistry.getServices(document.uri).validation.DocumentValidator;
|
||||
const options = this.getBuildOptions(document);
|
||||
const validationOptions = typeof options.validation === 'object' ? { ...options.validation } : {};
|
||||
validationOptions.categories = this.findMissingValidationCategories(document, options); // execute only not-yet-executed categories
|
||||
const diagnostics = await validator.validateDocument(document, validationOptions, cancelToken);
|
||||
if (document.diagnostics) {
|
||||
document.diagnostics.push(...diagnostics); // keep diagnostics of previously executed categories
|
||||
} else {
|
||||
document.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
// Store information about the executed validation in the build state
|
||||
const state = this.buildState.get(document.uri.toString());
|
||||
if (state) {
|
||||
state.result ??= {};
|
||||
if (state.result.validationChecks) {
|
||||
state.result.validationChecks = stream(state.result.validationChecks).concat(validationOptions.categories).distinct().toArray();
|
||||
} else {
|
||||
state.result.validationChecks = [...validationOptions.categories];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getBuildOptions(document: LangiumDocument): BuildOptions {
|
||||
return this.buildState.get(document.uri.toString())?.options ?? {};
|
||||
}
|
||||
|
||||
}
|
||||
120
frontend/node_modules/langium/src/workspace/file-system-provider.ts
generated
vendored
Normal file
120
frontend/node_modules/langium/src/workspace/file-system-provider.ts
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 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';
|
||||
|
||||
export interface FileSystemNode {
|
||||
readonly isFile: boolean;
|
||||
readonly isDirectory: boolean;
|
||||
readonly uri: URI;
|
||||
}
|
||||
|
||||
export type FileSystemFilter = (node: FileSystemNode) => boolean;
|
||||
|
||||
/**
|
||||
* Provides methods to interact with an abstract file system. The default implementation is based on the node.js `fs` API.
|
||||
*/
|
||||
export interface FileSystemProvider {
|
||||
/**
|
||||
* Gets the status of a file or directory.
|
||||
* The status includes meta data such as whether the node is a file or directory.
|
||||
* @param uri The URI of the file or directory.
|
||||
*/
|
||||
stat(uri: URI): Promise<FileSystemNode>;
|
||||
/**
|
||||
* Gets the status of a file or directory synchronously.
|
||||
* The status includes meta data such as whether the node is a file or directory.
|
||||
* @param uri The URI of the file or directory.
|
||||
*/
|
||||
statSync(uri: URI): FileSystemNode;
|
||||
/**
|
||||
* Checks if a file exists at the specified URI.
|
||||
* @returns `true` if a file exists at the specified URI, `false` otherwise.
|
||||
*/
|
||||
exists(uri: URI): Promise<boolean>;
|
||||
/**
|
||||
* Checks if a file exists at the specified URI synchronously.
|
||||
* @returns `true` if a file exists at the specified URI, `false` otherwise.
|
||||
*/
|
||||
existsSync(uri: URI): boolean;
|
||||
/**
|
||||
* Reads a binary file asynchronously from a given URI.
|
||||
* @returns The binary content of the file with the specified URI.
|
||||
*/
|
||||
readBinary(uri: URI): Promise<Uint8Array>;
|
||||
/**
|
||||
* Reads a binary file synchronously from a given URI.
|
||||
* @returns The binary content of the file with the specified URI.
|
||||
*/
|
||||
readBinarySync(uri: URI): Uint8Array;
|
||||
/**
|
||||
* Reads a document asynchronously from a given URI.
|
||||
* @returns The string content of the file with the specified URI.
|
||||
*/
|
||||
readFile(uri: URI): Promise<string>;
|
||||
/**
|
||||
* Reads a document synchronously from a given URI.
|
||||
* @returns The string content of the file with the specified
|
||||
*/
|
||||
readFileSync(uri: URI): string;
|
||||
/**
|
||||
* Reads the directory information for the given URI.
|
||||
* @returns The list of file system entries that are contained within the specified directory.
|
||||
*/
|
||||
readDirectory(uri: URI): Promise<FileSystemNode[]>;
|
||||
/**
|
||||
* Reads the directory information for the given URI synchronously.
|
||||
* @returns The list of file system entries that are contained within the specified directory.
|
||||
*/
|
||||
readDirectorySync(uri: URI): FileSystemNode[];
|
||||
}
|
||||
|
||||
export class EmptyFileSystemProvider implements FileSystemProvider {
|
||||
|
||||
stat(_uri: URI): Promise<FileSystemNode> {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
|
||||
statSync(_uri: URI): FileSystemNode {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
async exists(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
existsSync(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
readBinary(): Promise<Uint8Array> {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
|
||||
readBinarySync(): Uint8Array {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
|
||||
readFile(): Promise<string> {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
|
||||
readFileSync(): string {
|
||||
throw new Error('No file system is available.');
|
||||
}
|
||||
|
||||
async readDirectory(): Promise<FileSystemNode[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
readDirectorySync(): FileSystemNode[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const EmptyFileSystem = {
|
||||
fileSystemProvider: () => new EmptyFileSystemProvider()
|
||||
};
|
||||
195
frontend/node_modules/langium/src/workspace/index-manager.ts
generated
vendored
Normal file
195
frontend/node_modules/langium/src/workspace/index-manager.ts
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/******************************************************************************
|
||||
* 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 { ServiceRegistry } from '../service-registry.js';
|
||||
import type { LangiumSharedCoreServices } from '../services.js';
|
||||
import type { AstNode, AstNodeDescription, AstReflection } from '../syntax-tree.js';
|
||||
import { getDocument } from '../utils/ast-utils.js';
|
||||
import { ContextCache } from '../utils/caching.js';
|
||||
import { CancellationToken } from '../utils/cancellation.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
import type { URI } from '../utils/uri-utils.js';
|
||||
import { UriUtils } from '../utils/uri-utils.js';
|
||||
import type { ReferenceDescription } from './ast-descriptions.js';
|
||||
import type { LangiumDocument, LangiumDocuments } from './documents.js';
|
||||
|
||||
/**
|
||||
* The index manager is responsible for keeping metadata about symbols and cross-references
|
||||
* in the workspace. It is used to look up symbols in the global scope, mostly during linking
|
||||
* and completion. This service is shared between all languages of a language server.
|
||||
*/
|
||||
export interface IndexManager {
|
||||
|
||||
/**
|
||||
* Remove the specified document URI from the index.
|
||||
* Necessary when documents are deleted and not referenceable anymore.
|
||||
*
|
||||
* @param uri The URI of the document for which index data shall be removed
|
||||
*/
|
||||
remove(uri: URI): void;
|
||||
|
||||
/**
|
||||
* Remove only the information about the exportable content of a document.
|
||||
*/
|
||||
removeContent(uri: URI): void;
|
||||
|
||||
/**
|
||||
* Remove only the information about the cross-references of a document.
|
||||
*/
|
||||
removeReferences(uri: URI): void;
|
||||
|
||||
/**
|
||||
* Update the information about the exportable content of a document inside the index.
|
||||
*
|
||||
* @param document Document to be updated
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
updateContent(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update the information about the cross-references of a document inside the index.
|
||||
*
|
||||
* @param document Document to be updated
|
||||
* @param cancelToken Indicates when to cancel the current operation.
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
updateReferences(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Determine whether the given document could be affected by changes of the documents
|
||||
* identified by the given URIs (second parameter). The document is typically regarded as
|
||||
* affected if it contains a reference to any of the changed files.
|
||||
*
|
||||
* @param document Document to check whether it's affected
|
||||
* @param changedUris URIs of the changed documents
|
||||
*/
|
||||
isAffected(document: LangiumDocument, changedUris: Set<string>): boolean;
|
||||
|
||||
/**
|
||||
* Compute a list of all exported elements, optionally filtered using a type identifier and document URIs.
|
||||
*
|
||||
* @param nodeType The type to filter with, or `undefined` to return descriptions of all types.
|
||||
* @param uris If specified, only returns elements from the given URIs.
|
||||
* @returns a `Stream` containing all globally visible nodes (of a given type).
|
||||
*/
|
||||
allElements(nodeType?: string, uris?: Set<string>): Stream<AstNodeDescription>;
|
||||
|
||||
/**
|
||||
* Returns all known references that are pointing to the given `targetNode`.
|
||||
*
|
||||
* @param targetNode the `AstNode` to look up references for
|
||||
* @param astNodePath the path that points to the `targetNode` inside the document. See also `AstNodeLocator`
|
||||
*
|
||||
* @returns a `Stream` of references that are targeting the `targetNode`
|
||||
*/
|
||||
findAllReferences(targetNode: AstNode, astNodePath: string): Stream<ReferenceDescription>;
|
||||
|
||||
}
|
||||
|
||||
export class DefaultIndexManager implements IndexManager {
|
||||
|
||||
protected readonly serviceRegistry: ServiceRegistry;
|
||||
protected readonly documents: LangiumDocuments;
|
||||
protected readonly astReflection: AstReflection;
|
||||
|
||||
/**
|
||||
* The symbol index stores all `AstNodeDescription` items exported by a document.
|
||||
* The key used in this map is the string representation of the specific document URI.
|
||||
*/
|
||||
protected readonly symbolIndex = new Map<string, AstNodeDescription[]>();
|
||||
/**
|
||||
* This is a cache for the `allElements()` method.
|
||||
* It caches the descriptions from `symbolIndex` grouped by types.
|
||||
*/
|
||||
protected readonly symbolByTypeIndex = new ContextCache<string, string, AstNodeDescription[]>();
|
||||
/**
|
||||
* This index keeps track of all `ReferenceDescription` items exported by a document.
|
||||
* This is used to compute which elements are affected by a document change
|
||||
* and for finding references to an AST node.
|
||||
*/
|
||||
protected readonly referenceIndex = new Map<string, ReferenceDescription[]>();
|
||||
|
||||
constructor(services: LangiumSharedCoreServices) {
|
||||
this.documents = services.workspace.LangiumDocuments;
|
||||
this.serviceRegistry = services.ServiceRegistry;
|
||||
this.astReflection = services.AstReflection;
|
||||
}
|
||||
|
||||
findAllReferences(targetNode: AstNode, astNodePath: string): Stream<ReferenceDescription> {
|
||||
const targetDocUri = getDocument(targetNode).uri;
|
||||
const result: ReferenceDescription[] = [];
|
||||
this.referenceIndex.forEach(docRefs => {
|
||||
docRefs.forEach(refDescr => {
|
||||
if (UriUtils.equals(refDescr.targetUri, targetDocUri) && refDescr.targetPath === astNodePath) {
|
||||
result.push(refDescr);
|
||||
}
|
||||
});
|
||||
});
|
||||
return stream(result);
|
||||
}
|
||||
|
||||
allElements(nodeType?: string, uris?: Set<string>): Stream<AstNodeDescription> {
|
||||
let documentUris = stream(this.symbolIndex.keys());
|
||||
if (uris) {
|
||||
documentUris = documentUris.filter(uri => !uris || uris.has(uri));
|
||||
}
|
||||
return documentUris
|
||||
.map(uri => this.getFileDescriptions(uri, nodeType))
|
||||
.flat();
|
||||
}
|
||||
|
||||
protected getFileDescriptions(uri: string, nodeType?: string): AstNodeDescription[] {
|
||||
if (!nodeType) {
|
||||
return this.symbolIndex.get(uri) ?? [];
|
||||
}
|
||||
const descriptions = this.symbolByTypeIndex.get(uri, nodeType, () => {
|
||||
const allFileDescriptions = this.symbolIndex.get(uri) ?? [];
|
||||
return allFileDescriptions.filter(e => this.astReflection.isSubtype(e.type, nodeType));
|
||||
});
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
remove(uri: URI): void {
|
||||
this.removeContent(uri);
|
||||
this.removeReferences(uri);
|
||||
}
|
||||
|
||||
removeContent(uri: URI): void {
|
||||
const uriString = uri.toString();
|
||||
this.symbolIndex.delete(uriString);
|
||||
this.symbolByTypeIndex.clear(uriString);
|
||||
}
|
||||
|
||||
removeReferences(uri: URI): void {
|
||||
const uriString = uri.toString();
|
||||
this.referenceIndex.delete(uriString);
|
||||
}
|
||||
|
||||
async updateContent(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<void> {
|
||||
const services = this.serviceRegistry.getServices(document.uri);
|
||||
const exports = await services.references.ScopeComputation.collectExportedSymbols(document, cancelToken);
|
||||
const uri = document.uri.toString();
|
||||
this.symbolIndex.set(uri, exports);
|
||||
this.symbolByTypeIndex.clear(uri);
|
||||
}
|
||||
|
||||
async updateReferences(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<void> {
|
||||
const services = this.serviceRegistry.getServices(document.uri);
|
||||
const indexData = await services.workspace.ReferenceDescriptionProvider.createDescriptions(document, cancelToken);
|
||||
this.referenceIndex.set(document.uri.toString(), indexData);
|
||||
}
|
||||
|
||||
isAffected(document: LangiumDocument, changedUris: Set<string>): boolean {
|
||||
const references = this.referenceIndex.get(document.uri.toString());
|
||||
if (!references) {
|
||||
return false;
|
||||
}
|
||||
return references.some(ref => !ref.local && changedUris.has(ref.targetUri.toString()));
|
||||
}
|
||||
|
||||
}
|
||||
114
frontend/node_modules/langium/src/workspace/workspace-lock.ts
generated
vendored
Normal file
114
frontend/node_modules/langium/src/workspace/workspace-lock.ts
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 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 AbstractCancellationTokenSource, CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
|
||||
import { Deferred, isOperationCancelled, startCancelableOperation, type MaybePromise } from '../utils/promise-utils.js';
|
||||
|
||||
/**
|
||||
* Utility service to execute mutually exclusive actions.
|
||||
*/
|
||||
export interface WorkspaceLock {
|
||||
/**
|
||||
* Performs a single async action, like initializing the workspace or processing document changes.
|
||||
* Only one action will be executed at a time.
|
||||
*
|
||||
* When another action is queued up, the token provided for the action will be cancelled.
|
||||
* Assuming the action makes use of this token, the next action only has to wait for the current action to finish cancellation.
|
||||
*/
|
||||
write(action: (token: CancellationToken) => MaybePromise<void>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Performs a single action, like computing completion results or providing workspace symbols.
|
||||
* Read actions will only be executed after all write actions have finished. They will be executed in parallel if possible.
|
||||
*
|
||||
* If a write action is currently running, the read action will be queued up and executed afterwards.
|
||||
* If a new write action is queued up while a read action is waiting, the write action will receive priority and will be handled before the read action.
|
||||
*
|
||||
* Note that read actions are not allowed to modify anything in the workspace. Please use {@link write} instead.
|
||||
*/
|
||||
read<T>(action: () => MaybePromise<T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* Cancels the last queued write action. All previous write actions already have been cancelled.
|
||||
*/
|
||||
cancelWrite(): void;
|
||||
}
|
||||
|
||||
type LockAction<T = void> = (token: CancellationToken) => MaybePromise<T>;
|
||||
|
||||
interface LockEntry {
|
||||
action: LockAction<unknown>;
|
||||
deferred: Deferred<unknown>;
|
||||
cancellationToken: CancellationToken;
|
||||
}
|
||||
|
||||
export class DefaultWorkspaceLock implements WorkspaceLock {
|
||||
|
||||
private previousTokenSource: AbstractCancellationTokenSource = new CancellationTokenSource();
|
||||
private writeQueue: LockEntry[] = [];
|
||||
private readQueue: LockEntry[] = [];
|
||||
private done = true;
|
||||
|
||||
write(action: (token: CancellationToken) => MaybePromise<void>): Promise<void> {
|
||||
this.cancelWrite();
|
||||
const tokenSource = startCancelableOperation();
|
||||
this.previousTokenSource = tokenSource;
|
||||
return this.enqueue(this.writeQueue, action, tokenSource.token);
|
||||
}
|
||||
|
||||
read<T>(action: () => MaybePromise<T>): Promise<T> {
|
||||
return this.enqueue(this.readQueue, action);
|
||||
}
|
||||
|
||||
private enqueue<T = void>(queue: LockEntry[], action: LockAction<T>, cancellationToken = CancellationToken.None): Promise<T> {
|
||||
const deferred = new Deferred<unknown>();
|
||||
const entry: LockEntry = {
|
||||
action,
|
||||
deferred,
|
||||
cancellationToken
|
||||
};
|
||||
queue.push(entry);
|
||||
this.performNextOperation();
|
||||
return deferred.promise as Promise<T>;
|
||||
}
|
||||
|
||||
private async performNextOperation(): Promise<void> {
|
||||
if (!this.done) {
|
||||
return;
|
||||
}
|
||||
const entries: LockEntry[] = [];
|
||||
if (this.writeQueue.length > 0) {
|
||||
// Just perform the next write action
|
||||
entries.push(this.writeQueue.shift()!);
|
||||
} else if (this.readQueue.length > 0) {
|
||||
// Empty the read queue and perform all actions in parallel
|
||||
entries.push(...this.readQueue.splice(0, this.readQueue.length));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.done = false;
|
||||
await Promise.all(entries.map(async ({ action, deferred, cancellationToken }) => {
|
||||
try {
|
||||
// Move the execution of the action to the next event loop tick via `Promise.resolve()`
|
||||
const result = await Promise.resolve().then(() => action(cancellationToken));
|
||||
deferred.resolve(result);
|
||||
} catch (err) {
|
||||
if (isOperationCancelled(err)) {
|
||||
// If the operation was cancelled, we don't want to reject the promise
|
||||
deferred.resolve(undefined);
|
||||
} else {
|
||||
deferred.reject(err);
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.done = true;
|
||||
this.performNextOperation();
|
||||
}
|
||||
|
||||
cancelWrite(): void {
|
||||
this.previousTokenSource.cancel();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user