First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { URI } from '../utils/uri-utils.js';
import type { NameProvider } from '../references/name-provider.js';
import type { LangiumCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription, ReferenceInfo } from '../syntax-tree.js';
import type { AstNodeLocator } from './ast-node-locator.js';
import type { DocumentSegment, LangiumDocument } from './documents.js';
import { CancellationToken } from '../utils/cancellation.js';
/**
* Language-specific service for creating descriptions of AST nodes to be used for cross-reference resolutions.
*/
export interface AstNodeDescriptionProvider {
/**
* Create a description for the given AST node. This service method is typically used while indexing
* the contents of a document and during scope computation.
*
* @param node An AST node.
* @param name The name to be used to refer to the AST node. By default, this is determined by the
* `NameProvider` service, but alternative names may be provided according to the semantics
* of your language.
* @param document The document containing the AST node. If omitted, it is taken from the root AST node.
*/
createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription;
}
export declare class DefaultAstNodeDescriptionProvider implements AstNodeDescriptionProvider {
protected readonly astNodeLocator: AstNodeLocator;
protected readonly nameProvider: NameProvider;
constructor(services: LangiumCoreServices);
createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription;
}
/**
* Describes a cross-reference within a document or between two documents.
*/
export interface ReferenceDescription {
/** URI of the document that holds a reference */
sourceUri: URI;
/** Path to AstNode that holds a reference */
sourcePath: string;
/** Target document uri */
targetUri: URI;
/** Path to the target AstNode inside the document */
targetPath: string;
/** Segment of the reference text. */
segment: DocumentSegment;
/** Marks a local reference i.e. a cross reference inside a document. */
local?: boolean;
}
/**
* Language-specific service to create descriptions of all cross-references in a document. These are used by the `IndexManager`
* to determine which documents are affected and should be rebuilt when a document is changed.
*/
export interface ReferenceDescriptionProvider {
/**
* Create descriptions of all cross-references found in the given document. These descriptions are
* gathered by the `IndexManager` and stored in the global index so they can be considered when
* a document change is reported by the client.
*
* @param document The document in which to gather cross-references.
* @param cancelToken Indicates when to cancel the current operation.
* @throws `OperationCanceled` if a user action occurs during execution
*/
createDescriptions(document: LangiumDocument, cancelToken?: CancellationToken): Promise<ReferenceDescription[]>;
}
export declare class DefaultReferenceDescriptionProvider implements ReferenceDescriptionProvider {
protected readonly nodeLocator: AstNodeLocator;
constructor(services: LangiumCoreServices);
createDescriptions(document: LangiumDocument, cancelToken?: CancellationToken): Promise<ReferenceDescription[]>;
protected createInfoDescriptions(refInfo: ReferenceInfo): ReferenceDescription[];
}
//# sourceMappingURL=ast-descriptions.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ast-descriptions.d.ts","sourceRoot":"","sources":["../../src/workspace/ast-descriptions.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAO7D;;GAEG;AACH,MAAM,WAAW,0BAA0B;IAEvC;;;;;;;;;OASG;IACH,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,kBAAkB,CAAC;CAE9G;AAED,qBAAa,iCAAkC,YAAW,0BAA0B;IAEhF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IAClD,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;gBAElC,QAAQ,EAAE,mBAAmB;IAKzC,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,kBAAkB;CAsB7G;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,iDAAiD;IACjD,SAAS,EAAE,GAAG,CAAA;IACd,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,SAAS,EAAE,GAAG,CAAA;IACd,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB,qCAAqC;IACrC,OAAO,EAAE,eAAe,CAAA;IACxB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IACzC;;;;;;;;OAQG;IACH,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;CACnH;AAED,qBAAa,mCAAoC,YAAW,4BAA4B;IAEpF,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;gBAEnC,QAAQ,EAAE,mBAAmB;IAInC,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,WAAW,oBAAyB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAc1H,SAAS,CAAC,sBAAsB,CAAC,OAAO,EAAE,aAAa,GAAG,oBAAoB,EAAE;CA4BnF"}
+85
View File
@@ -0,0 +1,85 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { CancellationToken } from '../utils/cancellation.js';
import { isMultiReference, isReference } from '../syntax-tree.js';
import { getDocument, streamAst, streamReferences } from '../utils/ast-utils.js';
import { toDocumentSegment } from '../utils/cst-utils.js';
import { interruptAndCheck } from '../utils/promise-utils.js';
import { UriUtils } from '../utils/uri-utils.js';
export class DefaultAstNodeDescriptionProvider {
constructor(services) {
this.astNodeLocator = services.workspace.AstNodeLocator;
this.nameProvider = services.references.NameProvider;
}
createDescription(node, name, document) {
const doc = document ?? getDocument(node);
name ?? (name = this.nameProvider.getName(node));
const path = this.astNodeLocator.getAstNodePath(node);
if (!name) {
throw new Error(`Node at path ${path} has no name.`);
}
let nameNodeSegment;
const nameSegmentGetter = () => nameNodeSegment ?? (nameNodeSegment = toDocumentSegment(this.nameProvider.getNameNode(node) ?? node.$cstNode));
return {
node,
name,
get nameSegment() {
return nameSegmentGetter();
},
selectionSegment: toDocumentSegment(node.$cstNode),
type: node.$type,
documentUri: doc.uri,
path
};
}
}
export class DefaultReferenceDescriptionProvider {
constructor(services) {
this.nodeLocator = services.workspace.AstNodeLocator;
}
async createDescriptions(document, cancelToken = CancellationToken.None) {
const descr = [];
const rootNode = document.parseResult.value;
for (const astNode of streamAst(rootNode)) {
await interruptAndCheck(cancelToken);
streamReferences(astNode).forEach(refInfo => {
if (!refInfo.reference.error) {
descr.push(...this.createInfoDescriptions(refInfo));
}
});
}
return descr;
}
createInfoDescriptions(refInfo) {
const reference = refInfo.reference;
if (reference.error || !reference.$refNode) {
return [];
}
let items = [];
if (isReference(reference) && reference.$nodeDescription) {
items = [reference.$nodeDescription];
}
else if (isMultiReference(reference)) {
items = reference.items.map(e => e.$nodeDescription).filter(e => e !== undefined);
}
const sourceUri = getDocument(refInfo.container).uri;
const sourcePath = this.nodeLocator.getAstNodePath(refInfo.container);
const descriptions = [];
const segment = toDocumentSegment(reference.$refNode);
for (const item of items) {
descriptions.push({
sourceUri,
sourcePath,
targetUri: item.documentUri,
targetPath: item.path,
segment,
local: UriUtils.equals(item.documentUri, sourceUri)
});
}
return descriptions;
}
}
//# sourceMappingURL=ast-descriptions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ast-descriptions.js","sourceRoot":"","sources":["../../src/workspace/ast-descriptions.ts"],"names":[],"mappings":"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"}
+37
View File
@@ -0,0 +1,37 @@
/******************************************************************************
* 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 { AstNode } from '../syntax-tree.js';
/**
* Language-specific service for locating an `AstNode` in a document.
*/
export interface AstNodeLocator {
/**
* Creates a path represented by a `string` that identifies an `AstNode` inside its document.
* It must be possible to retrieve exactly the same `AstNode` from the document using this path.
*
* @param node The `AstNode` for which to create the path.
* @returns a path represented by a `string` that identifies `node` inside its document.
* @see {@link getAstNode}
*/
getAstNodePath(node: AstNode): string;
/**
* Locates an `AstNode` inside another node by following the given path.
*
* @param node Parent element.
* @param path Describes how to locate the `AstNode` inside the given `node`.
* @returns The `AstNode` located under the given path, or `undefined` if the path cannot be resolved.
* @see {@link getAstNodePath}
*/
getAstNode<T extends AstNode = AstNode>(node: AstNode, path: string): T | undefined;
}
export declare class DefaultAstNodeLocator implements AstNodeLocator {
protected segmentSeparator: string;
protected indexSeparator: string;
getAstNodePath(node: AstNode): string;
protected getPathSegment({ $containerProperty, $containerIndex }: AstNode): string;
getAstNode<T extends AstNode = AstNode>(node: AstNode, path: string): T | undefined;
}
//# sourceMappingURL=ast-node-locator.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ast-node-locator.d.ts","sourceRoot":"","sources":["../../src/workspace/ast-node-locator.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,cAAc;IAE3B;;;;;;;OAOG;IACH,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAEtC;;;;;;;OAOG;IACH,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAEvF;AAED,qBAAa,qBAAsB,YAAW,cAAc;IACxD,SAAS,CAAC,gBAAgB,SAAO;IACjC,SAAS,CAAC,cAAc,SAAO;IAE/B,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAUrC,SAAS,CAAC,cAAc,CAAC,EAAE,kBAAkB,EAAE,eAAe,EAAE,EAAE,OAAO,GAAG,MAAM;IAUlF,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;CAiBtF"}
+46
View File
@@ -0,0 +1,46 @@
/******************************************************************************
* 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 class DefaultAstNodeLocator {
constructor() {
this.segmentSeparator = '/';
this.indexSeparator = '@';
}
getAstNodePath(node) {
if (node.$container) {
const containerPath = this.getAstNodePath(node.$container);
const newSegment = this.getPathSegment(node);
const nodePath = containerPath + this.segmentSeparator + newSegment;
return nodePath;
}
return '';
}
getPathSegment({ $containerProperty, $containerIndex }) {
if (!$containerProperty) {
throw new Error("Missing '$containerProperty' in AST node.");
}
if ($containerIndex !== undefined) {
return $containerProperty + this.indexSeparator + $containerIndex;
}
return $containerProperty;
}
getAstNode(node, path) {
const segments = path.split(this.segmentSeparator);
return segments.reduce((previousValue, currentValue) => {
if (!previousValue || currentValue.length === 0) {
return previousValue;
}
const propertyIndex = currentValue.indexOf(this.indexSeparator);
if (propertyIndex > 0) {
const property = currentValue.substring(0, propertyIndex);
const arrayIndex = parseInt(currentValue.substring(propertyIndex + 1));
const array = previousValue[property];
return array?.[arrayIndex];
}
return previousValue[currentValue];
}, node);
}
}
//# sourceMappingURL=ast-node-locator.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ast-node-locator.js","sourceRoot":"","sources":["../../src/workspace/ast-node-locator.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AA+BhF,MAAM,OAAO,qBAAqB;IAAlC;QACc,qBAAgB,GAAG,GAAG,CAAC;QACvB,mBAAc,GAAG,GAAG,CAAC;IAuCnC,CAAC;IArCG,cAAc,CAAC,IAAa;QACxB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,QAAQ,GAAG,aAAa,GAAG,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC;YACpE,OAAO,QAAQ,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,CAAC;IACd,CAAC;IAES,cAAc,CAAC,EAAE,kBAAkB,EAAE,eAAe,EAAW;QACrE,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,kBAAkB,GAAG,IAAI,CAAC,cAAc,GAAG,eAAe,CAAC;QACtE,CAAC;QACD,OAAO,kBAAkB,CAAC;IAC9B,CAAC;IAED,UAAU,CAA8B,IAAa,EAAE,IAAY;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACnD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE;YACnD,IAAI,CAAC,aAAa,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9C,OAAO,aAAa,CAAC;YACzB,CAAC;YACD,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAChE,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;gBACpB,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;gBAC1D,MAAM,UAAU,GAAG,QAAQ,CAAC,YAAY,CAAC,SAAS,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvE,MAAM,KAAK,GAAI,aAAsD,CAAC,QAAQ,CAAC,CAAC;gBAChF,OAAO,KAAK,EAAE,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC;YACD,OAAQ,aAAoD,CAAC,YAAY,CAAC,CAAC;QAC/E,CAAC,EAAE,IAAI,CAAM,CAAC;IAClB,CAAC;CAEJ"}
+91
View File
@@ -0,0 +1,91 @@
/******************************************************************************
* 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';
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 declare class DefaultConfigurationProvider implements ConfigurationProvider {
protected readonly serviceRegistry: ServiceRegistry;
protected readonly _ready: Deferred<void>;
protected readonly onConfigurationSectionUpdateEmitter: Emitter<ConfigurationSectionUpdate>;
protected settings: Record<string, Record<string, any>>;
protected workspaceConfig: boolean;
constructor(services: LangiumSharedCoreServices);
get ready(): Promise<void>;
initialize(params: InitializeParams): void;
initialized(params: ConfigurationInitializedParams): Promise<void>;
/**
* 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;
protected updateSectionConfiguration(section: string, configuration: any): 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>;
protected toSectionName(languageId: string): string;
get onConfigurationSectionUpdate(): Event<ConfigurationSectionUpdate>;
}
//# sourceMappingURL=configuration.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["../../src/workspace/configuration.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,EACR,iBAAiB,EACjB,4BAA4B,EAC5B,yCAAyC,EACzC,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,iBAAiB,EACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAIrD,MAAM,WAAW,qBAAqB;IAElC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAE3C;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;;;;OAKG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAExE;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAEhE;;OAEG;IACH,4BAA4B,CAAC,QAAQ,EAAE,kCAAkC,GAAG,UAAU,CAAA;CACzF;AAED,MAAM,WAAW,8BAA+B,SAAQ,iBAAiB;IACrE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,yCAAyC,KAAK,IAAI,CAAC;IACvE,kBAAkB,CAAC,EAAE,CAAC,aAAa,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;CAC5E;AAED,MAAM,WAAW,0BAA0B;IACvC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,aAAa,EAAE,GAAG,CAAC;CACtB;AAED,MAAM,MAAM,kCAAkC,GAAG,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC;AAE9F;;GAEG;AACH,qBAAa,4BAA6B,YAAW,qBAAqB;IAEtE,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IACpD,SAAS,CAAC,QAAQ,CAAC,MAAM,iBAAwB;IACjD,SAAS,CAAC,QAAQ,CAAC,mCAAmC,sCAA6C;IACnG,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAM;IAC7D,SAAS,CAAC,eAAe,UAAS;gBAEtB,QAAQ,EAAE,yBAAyB;IAI/C,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzB;IAED,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAIpC,WAAW,CAAC,MAAM,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BxE;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,4BAA4B,GAAG,IAAI;IAU/D,SAAS,CAAC,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAG,IAAI;IAI/E;;;;;MAKE;IACI,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAS7E,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAInD,IAAI,4BAA4B,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAEpE;CACJ"}
+90
View File
@@ -0,0 +1,90 @@
/******************************************************************************
* 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 { Deferred } from '../utils/promise-utils.js';
/**
* Base configuration provider for building up other configuration providers
*/
export class DefaultConfigurationProvider {
constructor(services) {
this._ready = new Deferred();
this.onConfigurationSectionUpdateEmitter = new Emitter();
this.settings = {};
this.workspaceConfig = false;
this.serviceRegistry = services.ServiceRegistry;
}
get ready() {
return this._ready.promise;
}
initialize(params) {
this.workspaceConfig = params.capabilities.workspace?.configuration ?? false;
}
async initialized(params) {
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 => ({
// 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) {
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 });
});
}
updateSectionConfiguration(section, configuration) {
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, configuration) {
await this.ready;
const sectionName = this.toSectionName(language);
if (this.settings[sectionName]) {
return this.settings[sectionName][configuration];
}
}
toSectionName(languageId) {
return `${languageId}`;
}
get onConfigurationSectionUpdate() {
return this.onConfigurationSectionUpdateEmitter.event;
}
}
//# sourceMappingURL=configuration.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../src/workspace/configuration.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAY5C,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAgErD;;GAEG;AACH,MAAM,OAAO,4BAA4B;IAQrC,YAAY,QAAmC;QAL5B,WAAM,GAAG,IAAI,QAAQ,EAAQ,CAAC;QAC9B,wCAAmC,GAAG,IAAI,OAAO,EAA8B,CAAC;QACzF,aAAQ,GAAwC,EAAE,CAAC;QACnD,oBAAe,GAAG,KAAK,CAAC;QAG9B,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC;IACpD,CAAC;IAED,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED,UAAU,CAAC,MAAwB;QAC/B,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,aAAa,IAAI,KAAK,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAsC;QACpD,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAClB,mGAAmG;gBACnG,yGAAyG;gBAEzG,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;gBAC3C,MAAM,CAAC,QAAQ,CAAC;oBACZ,oDAAoD;oBACpD,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;iBACvF,CAAC,CAAC;YACP,CAAC;YAED,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAC5B,6GAA6G;gBAC7G,yGAAyG;gBACzG,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAmB;oBAC3E,oDAAoD;oBACpD,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC;iBAChE,CAAA,CAAC,CAAC;gBAEH,mDAAmD;gBACnD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC;gBAChE,cAAc,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;oBACjC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,OAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;gBACjE,CAAC,CAAC,CAAC;YACP,CAAC;QACL,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,MAAoC;QACpD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAClE,OAAO;QACX,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,EAAE,EAAE;YACjE,IAAI,CAAC,0BAA0B,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YACxD,IAAI,CAAC,mCAAmC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;IACP,CAAC;IAES,0BAA0B,CAAC,OAAe,EAAE,aAAkB;QACpE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC;IAC3C,CAAC;IAED;;;;;MAKE;IACF,KAAK,CAAC,gBAAgB,CAAC,QAAgB,EAAE,aAAqB;QAC1D,MAAM,IAAI,CAAC,KAAK,CAAC;QAEjB,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,aAAa,CAAC,CAAC;QACrD,CAAC;IACL,CAAC;IAES,aAAa,CAAC,UAAkB;QACtC,OAAO,GAAG,UAAU,EAAE,CAAC;IAC3B,CAAC;IAED,IAAI,4BAA4B;QAC5B,OAAO,IAAI,CAAC,mCAAmC,CAAC,KAAK,CAAC;IAC1D,CAAC;CACJ"}
+218
View File
@@ -0,0 +1,218 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { CancellationToken } from '../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 { 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 declare class DefaultDocumentBuilder implements DocumentBuilder {
updateBuildOptions: BuildOptions;
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: MultiMap<DocumentState, DocumentBuildListener>;
protected readonly documentPhaseListeners: MultiMap<DocumentState, DocumentPhaseListener>;
protected readonly buildState: Map<string, DocumentBuildState>;
protected readonly documentBuildWaiters: Map<string, Deferred<void>>;
protected currentState: DocumentState;
constructor(services: LangiumSharedCoreServices);
build<T extends AstNode>(documents: Array<LangiumDocument<T>>, options?: BuildOptions, cancelToken?: CancellationToken): Promise<void>;
update(changed: URI[], deleted: URI[], cancelToken?: CancellationToken): Promise<void>;
protected resultsAreIncomplete(document: LangiumDocument, options: BuildOptions | undefined): boolean;
protected findMissingValidationCategories(document: LangiumDocument, options: BuildOptions | undefined): ValidationCategory[];
protected findChangedUris(changed: URI): Promise<URI[]>;
protected emitUpdate(changed: URI[], deleted: URI[]): Promise<void>;
/**
* 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[];
private hasTextDocument;
/**
* Check whether the given document should be relinked after changes were found in the given URIs.
*/
protected shouldRelink(document: LangiumDocument, changedUris: Set<string>): boolean;
onUpdate(callback: DocumentUpdateListener): Disposable;
resetToState<T extends AstNode>(document: LangiumDocument<T>, state: DocumentState): void;
protected cleanUpDeleted<T extends AstNode>(document: LangiumDocument<T>): void;
/**
* 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 buildDocuments(documents: LangiumDocument[], options: BuildOptions, cancelToken: CancellationToken): Promise<void>;
protected markAsCompleted(document: LangiumDocument): void;
/**
* 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;
/**
* 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 runCancelable(documents: LangiumDocument[], targetState: DocumentState, cancelToken: CancellationToken, callback: (document: LangiumDocument) => MaybePromise<unknown>): Promise<void>;
onBuildPhase(targetState: DocumentState, callback: DocumentBuildListener): Disposable;
onDocumentPhase(targetState: DocumentState, callback: DocumentPhaseListener): Disposable;
waitUntil(state: DocumentState, cancelToken?: CancellationToken): Promise<void>;
waitUntil(state: DocumentState, uri?: URI, cancelToken?: CancellationToken): Promise<URI>;
protected awaitDocumentState(state: DocumentState, uri: URI, cancelToken: CancellationToken): Promise<URI>;
protected awaitBuilderState(state: DocumentState, cancelToken: CancellationToken): Promise<void>;
protected notifyDocumentPhase(document: LangiumDocument, state: DocumentState, cancelToken: CancellationToken): Promise<void>;
protected notifyBuildPhase(documents: LangiumDocument[], state: DocumentState, cancelToken: CancellationToken): Promise<void>;
/**
* 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;
/**
* 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;
/**
* 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 validate(document: LangiumDocument, cancelToken: CancellationToken): Promise<void>;
protected getBuildOptions(document: LangiumDocument): BuildOptions;
}
//# sourceMappingURL=document-builder.d.ts.map
File diff suppressed because one or more lines are too long
+503
View File
@@ -0,0 +1,503 @@
/******************************************************************************
* 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 { MultiMap } from '../utils/collections.js';
import { OperationCancelled, interruptAndCheck, isOperationCancelled } from '../utils/promise-utils.js';
import { stream } from '../utils/stream.js';
import { UriUtils } from '../utils/uri-utils.js';
import { DocumentState } from './documents.js';
export class DefaultDocumentBuilder {
constructor(services) {
this.updateBuildOptions = {
// Default: run only the built-in validation checks and those in the _fast_ category (includes those without category)
validation: {
categories: ['built-in', 'fast']
}
};
this.updateListeners = [];
this.buildPhaseListeners = new MultiMap();
this.documentPhaseListeners = new MultiMap();
this.buildState = new Map();
this.documentBuildWaiters = new Map();
this.currentState = DocumentState.Changed;
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(documents, options = {}, cancelToken = CancellationToken.None) {
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, deleted, cancelToken = CancellationToken.None) {
this.currentState = DocumentState.Changed;
// Remove all metadata of documents that are reported as deleted
const deletedUris = [];
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);
}
resultsAreIncomplete(document, options) {
return this.findMissingValidationCategories(document, options).length >= 1;
}
findMissingValidationCategories(document, options) {
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();
}
async findChangedUris(changed) {
// 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 [];
}
async emitUpdate(changed, deleted) {
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.
*/
sortDocuments(documents) {
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;
}
hasTextDocument(doc) {
return Boolean(this.textDocuments?.get(doc.uri));
}
/**
* Check whether the given document should be relinked after changes were found in the given URIs.
*/
shouldRelink(document, changedUris) {
// 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) {
this.updateListeners.push(callback);
return Disposable.create(() => {
const index = this.updateListeners.indexOf(callback);
if (index >= 0) {
this.updateListeners.splice(index, 1);
}
});
}
resetToState(document, state) {
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;
}
}
cleanUpDeleted(document) {
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.
*/
async buildDocuments(documents, options, cancelToken) {
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);
});
}
markAsCompleted(document) {
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
*/
prepareBuild(documents, options) {
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`.
*/
async runCancelable(documents, targetState, cancelToken, callback) {
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, callback) {
this.buildPhaseListeners.add(targetState, callback);
return Disposable.create(() => {
this.buildPhaseListeners.delete(targetState, callback);
});
}
onDocumentPhase(targetState, callback) {
this.documentPhaseListeners.add(targetState, callback);
return Disposable.create(() => {
this.documentPhaseListeners.delete(targetState, callback);
});
}
waitUntil(state, uriOrToken, cancelToken) {
let uri = undefined;
if (uriOrToken && 'path' in uriOrToken) {
uri = uriOrToken;
}
else {
cancelToken = uriOrToken;
}
cancelToken ?? (cancelToken = CancellationToken.None);
if (uri) {
return this.awaitDocumentState(state, uri, cancelToken);
}
else {
return this.awaitBuilderState(state, cancelToken);
}
}
awaitDocumentState(state, uri, cancelToken) {
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);
});
});
}
awaitBuilderState(state, cancelToken) {
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);
});
});
}
async notifyDocumentPhase(document, state, cancelToken) {
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;
}
}
}
}
async notifyBuildPhase(documents, state, cancelToken) {
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.
*/
shouldLink(document) {
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.
*/
shouldValidate(document) {
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.
*/
async validate(document, cancelToken) {
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 ?? (state.result = {});
if (state.result.validationChecks) {
state.result.validationChecks = stream(state.result.validationChecks).concat(validationOptions.categories).distinct().toArray();
}
else {
state.result.validationChecks = [...validationOptions.categories];
}
}
}
getBuildOptions(document) {
return this.buildState.get(document.uri.toString())?.options ?? {};
}
}
//# sourceMappingURL=document-builder.js.map
File diff suppressed because one or more lines are too long
+282
View File
@@ -0,0 +1,282 @@
/******************************************************************************
* 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.
******************************************************************************/
/**
* Re-export 'TextDocument' from 'vscode-languageserver-textdocument' for convenience,
* including both type _and_ symbol (namespace), as we here and there also refer to the symbol,
* the overhead is very small, just a few kilobytes.
* Everything else of that package (at the time contributing) is also defined
* in 'vscode-languageserver-protocol' or 'vscode-languageserver-types'.
*/
export { TextDocument } from 'vscode-languageserver-textdocument';
import type { Diagnostic, Range } from 'vscode-languageserver-types';
import type { FileSystemProvider } from './file-system-provider.js';
import type { ParseResult, ParserOptions } from '../parser/langium-parser.js';
import type { ServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription, MultiReference, Mutable, Reference } from '../syntax-tree.js';
import type { Stream } from '../utils/stream.js';
import { TextDocument } from './documents.js';
import { CancellationToken } from '../utils/cancellation.js';
import { URI, UriTrie } from '../utils/uri-utils.js';
/**
* A Langium document holds the parse result (AST and CST) and any additional state that is derived
* from the AST, e.g. the result of scope precomputation.
*/
export interface LangiumDocument<T extends AstNode = AstNode> {
/** The Uniform Resource Identifier (URI) of the document */
readonly uri: URI;
/** The text document used to convert between offsets and positions */
readonly textDocument: TextDocument;
/** The current state of the document */
state: DocumentState;
/** The parse result holds the Abstract Syntax Tree (AST) and potentially also parser / lexer errors */
parseResult: ParseResult<T>;
/** Result of the scope precomputation phase */
localSymbols?: LocalSymbols;
/** An array of all cross-references found in the AST while linking */
references: Array<Reference | MultiReference>;
/** Result of the validation phase */
diagnostics?: Diagnostic[];
}
/**
* A document is subject to several phases that are run in predefined order. Any state value implies that
* smaller state values are finished as well.
*/
export declare enum DocumentState {
/**
* The text content has changed and needs to be parsed again. The AST held by this outdated
* document instance is no longer valid.
*/
Changed = 0,
/**
* An AST has been created from the text content. The document structure can be traversed,
* but cross-references cannot be resolved yet. If necessary, the structure can be manipulated
* at this stage as a preprocessing step.
*/
Parsed = 1,
/**
* The `IndexManager` service has processed AST nodes of this document. This means the
* exported symbols are available in the global scope and can be resolved from other documents.
*/
IndexedContent = 2,
/**
* The `ScopeComputation` service has processed this document. This means the document's locally accessible
* symbols are captured in a `DocumentSymbols` table and can be looked up by the `ScopeProvider` service.
* Once a document has reached this state, you may follow every reference - it will lazily
* resolve its `ref` property and yield either the target AST node or `undefined` in case
* the target is not in scope.
*/
ComputedScopes = 3,
/**
* The `Linker` service has processed this document. All outgoing references have been
* resolved or marked as erroneous.
*/
Linked = 4,
/**
* The `IndexManager` service has processed AST node references of this document. This is
* necessary to determine which documents are affected by a change in one of the workspace
* documents.
*/
IndexedReferences = 5,
/**
* The `DocumentValidator` service has processed this document. The language server listens
* to the results of this phase and sends diagnostics to the client.
*/
Validated = 6
}
/**
* Result of the scope pre-computation phase performed by the `ScopeComputation` service.
* It maps AST nodes of a document to their corresponding sets of symbols that are accessible
* by those nodes/subtrees, provided any symbols corresponding specifically to those nodes/subtrees exist.
* The sets of symbols are assumed to be un-ordered. Hence, no assumptions about the order of
* symbols in the sets should be made. The default `ScopeComputation` implementation uses an
* instance of `MultiMap<AstNode, AstNodeDescription>`, which conforms to this interface.
*/
export interface LocalSymbols {
has(node: AstNode): boolean;
getStream(key: AstNode): Stream<AstNodeDescription>;
}
export interface DocumentSegment {
readonly range: Range;
readonly offset: number;
readonly length: number;
readonly end: number;
}
/**
* Surrogate definition of the `TextDocuments` interface from the `vscode-languageserver` package.
* No implementation object is expected to be offered by `LangiumCoreServices`, but only by `LangiumLSPServices`.
*/
export type TextDocumentProvider = {
get(uri: string | URI): TextDocument | undefined;
};
/**
* Shared service for creating `LangiumDocument` instances.
*
* Register a custom implementation if special (additional) behavior is required for your language(s).
* Note: If you specialize {@link fromString} or {@link fromTextDocument} you probably might want to
* specialize {@link update}, too!
*/
export interface LangiumDocumentFactory {
/**
* Create a Langium document from a `TextDocument` (usually associated with a file).
*/
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, options?: ParserOptions): LangiumDocument<T>;
/**
* Create a Langium document from a `TextDocument` asynchronously. This action can be cancelled if a cancellable parser implementation has been provided.
*/
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri: URI | undefined, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
/**
* Create an Langium document from an in-memory string.
*/
fromString<T extends AstNode = AstNode>(text: string, uri: URI, options?: ParserOptions): LangiumDocument<T>;
/**
* Create a Langium document from an in-memory string asynchronously. This action can be cancelled if a cancellable parser implementation has been provided.
*/
fromString<T extends AstNode = AstNode>(text: string, uri: URI, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
/**
* Create an Langium document from a model that has been constructed in memory.
*/
fromModel<T extends AstNode = AstNode>(model: T, uri: URI): LangiumDocument<T>;
/**
* Create an Langium document from a specified `URI`. The factory will use the `FileSystemAccess` service to read the file.
*/
fromUri<T extends AstNode = AstNode>(uri: URI, cancellationToken?: CancellationToken): Promise<LangiumDocument<T>>;
/**
* Update the given document after changes in the corresponding textual representation.
* Method is called by the document builder after it has been requested to build an existing
* document and the document's state is {@link DocumentState.Changed}.
* The text parsing is expected to be done the same way as in {@link fromTextDocument}
* and {@link fromString}.
*/
update<T extends AstNode = AstNode>(document: LangiumDocument<T>, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
}
export declare class DefaultLangiumDocumentFactory implements LangiumDocumentFactory {
protected readonly serviceRegistry: ServiceRegistry;
protected readonly textDocuments?: TextDocumentProvider;
protected readonly fileSystemProvider: FileSystemProvider;
constructor(services: LangiumSharedCoreServices);
fromUri<T extends AstNode = AstNode>(uri: URI, cancellationToken?: CancellationToken): Promise<LangiumDocument<T>>;
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, options?: ParserOptions): LangiumDocument<T>;
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri: URI | undefined, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
fromString<T extends AstNode = AstNode>(text: string, uri: URI, options?: ParserOptions): LangiumDocument<T>;
fromString<T extends AstNode = AstNode>(text: string, uri: URI, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
fromModel<T extends AstNode = AstNode>(model: T, uri: URI): LangiumDocument<T>;
protected create<T extends AstNode = AstNode>(uri: URI, content: string | TextDocument | {
$model: T;
}, options?: ParserOptions): LangiumDocument<T>;
protected createAsync<T extends AstNode = AstNode>(uri: URI, content: string | TextDocument, cancelToken: CancellationToken): Promise<LangiumDocument<T>>;
/**
* Create a LangiumDocument from a given parse result.
*
* A TextDocument is created on demand if it is not provided as argument here. Usually this
* should not be necessary because the main purpose of the TextDocument is to convert between
* text ranges and offsets, which is done solely in LSP request handling.
*
* With the introduction of {@link update} below this method is supposed to be mainly called
* during workspace initialization and on addition/recognition of new files, while changes in
* existing documents are processed via {@link update}.
*/
protected createLangiumDocument<T extends AstNode = AstNode>(parseResult: ParseResult<T>, uri: URI, textDocument?: TextDocument, text?: string): LangiumDocument<T>;
update<T extends AstNode = AstNode>(document: Mutable<LangiumDocument<T>>, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
protected parse<T extends AstNode>(uri: URI, text: string, options?: ParserOptions): ParseResult<T>;
protected parseAsync<T extends AstNode>(uri: URI, text: string, cancellationToken: CancellationToken): Promise<ParseResult<T>>;
protected createTextDocumentGetter(uri: URI, text?: string): () => TextDocument;
}
/**
* Shared service for managing Langium documents.
*/
export interface LangiumDocuments {
/**
* A stream of all documents managed under this service.
*/
readonly all: Stream<LangiumDocument>;
/**
* Manage a new document under this service.
* @throws an error if a document with the same URI is already present.
*/
addDocument(document: LangiumDocument): void;
/**
* Retrieve the document with the given URI, if present. Otherwise returns `undefined`.
*/
getDocument(uri: URI): LangiumDocument | undefined;
/**
* If the given URI is a directory, all documents within this directory are retrieved.
* If it is a file, just that single document is retrieved.
*/
getDocuments(folder: URI): LangiumDocument[];
/**
* Retrieve the document with the given URI. If not present, a new one will be created using the file system access.
* The new document will be added to the list of documents managed under this service.
*/
getOrCreateDocument(uri: URI, cancellationToken?: CancellationToken): Promise<LangiumDocument>;
/**
* Creates a new document with the given URI and text content.
* The new document is automatically added to this service and can be retrieved using {@link getDocument}.
*
* @throws an error if a document with the same URI is already present.
*/
createDocument(uri: URI, text: string): LangiumDocument;
/**
* Creates a new document with the given URI and text content asynchronously.
* The process can be interrupted with a cancellation token.
* The new document is automatically added to this service and can be retrieved using {@link getDocument}.
*
* @throws an error if a document with the same URI is already present.
*/
createDocument(uri: URI, text: string, cancellationToken: CancellationToken): Promise<LangiumDocument>;
/**
* Flag the document with the given URI as `Changed`, if present, meaning that its content
* is no longer valid. The content (parseResult) stays untouched, while internal data may
* be dropped to reduce memory footprint.
*
* @returns the affected {@link LangiumDocument} if existing for convenience
*
* @deprecated Since 4.2 use `DocumentBuilder.resetToState(DocumentState.Changed)` instead
*/
invalidateDocument(uri: URI): LangiumDocument | undefined;
/**
* Returns `true` if a document with the given URI is managed under this service.
*/
hasDocument(uri: URI): boolean;
/**
* Remove the document with the given URI, if present, and mark it as `Changed`, meaning
* that its content is no longer valid. The next call to `getOrCreateDocument` with the same
* URI will create a new document instance.
*
* @returns the affected {@link LangiumDocument} if existing for convenience
*/
deleteDocument(uri: URI): LangiumDocument | undefined;
/**
* If the given URI is a directory, remove all documents within this directory.
* If it is a file, just remove that single document from the documents.
*
* @returns the affected {@link LangiumDocument}s if existing for convenience
*/
deleteDocuments(uri: URI): LangiumDocument[];
}
export declare class DefaultLangiumDocuments implements LangiumDocuments {
protected readonly services: LangiumSharedCoreServices;
protected readonly langiumDocumentFactory: LangiumDocumentFactory;
private documentBuilder;
protected readonly documentTrie: UriTrie<LangiumDocument<AstNode>>;
constructor(services: LangiumSharedCoreServices);
get all(): Stream<LangiumDocument>;
addDocument(document: LangiumDocument): void;
getDocument(uri: URI): LangiumDocument | undefined;
getDocuments(folder: URI): LangiumDocument[];
getOrCreateDocument(uri: URI, cancellationToken?: CancellationToken): Promise<LangiumDocument>;
createDocument(uri: URI, text: string): LangiumDocument;
createDocument(uri: URI, text: string, cancellationToken: CancellationToken): Promise<LangiumDocument>;
hasDocument(uri: URI): boolean;
/**
* @deprecated Since 4.2 use `DocumentBuilder.resetToState(DocumentState.Changed)` instead
* TODO remove this for the next major release
*/
invalidateDocument(uri: URI): LangiumDocument | undefined;
deleteDocument(uri: URI): LangiumDocument | undefined;
deleteDocuments(folder: URI): LangiumDocument[];
}
//# sourceMappingURL=documents.d.ts.map
File diff suppressed because one or more lines are too long
+278
View File
@@ -0,0 +1,278 @@
/******************************************************************************
* 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.
******************************************************************************/
/**
* Re-export 'TextDocument' from 'vscode-languageserver-textdocument' for convenience,
* including both type _and_ symbol (namespace), as we here and there also refer to the symbol,
* the overhead is very small, just a few kilobytes.
* Everything else of that package (at the time contributing) is also defined
* in 'vscode-languageserver-protocol' or 'vscode-languageserver-types'.
*/
export { TextDocument } from 'vscode-languageserver-textdocument';
import { TextDocument } from './documents.js';
import { CancellationToken } from '../utils/cancellation.js';
import { stream } from '../utils/stream.js';
import { URI, UriTrie } from '../utils/uri-utils.js';
/**
* A document is subject to several phases that are run in predefined order. Any state value implies that
* smaller state values are finished as well.
*/
export var DocumentState;
(function (DocumentState) {
/**
* The text content has changed and needs to be parsed again. The AST held by this outdated
* document instance is no longer valid.
*/
DocumentState[DocumentState["Changed"] = 0] = "Changed";
/**
* An AST has been created from the text content. The document structure can be traversed,
* but cross-references cannot be resolved yet. If necessary, the structure can be manipulated
* at this stage as a preprocessing step.
*/
DocumentState[DocumentState["Parsed"] = 1] = "Parsed";
/**
* The `IndexManager` service has processed AST nodes of this document. This means the
* exported symbols are available in the global scope and can be resolved from other documents.
*/
DocumentState[DocumentState["IndexedContent"] = 2] = "IndexedContent";
/**
* The `ScopeComputation` service has processed this document. This means the document's locally accessible
* symbols are captured in a `DocumentSymbols` table and can be looked up by the `ScopeProvider` service.
* Once a document has reached this state, you may follow every reference - it will lazily
* resolve its `ref` property and yield either the target AST node or `undefined` in case
* the target is not in scope.
*/
DocumentState[DocumentState["ComputedScopes"] = 3] = "ComputedScopes";
/**
* The `Linker` service has processed this document. All outgoing references have been
* resolved or marked as erroneous.
*/
DocumentState[DocumentState["Linked"] = 4] = "Linked";
/**
* The `IndexManager` service has processed AST node references of this document. This is
* necessary to determine which documents are affected by a change in one of the workspace
* documents.
*/
DocumentState[DocumentState["IndexedReferences"] = 5] = "IndexedReferences";
/**
* The `DocumentValidator` service has processed this document. The language server listens
* to the results of this phase and sends diagnostics to the client.
*/
DocumentState[DocumentState["Validated"] = 6] = "Validated";
})(DocumentState || (DocumentState = {}));
export class DefaultLangiumDocumentFactory {
constructor(services) {
this.serviceRegistry = services.ServiceRegistry;
this.textDocuments = services.workspace.TextDocuments;
this.fileSystemProvider = services.workspace.FileSystemProvider;
}
async fromUri(uri, cancellationToken = CancellationToken.None) {
const content = await this.fileSystemProvider.readFile(uri);
return this.createAsync(uri, content, cancellationToken);
}
fromTextDocument(textDocument, uri, token) {
uri = uri ?? URI.parse(textDocument.uri);
if (CancellationToken.is(token)) {
return this.createAsync(uri, textDocument, token);
}
else {
return this.create(uri, textDocument, token);
}
}
fromString(text, uri, token) {
if (CancellationToken.is(token)) {
return this.createAsync(uri, text, token);
}
else {
return this.create(uri, text, token);
}
}
fromModel(model, uri) {
return this.create(uri, { $model: model });
}
create(uri, content, options) {
if (typeof content === 'string') {
const parseResult = this.parse(uri, content, options);
return this.createLangiumDocument(parseResult, uri, undefined, content);
}
else if ('$model' in content) {
const parseResult = { value: content.$model, parserErrors: [], lexerErrors: [] };
return this.createLangiumDocument(parseResult, uri);
}
else {
const parseResult = this.parse(uri, content.getText(), options);
return this.createLangiumDocument(parseResult, uri, content);
}
}
async createAsync(uri, content, cancelToken) {
if (typeof content === 'string') {
const parseResult = await this.parseAsync(uri, content, cancelToken);
return this.createLangiumDocument(parseResult, uri, undefined, content);
}
else {
const parseResult = await this.parseAsync(uri, content.getText(), cancelToken);
return this.createLangiumDocument(parseResult, uri, content);
}
}
/**
* Create a LangiumDocument from a given parse result.
*
* A TextDocument is created on demand if it is not provided as argument here. Usually this
* should not be necessary because the main purpose of the TextDocument is to convert between
* text ranges and offsets, which is done solely in LSP request handling.
*
* With the introduction of {@link update} below this method is supposed to be mainly called
* during workspace initialization and on addition/recognition of new files, while changes in
* existing documents are processed via {@link update}.
*/
createLangiumDocument(parseResult, uri, textDocument, text) {
let document;
if (textDocument) {
document = {
parseResult,
uri,
state: DocumentState.Parsed,
references: [],
textDocument
};
}
else {
const textDocumentGetter = this.createTextDocumentGetter(uri, text);
document = {
parseResult,
uri,
state: DocumentState.Parsed,
references: [],
get textDocument() {
return textDocumentGetter();
}
};
}
parseResult.value.$document = document;
return document;
}
async update(document, cancellationToken) {
// The CST full text property contains the original text that was used to create the AST.
const oldText = document.parseResult.value.$cstNode?.root.fullText;
const textDocument = this.textDocuments?.get(document.uri.toString());
const text = textDocument ? textDocument.getText() : await this.fileSystemProvider.readFile(document.uri);
if (textDocument) {
Object.defineProperty(document, 'textDocument', {
value: textDocument
});
}
else {
const textDocumentGetter = this.createTextDocumentGetter(document.uri, text);
Object.defineProperty(document, 'textDocument', {
get: textDocumentGetter
});
}
// Some of these documents can be pretty large, so parsing them again can be quite expensive.
// Therefore, we only parse if the text has actually changed.
if (oldText !== text) {
document.parseResult = await this.parseAsync(document.uri, text, cancellationToken);
document.parseResult.value.$document = document;
}
document.state = DocumentState.Parsed;
return document;
}
parse(uri, text, options) {
const services = this.serviceRegistry.getServices(uri);
return services.parser.LangiumParser.parse(text, options);
}
parseAsync(uri, text, cancellationToken) {
const services = this.serviceRegistry.getServices(uri);
return services.parser.AsyncParser.parse(text, cancellationToken);
}
createTextDocumentGetter(uri, text) {
const serviceRegistry = this.serviceRegistry;
let textDoc = undefined;
return () => {
return textDoc ?? (textDoc = TextDocument.create(uri.toString(), serviceRegistry.getServices(uri).LanguageMetaData.languageId, 0, text ?? ''));
};
}
}
export class DefaultLangiumDocuments {
constructor(services) {
this.documentTrie = new UriTrie();
this.services = services;
this.langiumDocumentFactory = services.workspace.LangiumDocumentFactory;
this.documentBuilder = () => services.workspace.DocumentBuilder;
}
get all() {
return stream(this.documentTrie.all());
}
addDocument(document) {
const uriString = document.uri.toString();
if (this.documentTrie.has(uriString)) {
throw new Error(`A document with the URI '${uriString}' is already present.`);
}
this.documentTrie.insert(uriString, document);
}
getDocument(uri) {
const uriString = uri.toString();
return this.documentTrie.find(uriString);
}
getDocuments(folder) {
const uriString = folder.toString();
return this.documentTrie.findAll(uriString);
}
async getOrCreateDocument(uri, cancellationToken) {
let document = this.getDocument(uri);
if (document) {
return document;
}
document = await this.langiumDocumentFactory.fromUri(uri, cancellationToken);
this.addDocument(document);
return document;
}
createDocument(uri, text, cancellationToken) {
if (cancellationToken) {
return this.langiumDocumentFactory.fromString(text, uri, cancellationToken).then(document => {
this.addDocument(document);
return document;
});
}
else {
const document = this.langiumDocumentFactory.fromString(text, uri);
this.addDocument(document);
return document;
}
}
hasDocument(uri) {
return this.documentTrie.has(uri.toString());
}
/**
* @deprecated Since 4.2 use `DocumentBuilder.resetToState(DocumentState.Changed)` instead
* TODO remove this for the next major release
*/
invalidateDocument(uri) {
const uriString = uri.toString();
const langiumDoc = this.documentTrie.find(uriString);
if (langiumDoc) {
this.documentBuilder().resetToState(langiumDoc, DocumentState.Changed);
}
return langiumDoc;
}
deleteDocument(uri) {
const uriString = uri.toString();
const langiumDoc = this.documentTrie.find(uriString);
if (langiumDoc) {
langiumDoc.state = DocumentState.Changed;
this.documentTrie.delete(uriString);
}
return langiumDoc;
}
deleteDocuments(folder) {
const uriString = folder.toString();
const langiumDocs = this.documentTrie.findAll(uriString);
for (const langiumDoc of langiumDocs) {
langiumDoc.state = DocumentState.Changed;
}
this.documentTrie.delete(uriString);
return langiumDocs;
}
}
//# sourceMappingURL=documents.js.map
File diff suppressed because one or more lines are too long
+85
View File
@@ -0,0 +1,85 @@
/******************************************************************************
* 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 declare class EmptyFileSystemProvider implements FileSystemProvider {
stat(_uri: URI): Promise<FileSystemNode>;
statSync(_uri: URI): FileSystemNode;
exists(): Promise<boolean>;
existsSync(): boolean;
readBinary(): Promise<Uint8Array>;
readBinarySync(): Uint8Array;
readFile(): Promise<string>;
readFileSync(): string;
readDirectory(): Promise<FileSystemNode[]>;
readDirectorySync(): FileSystemNode[];
}
export declare const EmptyFileSystem: {
fileSystemProvider: () => EmptyFileSystemProvider;
};
//# sourceMappingURL=file-system-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"file-system-provider.d.ts","sourceRoot":"","sources":["../../src/workspace/file-system-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;CACrB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC;IAC9B;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC;IAC/B;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACnD;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,EAAE,CAAC;CACjD;AAED,qBAAa,uBAAwB,YAAW,kBAAkB;IAE9D,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,cAAc,CAAC;IAIxC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,cAAc;IAG7B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC,UAAU,IAAI,OAAO;IAIrB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAIjC,cAAc,IAAI,UAAU;IAI5B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAI3B,YAAY,IAAI,MAAM;IAIhB,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAIhD,iBAAiB,IAAI,cAAc,EAAE;CAIxC;AAED,eAAO,MAAM,eAAe;;CAE3B,CAAC"}
+41
View File
@@ -0,0 +1,41 @@
/******************************************************************************
* 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.
******************************************************************************/
export class EmptyFileSystemProvider {
stat(_uri) {
throw new Error('No file system is available.');
}
statSync(_uri) {
throw new Error('No file system is available.');
}
async exists() {
return false;
}
existsSync() {
return false;
}
readBinary() {
throw new Error('No file system is available.');
}
readBinarySync() {
throw new Error('No file system is available.');
}
readFile() {
throw new Error('No file system is available.');
}
readFileSync() {
throw new Error('No file system is available.');
}
async readDirectory() {
return [];
}
readDirectorySync() {
return [];
}
}
export const EmptyFileSystem = {
fileSystemProvider: () => new EmptyFileSystemProvider()
};
//# sourceMappingURL=file-system-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"file-system-provider.js","sourceRoot":"","sources":["../../src/workspace/file-system-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAsEhF,MAAM,OAAO,uBAAuB;IAEhC,IAAI,CAAC,IAAS;QACV,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,QAAQ,CAAC,IAAS;QACd,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IACD,KAAK,CAAC,MAAM;QACR,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,UAAU;QACN,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,UAAU;QACN,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,cAAc;QACV,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,QAAQ;QACJ,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,YAAY;QACR,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,aAAa;QACf,OAAO,EAAE,CAAC;IACd,CAAC;IAED,iBAAiB;QACb,OAAO,EAAE,CAAC;IACd,CAAC;CAEJ;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;IAC3B,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,uBAAuB,EAAE;CAC1D,CAAC"}
+110
View File
@@ -0,0 +1,110 @@
/******************************************************************************
* 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 { ContextCache } from '../utils/caching.js';
import { CancellationToken } from '../utils/cancellation.js';
import type { Stream } from '../utils/stream.js';
import type { URI } 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 declare 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: Map<string, AstNodeDescription[]>;
/**
* This is a cache for the `allElements()` method.
* It caches the descriptions from `symbolIndex` grouped by types.
*/
protected readonly symbolByTypeIndex: ContextCache<string, string, AstNodeDescription[], string>;
/**
* 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: Map<string, ReferenceDescription[]>;
constructor(services: LangiumSharedCoreServices);
findAllReferences(targetNode: AstNode, astNodePath: string): Stream<ReferenceDescription>;
allElements(nodeType?: string, uris?: Set<string>): Stream<AstNodeDescription>;
protected getFileDescriptions(uri: string, nodeType?: string): AstNodeDescription[];
remove(uri: URI): void;
removeContent(uri: URI): void;
removeReferences(uri: URI): void;
updateContent(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
updateReferences(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
isAffected(document: LangiumDocument, changedUris: Set<string>): boolean;
}
//# sourceMappingURL=index-manager.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index-manager.d.ts","sourceRoot":"","sources":["../../src/workspace/index-manager.ts"],"names":[],"mappings":"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"}
+97
View File
@@ -0,0 +1,97 @@
/******************************************************************************
* 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 { getDocument } from '../utils/ast-utils.js';
import { ContextCache } from '../utils/caching.js';
import { CancellationToken } from '../utils/cancellation.js';
import { stream } from '../utils/stream.js';
import { UriUtils } from '../utils/uri-utils.js';
export class DefaultIndexManager {
constructor(services) {
/**
* 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.
*/
this.symbolIndex = new Map();
/**
* This is a cache for the `allElements()` method.
* It caches the descriptions from `symbolIndex` grouped by types.
*/
this.symbolByTypeIndex = new ContextCache();
/**
* 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.
*/
this.referenceIndex = new Map();
this.documents = services.workspace.LangiumDocuments;
this.serviceRegistry = services.ServiceRegistry;
this.astReflection = services.AstReflection;
}
findAllReferences(targetNode, astNodePath) {
const targetDocUri = getDocument(targetNode).uri;
const result = [];
this.referenceIndex.forEach(docRefs => {
docRefs.forEach(refDescr => {
if (UriUtils.equals(refDescr.targetUri, targetDocUri) && refDescr.targetPath === astNodePath) {
result.push(refDescr);
}
});
});
return stream(result);
}
allElements(nodeType, uris) {
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();
}
getFileDescriptions(uri, nodeType) {
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) {
this.removeContent(uri);
this.removeReferences(uri);
}
removeContent(uri) {
const uriString = uri.toString();
this.symbolIndex.delete(uriString);
this.symbolByTypeIndex.clear(uriString);
}
removeReferences(uri) {
const uriString = uri.toString();
this.referenceIndex.delete(uriString);
}
async updateContent(document, cancelToken = CancellationToken.None) {
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, cancelToken = CancellationToken.None) {
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, changedUris) {
const references = this.referenceIndex.get(document.uri.toString());
if (!references) {
return false;
}
return references.some(ref => !ref.local && changedUris.has(ref.targetUri.toString()));
}
}
//# sourceMappingURL=index-manager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index-manager.js","sourceRoot":"","sources":["../../src/workspace/index-manager.ts"],"names":[],"mappings":"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"}
+16
View File
@@ -0,0 +1,16 @@
/******************************************************************************
* 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 './ast-descriptions.js';
export * from './ast-node-locator.js';
export * from './configuration.js';
export * from './document-builder.js';
export * from './documents.js';
export * from './file-system-provider.js';
export * from './index-manager.js';
export * from './workspace-lock.js';
export * from './workspace-manager.js';
export * from './profiler.js';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC"}
+16
View File
@@ -0,0 +1,16 @@
/******************************************************************************
* 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 './ast-descriptions.js';
export * from './ast-node-locator.js';
export * from './configuration.js';
export * from './document-builder.js';
export * from './documents.js';
export * from './file-system-provider.js';
export * from './index-manager.js';
export * from './workspace-lock.js';
export * from './workspace-manager.js';
export * from './profiler.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC"}
+73
View File
@@ -0,0 +1,73 @@
/******************************************************************************
* Copyright 2025 Y. Daveluy
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { MultiMap } from '../utils/collections.js';
import type { Stream } from '../utils/stream.js';
export type ProfilingCategory = 'validating' | 'parsing' | 'linking';
export interface LangiumProfiler {
/**
* Checks if the given category is active.
* @param category The category to check.
* @returns `true` if the category is active, `false` otherwise.
*/
isActive(category: ProfilingCategory): boolean;
/**
* Starts the profiling for the given categories. If none are provided, all categories are started.
* @param categories The categories to start profiling for.
*/
start(...categories: ProfilingCategory[]): void;
/**
* Stops the profiling for the given categories. If none are provided, all categories are stopped.
* @param categories The categories to stop profiling for.
*/
stop(...categories: ProfilingCategory[]): void;
/**
* Creates a new {@link ProfilingTask} for the given category.
* @param category The category to create the task for.
* @param taskId The identifier of the task.
*/
createTask(category: ProfilingCategory, taskId: string): ProfilingTask;
/**
* Gets the {@link ProfilingRecord}s for the given categories. If none are provided, all records are returned.
* @param categories The categories to get the records for.
* @returns A stream of profiling records.
*/
getRecords(...categories: ProfilingCategory[]): Stream<ProfilingRecord>;
}
export declare class DefaultLangiumProfiler implements LangiumProfiler {
protected activeCategories: Set<ProfilingCategory>;
protected readonly allCategories: ReadonlySet<ProfilingCategory>;
protected readonly records: MultiMap<string, ProfilingRecord>;
constructor(activeCategories?: Set<ProfilingCategory>);
isActive(category: ProfilingCategory): boolean;
start(...categories: ProfilingCategory[]): void;
stop(...categories: ProfilingCategory[]): void;
createTask(category: ProfilingCategory, taskId: string): ProfilingTask;
protected dumpRecord(category: string, record: ProfilingRecord): ProfilingRecord;
getRecords(...categories: ProfilingCategory[]): Stream<ProfilingRecord>;
}
export interface ProfilingRecord {
identifier: string;
date: Date;
duration: number;
entries: MultiMap<string, number>;
}
export declare class ProfilingTask {
protected startTime?: number;
protected readonly addRecord: (record: ProfilingRecord) => void;
protected readonly identifier: string;
protected readonly stack: Array<{
id: string;
start: number;
content: number;
}>;
protected readonly entries: MultiMap<string, number>;
constructor(addRecord: (record: ProfilingRecord) => void, identifier: string);
start(): void;
stop(): void;
startSubTask(subTaskId: string): void;
stopSubTask(subTaskId: string): void;
}
//# sourceMappingURL=profiler.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../../src/workspace/profiler.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAErE,MAAM,WAAW,eAAe;IAE5B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAE/C;;;OAGG;IACH,KAAK,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAC;IAEhD;;;OAGG;IACH,IAAI,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAC;IAE/C;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAEvE;;;;OAIG;IACH,UAAU,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;CAC3E;AAED,qBAAa,sBAAuB,YAAW,eAAe;IAC1D,SAAS,CAAC,gBAAgB,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAgC;IAClF,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAsE;IACtI,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;gBAElD,gBAAgB,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC;IAKrD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO;IAI9C,KAAK,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAU/C,IAAI,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAQ9C,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa;IAQtE,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,eAAe;IAqBhF,UAAU,CAAC,GAAG,UAAU,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,eAAe,CAAC;CAS1E;AAED,MAAM,WAAW,eAAe;IAE5B,UAAU,EAAE,MAAM,CAAA;IAElB,IAAI,EAAE,IAAI,CAAA;IAEV,QAAQ,EAAE,MAAM,CAAA;IAGhB,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,qBAAa,aAAa;IACtB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAEhE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IACrF,SAAS,CAAC,QAAQ,CAAC,OAAO,2BAAkC;gBAEhD,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,EAAE,UAAU,EAAE,MAAM;IAK5E,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;IAkBZ,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAkBvC"}
+118
View File
@@ -0,0 +1,118 @@
/******************************************************************************
* Copyright 2025 Y. Daveluy
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { MultiMap } from '../utils/collections.js';
export class DefaultLangiumProfiler {
constructor(activeCategories) {
this.activeCategories = new Set();
this.allCategories = new Set(['validating', 'parsing', 'linking']);
this.activeCategories = activeCategories ?? new Set(this.allCategories);
this.records = new MultiMap();
}
isActive(category) {
return this.activeCategories.has(category);
}
start(...categories) {
if (!categories) {
// Create a new set with all categories (immutable copy)
this.activeCategories = new Set(this.allCategories);
}
else {
categories.forEach(category => this.activeCategories.add(category));
}
}
stop(...categories) {
if (!categories) {
this.activeCategories.clear();
}
else {
categories.forEach(category => this.activeCategories.delete(category));
}
}
createTask(category, taskId) {
if (!this.isActive(category)) {
throw new Error(`Category "${category}" is not active.`);
}
console.log(`Creating profiling task for '${category}.${taskId}'.`);
return new ProfilingTask((record) => this.records.add(category, this.dumpRecord(category, record)), taskId);
}
dumpRecord(category, record) {
console.info(`Task ${category}.${record.identifier} executed in ${record.duration.toFixed(2)}ms and ended at ${record.date.toISOString()}`);
const result = [];
for (const key of record.entries.keys()) {
const values = record.entries.get(key);
const duration = values.reduce((p, c) => p + c);
result.push({ name: `${record.identifier}.${key}`, count: values.length, duration: duration });
}
// sum all duration
const taskInternalDuration = record.duration - result.map(r => r.duration).reduce((a, b) => a + b, 0);
result.push({ name: record.identifier, count: 1, duration: taskInternalDuration });
result.sort((a, b) => b.duration - a.duration);
function Round(value) { return Math.round(100 * value) / 100; }
console.table(result.map(e => { return { Element: e.name, Count: e.count, 'Self %': Round(100 * e.duration / record.duration), 'Time (ms)': Round(e.duration) }; }));
return record;
}
getRecords(...categories) {
if (categories.length === 0) {
// return all records
return this.records.values();
}
else {
// return records for the given categories
return this.records.entries().filter((e) => categories.some(c => c === e[0])).flatMap(e => e[1]);
}
}
}
export class ProfilingTask {
constructor(addRecord, identifier) {
this.stack = [];
this.entries = new MultiMap();
this.addRecord = addRecord;
this.identifier = identifier;
}
start() {
if (this.startTime !== undefined) {
throw new Error(`Task "${this.identifier}" is already started.`);
}
this.startTime = performance.now();
}
stop() {
if (this.startTime === undefined) {
throw new Error(`Task "${this.identifier}" was not started.`);
}
if (this.stack.length !== 0) {
throw new Error(`Task "${this.identifier}" cannot be stopped before sub-task(s): ${this.stack.map(s => s.id).join(', ')}.`);
}
const record = {
identifier: this.identifier,
date: new Date(),
duration: performance.now() - this.startTime,
entries: this.entries
};
this.addRecord(record);
this.startTime = undefined;
this.entries.clear();
}
startSubTask(subTaskId) {
this.stack.push({ id: subTaskId, start: performance.now(), content: 0 });
}
stopSubTask(subTaskId) {
const subStack = this.stack.pop();
if (!subStack) {
throw new Error(`Task "${this.identifier}.${subTaskId}" was not started.`);
}
if (subStack.id !== subTaskId) {
throw new Error(`Sub-Task "${subStack.id}" is not already stopped.`);
}
const duration = performance.now() - subStack.start;
if (this.stack.at(-1) !== undefined) {
this.stack[this.stack.length - 1].content += duration;
}
// we are interested here by the duration of the current sub-task without the duration of nested sub-tasks.
const selfDuration = duration - subStack.content;
this.entries.add(subTaskId, selfDuration);
}
}
//# sourceMappingURL=profiler.js.map
File diff suppressed because one or more lines are too long
+46
View File
@@ -0,0 +1,46 @@
/******************************************************************************
* 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 { CancellationToken } from '../utils/cancellation.js';
import { 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;
}
export declare class DefaultWorkspaceLock implements WorkspaceLock {
private previousTokenSource;
private writeQueue;
private readQueue;
private done;
write(action: (token: CancellationToken) => MaybePromise<void>): Promise<void>;
read<T>(action: () => MaybePromise<T>): Promise<T>;
private enqueue;
private performNextOperation;
cancelWrite(): void;
}
//# sourceMappingURL=workspace-lock.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"workspace-lock.d.ts","sourceRoot":"","sources":["../../src/workspace/workspace-lock.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAwC,iBAAiB,EAA2B,MAAM,0BAA0B,CAAC;AAC5H,OAAO,EAA4D,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAExH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,YAAY,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/E;;;;;;;;OAQG;IACH,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnD;;OAEG;IACH,WAAW,IAAI,IAAI,CAAC;CACvB;AAUD,qBAAa,oBAAqB,YAAW,aAAa;IAEtD,OAAO,CAAC,mBAAmB,CAAkE;IAC7F,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,IAAI,CAAQ;IAEpB,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,YAAY,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9E,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIlD,OAAO,CAAC,OAAO;YAYD,oBAAoB;IAiClC,WAAW,IAAI,IAAI;CAGtB"}
+75
View File
@@ -0,0 +1,75 @@
/******************************************************************************
* 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 { CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
import { Deferred, isOperationCancelled, startCancelableOperation } from '../utils/promise-utils.js';
export class DefaultWorkspaceLock {
constructor() {
this.previousTokenSource = new CancellationTokenSource();
this.writeQueue = [];
this.readQueue = [];
this.done = true;
}
write(action) {
this.cancelWrite();
const tokenSource = startCancelableOperation();
this.previousTokenSource = tokenSource;
return this.enqueue(this.writeQueue, action, tokenSource.token);
}
read(action) {
return this.enqueue(this.readQueue, action);
}
enqueue(queue, action, cancellationToken = CancellationToken.None) {
const deferred = new Deferred();
const entry = {
action,
deferred,
cancellationToken
};
queue.push(entry);
this.performNextOperation();
return deferred.promise;
}
async performNextOperation() {
if (!this.done) {
return;
}
const entries = [];
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() {
this.previousTokenSource.cancel();
}
}
//# sourceMappingURL=workspace-lock.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"workspace-lock.js","sourceRoot":"","sources":["../../src/workspace/workspace-lock.ts"],"names":[],"mappings":"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"}
+124
View File
@@ -0,0 +1,124 @@
/******************************************************************************
* 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 { InitializeParams, InitializedParams } from 'vscode-languageserver-protocol';
import type { WorkspaceFolder } from 'vscode-languageserver-types';
import type { ServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';
import { CancellationToken } from '../utils/cancellation.js';
import { Deferred } from '../utils/promise-utils.js';
import { URI } from '../utils/uri-utils.js';
import type { BuildOptions, DocumentBuilder } from './document-builder.js';
import type { LangiumDocument, LangiumDocuments } from './documents.js';
import type { FileSystemNode, FileSystemProvider } from './file-system-provider.js';
import type { WorkspaceLock } from './workspace-lock.js';
import { type Stream } from '../utils/stream.js';
export type { WorkspaceFolder };
/**
* The workspace manager is responsible for finding source files in the workspace.
* This service is shared between all languages of a language server.
*/
export interface WorkspaceManager {
/** The options used for the initial workspace build. */
initialBuildOptions: BuildOptions | undefined;
/**
* A promise that resolves when the workspace manager is ready to be used.
* Use this to ensure that the workspace manager has finished its initialization.
*/
readonly ready: Promise<void>;
/**
* The workspace folders of the current workspace.
* Available only after the `ready` promise resolves.
*/
get workspaceFolders(): readonly WorkspaceFolder[] | undefined;
/**
* 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: InitializedParams): Promise<void>;
/**
* Does the initial indexing of workspace folders.
* Collects information about exported and referenced AstNodes in
* each language file and stores it locally.
*
* @param folders The set of workspace folders to be indexed.
* @param cancelToken A cancellation token that can be used to cancel the operation.
*
* @throws OperationCancelled if a cancellation event has been detected
*/
initializeWorkspace(folders: WorkspaceFolder[], cancelToken?: CancellationToken): Promise<void>;
/**
* Searches for workspace files in the given folder and its subdirectories.
* Note that this method does not create documents for the found files.
* @param uri The URI of the folder to search in.
* @returns A promise that resolves to an array of URIs of the found files.
*/
searchFolder(uri: URI): Promise<URI[]>;
/**
* Determine whether the given file system node shall be included in the workspace.
* @param entry The file system node to check.
* @returns `true` if the entry shall be included, `false` otherwise.
*/
shouldIncludeEntry(entry: FileSystemNode): boolean;
}
/**
* The FileSelector provides file names and extensions used by this extension.
*/
export interface FileSelector {
/** Allowed file extensions (e.g., ["ts", "js"]). */
fileExtensions: string[];
/** Allowed file names (e.g., ["config", "settings"]). */
fileNames: string[];
}
export declare class DefaultWorkspaceManager implements WorkspaceManager {
initialBuildOptions: BuildOptions;
protected readonly serviceRegistry: ServiceRegistry;
protected readonly langiumDocuments: LangiumDocuments;
protected readonly documentBuilder: DocumentBuilder;
protected readonly fileSystemProvider: FileSystemProvider;
protected readonly mutex: WorkspaceLock;
protected readonly _ready: Deferred<void>;
protected folders?: WorkspaceFolder[];
constructor(services: LangiumSharedCoreServices);
get ready(): Promise<void>;
get workspaceFolders(): readonly WorkspaceFolder[] | undefined;
initialize(params: InitializeParams): void;
initialized(_params: InitializedParams): Promise<void>;
initializeWorkspace(folders: WorkspaceFolder[], cancelToken?: CancellationToken): Promise<void>;
/**
* Performs the uninterruptable startup sequence of the workspace manager.
* This methods loads all documents in the workspace and other documents and returns them.
*/
protected performStartup(folders: WorkspaceFolder[]): Promise<LangiumDocument[]>;
protected loadWorkspaceDocuments(uris: Stream<URI>, collector: (document: LangiumDocument) => void): Promise<void>;
/**
* Load all additional documents that shall be visible in the context of the given workspace
* folders and add them to the collector. This can be used to include built-in libraries of
* your language, which can be either loaded from provided files or constructed in memory.
*/
protected loadAdditionalDocuments(_folders: WorkspaceFolder[], _collector: (document: LangiumDocument) => void): Promise<void>;
/**
* Determine the root folder of the source documents in the given workspace folder.
* The default implementation returns the URI of the workspace folder, but you can override
* this to return a subfolder like `src` instead.
*/
protected getRootFolder(workspaceFolder: WorkspaceFolder): URI;
/**
* Traverse the file system folder identified by the given URI and its subfolders. All
* contained files that match the file extensions are added to the `uris` array.
*/
protected traverseFolder(folderPath: URI, uris: URI[]): Promise<void>;
searchFolder(uri: URI): Promise<URI[]>;
/**
* Determine whether the given folder entry shall be included while indexing the workspace.
*/
shouldIncludeEntry(entry: FileSystemNode): boolean;
}
//# sourceMappingURL=workspace-manager.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"workspace-manager.d.ts","sourceRoot":"","sources":["../../src/workspace/workspace-manager.ts"],"names":[],"mappings":"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"}
+135
View File
@@ -0,0 +1,135 @@
/******************************************************************************
* 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 { CancellationToken } from '../utils/cancellation.js';
import { Deferred, interruptAndCheck } from '../utils/promise-utils.js';
import { URI, UriUtils } from '../utils/uri-utils.js';
import { stream } from '../utils/stream.js';
export class DefaultWorkspaceManager {
constructor(services) {
this.initialBuildOptions = {};
this._ready = new Deferred();
this.serviceRegistry = services.ServiceRegistry;
this.langiumDocuments = services.workspace.LangiumDocuments;
this.documentBuilder = services.workspace.DocumentBuilder;
this.fileSystemProvider = services.workspace.FileSystemProvider;
this.mutex = services.workspace.WorkspaceLock;
}
get ready() {
return this._ready.promise;
}
get workspaceFolders() {
return this.folders;
}
initialize(params) {
this.folders = params.workspaceFolders ?? undefined;
}
initialized(_params) {
// Initialize the workspace even if there are no workspace folders
// We still want to load additional documents (language library or similar) during initialization
return this.mutex.write(token => this.initializeWorkspace(this.folders ?? [], token));
}
async initializeWorkspace(folders, cancelToken = CancellationToken.None) {
const documents = await this.performStartup(folders);
// Only after creating all documents do we check whether we need to cancel the initialization
// The document builder will later pick up on all unprocessed documents
await interruptAndCheck(cancelToken);
await this.documentBuilder.build(documents, this.initialBuildOptions, cancelToken);
}
/**
* Performs the uninterruptable startup sequence of the workspace manager.
* This methods loads all documents in the workspace and other documents and returns them.
*/
async performStartup(folders) {
const documents = [];
const collector = (document) => {
documents.push(document);
if (!this.langiumDocuments.hasDocument(document.uri)) {
this.langiumDocuments.addDocument(document);
}
};
// Even though we don't await the initialization of the workspace manager,
// we can still assume that all library documents and file documents are loaded by the time we start building documents.
// The mutex prevents anything from performing a workspace build until we check the cancellation token
await this.loadAdditionalDocuments(folders, collector);
const uris = [];
await Promise.all(folders.map(wf => this.getRootFolder(wf))
.map(async (entry) => this.traverseFolder(entry, uris)));
const uniqueUris = stream(uris)
// Ensure that we only create one document per URI/file
.distinct(uri => uri.toString())
// Also ensure that the documents don't already exist
.filter(uri => !this.langiumDocuments.hasDocument(uri));
await this.loadWorkspaceDocuments(uniqueUris, collector);
this._ready.resolve();
return documents;
}
async loadWorkspaceDocuments(uris, collector) {
await Promise.all(uris.map(async (uri) => {
const document = await this.langiumDocuments.getOrCreateDocument(uri);
collector(document);
}));
}
/**
* Load all additional documents that shall be visible in the context of the given workspace
* folders and add them to the collector. This can be used to include built-in libraries of
* your language, which can be either loaded from provided files or constructed in memory.
*/
loadAdditionalDocuments(_folders, _collector) {
return Promise.resolve();
}
/**
* Determine the root folder of the source documents in the given workspace folder.
* The default implementation returns the URI of the workspace folder, but you can override
* this to return a subfolder like `src` instead.
*/
getRootFolder(workspaceFolder) {
return URI.parse(workspaceFolder.uri);
}
/**
* Traverse the file system folder identified by the given URI and its subfolders. All
* contained files that match the file extensions are added to the `uris` array.
*/
async traverseFolder(folderPath, uris) {
try {
const content = await this.fileSystemProvider.readDirectory(folderPath);
await Promise.all(content.map(async (entry) => {
if (this.shouldIncludeEntry(entry)) {
if (entry.isDirectory) {
await this.traverseFolder(entry.uri, uris);
}
else if (entry.isFile) {
uris.push(entry.uri);
}
}
}));
}
catch (e) {
console.error('Failure to read directory content of ' + folderPath.toString(true), e);
}
}
async searchFolder(uri) {
const uris = [];
await this.traverseFolder(uri, uris);
return uris;
}
/**
* Determine whether the given folder entry shall be included while indexing the workspace.
*/
shouldIncludeEntry(entry) {
const name = UriUtils.basename(entry.uri);
if (name.startsWith('.')) {
return false;
}
if (entry.isDirectory) {
return name !== 'node_modules' && name !== 'out';
}
else if (entry.isFile) {
return this.serviceRegistry.hasServices(entry.uri);
}
return false;
}
}
//# sourceMappingURL=workspace-manager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"workspace-manager.js","sourceRoot":"","sources":["../../src/workspace/workspace-manager.ts"],"names":[],"mappings":"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"}