Skip to content

Commit

Permalink
refactor workspace managers, hoist up relevant vscode-ls types,
Browse files Browse the repository at this point in the history
interface rename
  • Loading branch information
montymxb committed Dec 1, 2023
1 parent 8609bcd commit 24f0739
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 140 deletions.
5 changes: 2 additions & 3 deletions examples/domainmodel/src/cli/cli-util.ts
Expand Up @@ -4,8 +4,7 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import type { AstNode, LangiumDocument, LangiumCoreServices } from 'langium';
import type { WorkspaceFolder } from 'vscode-languageserver';
import type { AstNode, LangiumDocument, LangiumCoreServices, WorkspaceFolder } from 'langium';
import * as fs from 'node:fs';
import * as path from 'node:path';
import chalk from 'chalk';
Expand Down Expand Up @@ -53,7 +52,7 @@ export async function setRootFolder(fileName: string, services: LangiumServices,
}
const folders: WorkspaceFolder[] = [{
name: path.basename(root),
uri: URI.file(root).toString()
uri: URI.file(root)
}];
await services.shared.workspace.WorkspaceManager.initializeWorkspace(folders);
}
Expand Down
1 change: 1 addition & 0 deletions examples/domainmodel/test/domainmodel-cli.test.ts
Expand Up @@ -37,6 +37,7 @@ describe('Test the domainmodel CLI', () => {
}

test('Test action without CLI', async () => {
// TODO @montymxb, revert to quiet: true when the import issue is fixed
await generateAction(rawfileName, { destination, quiet: false });
commonExpectations();
});
Expand Down
5 changes: 2 additions & 3 deletions examples/requirements/src/cli/cli-util.ts
Expand Up @@ -4,9 +4,8 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import type { LangiumDocument, LangiumCoreServices } from 'langium';
import type { LangiumDocument, LangiumCoreServices, WorkspaceFolder } from 'langium';
import type { RequirementModel, TestModel } from '../language-server/generated/ast.js';
import type { WorkspaceFolder } from 'vscode-languageclient';
import chalk from 'chalk';
import * as path from 'node:path';
import * as fs from 'node:fs';
Expand Down Expand Up @@ -34,7 +33,7 @@ export async function extractDocuments(fileName: string, services: LangiumCoreSe
}

const folders: WorkspaceFolder[] = [{
uri: URI.file(path.resolve(path.dirname(fileName))).toString(),
uri: URI.file(path.resolve(path.dirname(fileName))),
name: 'main'
}];
await services.shared.workspace.WorkspaceManager.initializeWorkspace(folders);
Expand Down
4 changes: 2 additions & 2 deletions packages/langium-sprotty/src/diagram-server-manager.ts
Expand Up @@ -6,7 +6,7 @@

import type { CancellationToken, Connection } from 'vscode-languageserver';
import type { ActionMessage, DiagramOptions, DiagramServer, RequestModelAction } from 'sprotty-protocol';
import type { LangiumDocument, CoreServiceRegistryI, URI } from 'langium';
import type { LangiumDocument, ICoreServiceRegistry, URI } from 'langium';
import type { LangiumSprottyServices, LangiumSprottySharedServices } from './sprotty-services.js';
import type { LangiumDiagramGeneratorArguments } from './diagram-generator.js';
import { isRequestAction, RejectAction } from 'sprotty-protocol';
Expand Down Expand Up @@ -34,7 +34,7 @@ export interface DiagramServerManager {
export class DefaultDiagramServerManager implements DiagramServerManager {

protected readonly connection?: Connection;
protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly diagramServerFactory: (clientId: string, options?: DiagramOptions) => DiagramServer;
protected readonly diagramServerMap: Map<string, DiagramServer> = new Map();

Expand Down
Expand Up @@ -5,10 +5,9 @@
******************************************************************************/

import type { Ignore } from 'ignore';
import type { LangiumSharedCoreServices, ConfigurationProvider, FileSystemNode } from 'langium';
import type { WorkspaceFolder } from 'vscode-languageserver-protocol';
import type { LangiumSharedCoreServices, ConfigurationProvider, FileSystemNode, WorkspaceFolder } from 'langium';
import ignore from 'ignore';
import { DefaultWorkspaceManager, URI, UriUtils } from 'langium';
import { DefaultWorkspaceManager, UriUtils } from 'langium';
import * as path from 'path';
import { CancellationToken } from 'vscode-languageserver-protocol';

Expand Down Expand Up @@ -41,7 +40,7 @@ export class LangiumGrammarWorkspaceManager extends DefaultWorkspaceManager {
protected override includeEntry(workspaceFolder: WorkspaceFolder, entry: FileSystemNode, fileExtensions: string[]): boolean {
if (this.matcher) {
// create path relative to workspace folder root: /user/foo/workspace/entry.txt -> entry.txt
const relPath = path.relative(URI.parse(workspaceFolder.uri).path, entry.uri.path);
const relPath = path.relative(workspaceFolder.uri.path, entry.uri.path);
const ignored = this.matcher.ignores(relPath);
return !ignored && (entry.isDirectory || (entry.isFile && fileExtensions.includes(UriUtils.extname(entry.uri))));
}
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/lsp/workspace/default-configuration.ts
Expand Up @@ -6,7 +6,7 @@

import type { Connection, DidChangeConfigurationParams } from 'vscode-languageserver';
import type { ConfigurationItem } from 'vscode-languageserver-protocol';
import type { CoreServiceRegistryI } from '../../service-registry.js';
import type { ICoreServiceRegistry } from '../../service-registry.js';
import { DidChangeConfigurationNotification } from 'vscode-languageserver-protocol';
import type { ConfigurationProvider } from '../../workspace/configuration.js';
import type { LangiumSharedServices } from '../lsp-services.js';
Expand All @@ -21,7 +21,7 @@ export class LSPConfigurationProvider implements ConfigurationProvider {
protected settings: Record<string, Record<string, any>> = {};
protected workspaceConfig = false;
protected initialized = false;
protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly connection: Connection | undefined;

constructor(services: LangiumSharedServices) {
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/lsp/workspace/documents.ts
Expand Up @@ -6,7 +6,7 @@

import type { TextDocuments } from 'vscode-languageserver';
import type { ParseResult } from '../../parser/langium-parser.js';
import type { CoreServiceRegistryI } from '../../service-registry.js';
import type { ICoreServiceRegistry } from '../../service-registry.js';
import type { AstNode } from '../../syntax-tree.js';
import type { Mutable } from '../../utils/ast-util.js';
import type { FileSystemProvider } from './../../workspace/file-system-provider.js';
Expand All @@ -20,7 +20,7 @@ import type { LangiumSharedServices } from '../lsp-services.js';
*/
export class LSPLangiumDocumentFactory implements LangiumDocumentFactory {

protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly textDocuments: TextDocuments<TextDocument>;
protected readonly fileSystemProvider: FileSystemProvider;

Expand Down
107 changes: 5 additions & 102 deletions packages/langium/src/lsp/workspace/workspace-manager.ts
Expand Up @@ -4,41 +4,21 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { CancellationToken } from 'vscode-languageserver';
import { interruptAndCheck } from '../../utils/promise-util.js';
import { URI, UriUtils } from '../../utils/uri-util.js';
import type { WorkspaceManager } from './../../workspace/workspace-manager.js';
import type { WorkspaceFolder } from 'vscode-languageserver';
import type { CoreServiceRegistryI } from '../../service-registry.js';
import type { MutexLock } from '../../utils/promise-util.js';
import type { BuildOptions, DocumentBuilder } from './../../workspace/document-builder.js';
import type { LangiumDocument, LangiumDocuments } from './../../workspace/documents.js';
import type { FileSystemNode, FileSystemProvider } from './../../workspace/file-system-provider.js';
import { DefaultWorkspaceManager, type WorkspaceFolder } from './../../workspace/workspace-manager.js';
import type { LangiumSharedServices } from '../lsp-services.js';

/**
* Langium Workspace manager that uses LSP workspace folders
*/
export class LSPWorkspaceManager implements WorkspaceManager {
export class LSPWorkspaceManager extends DefaultWorkspaceManager {

initialBuildOptions: BuildOptions = {};

protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly langiumDocuments: LangiumDocuments;
protected readonly documentBuilder: DocumentBuilder;
protected readonly fileSystemProvider: FileSystemProvider;
protected readonly mutex: MutexLock;
protected folders?: WorkspaceFolder[];

constructor(services: LangiumSharedServices) {
this.serviceRegistry = services.ServiceRegistry;
this.langiumDocuments = services.workspace.LangiumDocuments;
this.documentBuilder = services.workspace.DocumentBuilder;
this.fileSystemProvider = services.workspace.FileSystemProvider;
this.mutex = services.workspace.MutexLock;

super(services);
services.lsp.LanguageServer.onInitialize(params => {
this.folders = params.workspaceFolders ?? undefined;
// convert lsp workspace folders to langium workspace folders (equivalent type plays nice with the existing non-lsp core services)
this.folders = (params.workspaceFolders as unknown as WorkspaceFolder[]) ?? undefined;
});

services.lsp.LanguageServer.onInitialized(_params => {
Expand All @@ -47,81 +27,4 @@ export class LSPWorkspaceManager implements WorkspaceManager {
this.mutex.lock(token => this.initializeWorkspace(this.folders ?? [], token));
});
}

async initializeWorkspace(folders: WorkspaceFolder[], cancelToken = CancellationToken.None): Promise<void> {
const fileExtensions = this.serviceRegistry.all.flatMap(e => e.LanguageMetaData.fileExtensions);
const documents: LangiumDocument[] = [];
const collector = (document: LangiumDocument) => {
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);
await Promise.all(
folders.map(wf => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI])
.map(async entry => this.traverseFolder(...entry, fileExtensions, collector))
);
// 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);
}

/**
* 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> {
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.
*/
protected getRootFolder(workspaceFolder: WorkspaceFolder): URI {
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 collector.
*/
protected async traverseFolder(workspaceFolder: WorkspaceFolder, folderPath: URI, fileExtensions: string[], collector: (document: LangiumDocument) => void): Promise<void> {
const content = await this.fileSystemProvider.readDirectory(folderPath);
await Promise.all(content.map(async entry => {
if (this.includeEntry(workspaceFolder, entry, fileExtensions)) {
if (entry.isDirectory) {
await this.traverseFolder(workspaceFolder, entry.uri, fileExtensions, collector);
} else if (entry.isFile) {
const document = this.langiumDocuments.getOrCreateDocument(entry.uri);
collector(document);
}
}
}));
}

/**
* Determine whether the given folder entry shall be included while indexing the workspace.
*/
protected includeEntry(workspaceFolder: WorkspaceFolder, entry: FileSystemNode, fileExtensions: string[]): boolean {
const name = UriUtils.basename(entry.uri);
if (name.startsWith('.')) {
return false;
}
if (entry.isDirectory) {
return name !== 'node_modules' && name !== 'out';
} else if (entry.isFile) {
const extname = UriUtils.extname(entry.uri);
return fileExtensions.includes(extname);
}
return false;
}

}
6 changes: 3 additions & 3 deletions packages/langium/src/service-registry.ts
Expand Up @@ -8,10 +8,10 @@ import type { LangiumCoreServices } from './services.js';
import { UriUtils, type URI } from './utils/uri-util.js';

/**
* The core service registry provides access to the core language-specific services. These are resolved
* The core service registry provides access to the language-specific core services. These are resolved
* via the URI of a text document.
*/
export interface CoreServiceRegistryI {
export interface ICoreServiceRegistry {

/**
* Register a language via its injected services.
Expand All @@ -33,7 +33,7 @@ export interface CoreServiceRegistryI {
/**
* Registry for Langium's core services
*/
export class CoreServiceRegistry implements CoreServiceRegistryI {
export class CoreServiceRegistry implements ICoreServiceRegistry {

protected singleton?: LangiumCoreServices;
protected map?: Record<string, LangiumCoreServices>;
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/services.ts
Expand Up @@ -21,7 +21,7 @@ import type { References } from './references/references.js';
import type { ScopeComputation } from './references/scope-computation.js';
import type { ScopeProvider } from './references/scope-provider.js';
import type { JsonSerializer } from './serializer/json-serializer.js';
import type { CoreServiceRegistryI } from './service-registry.js';
import type { ICoreServiceRegistry } from './service-registry.js';
import type { AstReflection } from './syntax-tree.js';
import type { MutexLock } from './utils/promise-util.js';
import type { DocumentValidator } from './validation/document-validator.js';
Expand Down Expand Up @@ -107,7 +107,7 @@ export type LangiumGeneratedSharedServices = {
* Services shared between multiple languages where Langium provides default implementations.
*/
export type LangiumDefaultSharedServices = {
ServiceRegistry: CoreServiceRegistryI
ServiceRegistry: ICoreServiceRegistry
workspace: {
DocumentBuilder: DocumentBuilder
IndexManager: IndexManager
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/workspace/configuration.ts
Expand Up @@ -5,7 +5,7 @@
******************************************************************************/

import type { DidChangeConfigurationParams } from 'vscode-languageserver';
import type { CoreServiceRegistryI } from '../service-registry.js';
import type { ICoreServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -37,7 +37,7 @@ export class EmptyConfigurationProvider implements ConfigurationProvider {
protected settings: Record<string, Record<string, any>> = {};
protected workspaceConfig = false;
protected initialized = false;
protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;

constructor(services: LangiumSharedCoreServices) {
this.serviceRegistry = services.ServiceRegistry;
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/workspace/document-builder.ts
Expand Up @@ -5,7 +5,7 @@
******************************************************************************/

import type { URI } from '../utils/uri-util.js';
import type { CoreServiceRegistryI } from '../service-registry.js';
import type { ICoreServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { AstNode } from '../syntax-tree.js';
import type { MaybePromise } from '../utils/promise-util.js';
Expand Down Expand Up @@ -95,7 +95,7 @@ export class DefaultDocumentBuilder implements DocumentBuilder {
protected readonly langiumDocuments: LangiumDocuments;
protected readonly langiumDocumentFactory: LangiumDocumentFactory;
protected readonly indexManager: IndexManager;
protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly updateListeners: DocumentUpdateListener[] = [];
protected readonly buildPhaseListeners: MultiMap<DocumentState, DocumentBuildListener> = new MultiMap();
protected readonly buildState: Map<string, DocumentBuildState> = new Map();
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/workspace/documents.ts
Expand Up @@ -7,7 +7,7 @@
import type { Range } from 'vscode-languageserver-textdocument';
import type { Diagnostic } from 'vscode-languageserver';
import type { ParseResult } from '../parser/langium-parser.js';
import type { CoreServiceRegistryI } from '../service-registry.js';
import type { ICoreServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription, Reference } from '../syntax-tree.js';
import type { Mutable } from '../utils/ast-util.js';
Expand Down Expand Up @@ -140,7 +140,7 @@ export interface LangiumDocumentFactory {

export class DefaultLangiumDocumentFactory implements LangiumDocumentFactory {

protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly fileSystemProvider: FileSystemProvider;

constructor(services: LangiumSharedCoreServices) {
Expand Down
4 changes: 2 additions & 2 deletions packages/langium/src/workspace/index-manager.ts
Expand Up @@ -5,7 +5,7 @@
******************************************************************************/

import type { URI } from '../utils/uri-util.js';
import type { CoreServiceRegistryI } from '../service-registry.js';
import type { ICoreServiceRegistry } from '../service-registry.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription, AstReflection } from '../syntax-tree.js';
import type { Stream } from '../utils/stream.js';
Expand Down Expand Up @@ -83,7 +83,7 @@ export interface IndexManager {

export class DefaultIndexManager implements IndexManager {

protected readonly serviceRegistry: CoreServiceRegistryI;
protected readonly serviceRegistry: ICoreServiceRegistry;
protected readonly documents: LangiumDocuments;
protected readonly astReflection: AstReflection;

Expand Down

0 comments on commit 24f0739

Please sign in to comment.