Skip to content

Commit

Permalink
Configurable folder excludes #344
Browse files Browse the repository at this point in the history
  • Loading branch information
dhuebner committed Aug 19, 2022
1 parent ae2708f commit 4bc41ef
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 37 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 0 additions & 19 deletions packages/langium-vscode/.eslintrc.json

This file was deleted.

15 changes: 13 additions & 2 deletions packages/langium-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,17 @@
"fileMatch": "langium-config.json",
"url": "./data/langium-config-schema.json"
}
]
],
"configuration": {
"title": "Langium",
"properties": {
"langium-settings.build.ignorePatterns": {
"type": "string",
"default": "node_modules, out",
"description": "Specifies the exclusion patterns during initial workspace indexing. You will need to reload your extension afterwards."
}
}
}
},
"activationEvents": [
"onLanguage:langium"
Expand All @@ -55,7 +65,8 @@
"dependencies": {
"langium": "0.4.0",
"vscode-languageclient": "^8.0.1",
"vscode-languageserver": "^8.0.1"
"vscode-languageserver": "^8.0.1",
"ignore":"~5.2.0"
},
"devDependencies": {
"@types/vscode": "^1.53.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/******************************************************************************
* 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 ignore, { Ignore } from 'ignore';
import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver-protocol';
import { URI, Utils } from 'vscode-uri';
import { LangiumSharedServices } from 'langium/src/services';
import { ConfigurationProvider } from 'langium/src/workspace/configuration';
import { FileSystemNode } from 'langium/src/workspace/file-system-provider';
import { DefaultWorkspaceManager } from 'langium/src/workspace/workspace-manager';
import { LangiumGrammarLanguageMetaData } from 'langium/src/grammar/generated/module';

const CONFIG_KEY = 'build';

interface WorkspaceManagerConf {
/**
* gitignore separated exclusion patterns, separated by comma
*/
ignorePatterns: string
}

export class LangiumGrammarWorkspaceManager extends DefaultWorkspaceManager {

protected readonly configurationProvider: ConfigurationProvider;
protected matcher: Ignore | undefined;

constructor(services: LangiumSharedServices) {
super(services);
this.configurationProvider = services.workspace.ConfigurationProvider;
}

async initializeWorkspace(folders: WorkspaceFolder[], cancelToken = CancellationToken.None): Promise<void> {
const buildConf: WorkspaceManagerConf = await this.configurationProvider.getConfiguration(LangiumGrammarLanguageMetaData.languageId, CONFIG_KEY);
const ignorePatterns = buildConf.ignorePatterns?.split(',')?.map(pattern => pattern.trim())?.filter(pattern => pattern.length > 0);
this.matcher = ignorePatterns ? ignore().add(ignorePatterns) : undefined;
return super.initializeWorkspace(folders, cancelToken);
}

protected includeEntry(entry: FileSystemNode, fileExtensions: string[]): boolean {
if (this.matcher) {
const parentWsFolder = this.folders?.map(folder => folder.uri).find(folderUri => entry.uri.toString().startsWith(folderUri));
let entryPath = entry.uri.path;
if (parentWsFolder) {
// create path relative to workspace folder root: /user/foo/workspace/entry.txt -> entry.txt
const wsFolderPath = URI.parse(parentWsFolder).path + '/';
entryPath = entryPath.replace(wsFolderPath, '');
}
const ignored = this.matcher.ignores(entryPath);
return !ignored && (entry.isDirectory || (entry.isFile && fileExtensions.includes(Utils.extname(entry.uri))));
}
return super.includeEntry(entry, fileExtensions);
}

}
12 changes: 10 additions & 2 deletions packages/langium-vscode/src/language-server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { createLangiumGrammarServices, startLanguageServer } from 'langium';
import { createLangiumGrammarServices, LangiumSharedServices, Module, PartialLangiumSharedServices, startLanguageServer } from 'langium';
import { NodeFileSystem } from 'langium/node';
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
import { LangiumGrammarWorkspaceManager } from './grammar-workspace-manager';

const connection = createConnection(ProposedFeatures.all);
const { shared } = createLangiumGrammarServices({ connection, ...NodeFileSystem });

export const LangiumGrammarSharedModule: Module<LangiumSharedServices, PartialLangiumSharedServices> = {
workspace: {
WorkspaceManager: (services) => new LangiumGrammarWorkspaceManager(services)
}
};

const { shared } = createLangiumGrammarServices({ connection, ...NodeFileSystem }, LangiumGrammarSharedModule);
startLanguageServer(shared);
4 changes: 3 additions & 1 deletion packages/langium/src/default-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { DefaultDocumentValidator } from './validation/document-validator';
import { ValidationRegistry } from './validation/validation-registry';
import { DefaultAstNodeDescriptionProvider, DefaultReferenceDescriptionProvider } from './workspace/ast-descriptions';
import { DefaultAstNodeLocator } from './workspace/ast-node-locator';
import { DefaultConfigurationProvider } from './workspace/configuration';
import { DefaultDocumentBuilder } from './workspace/document-builder';
import { DefaultLangiumDocumentFactory, DefaultLangiumDocuments, DefaultTextDocumentFactory } from './workspace/documents';
import { FileSystemProvider } from './workspace/file-system-provider';
Expand Down Expand Up @@ -132,7 +133,8 @@ export function createDefaultSharedModule(context: DefaultSharedModuleContext):
IndexManager: (services) => new DefaultIndexManager(services),
WorkspaceManager: (services) => new DefaultWorkspaceManager(services),
FileSystemProvider: (services) => context.fileSystemProvider(services),
MutexLock: () => new MutexLock()
MutexLock: () => new MutexLock(),
ConfigurationProvider: (services) => new DefaultConfigurationProvider(services)
}
};
}
19 changes: 10 additions & 9 deletions packages/langium/src/grammar/langium-grammar-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext } from '../default-module';
import { inject, Module } from '../dependency-injection';
import { LangiumServices, LangiumSharedServices, PartialLangiumServices } from '../services';
import { LangiumServices, LangiumSharedServices, PartialLangiumServices, PartialLangiumSharedServices } from '../services';
import { LangiumGrammarGeneratedModule, LangiumGrammarGeneratedSharedModule } from './generated/module';
import { LangiumGrammarCodeActionProvider } from './lsp/grammar-code-actions';
import { LangiumGrammarScopeComputation, LangiumGrammarScopeProvider } from './langium-grammar-scope';
import { LangiumGrammarSemanticTokenProvider } from './lsp/grammar-semantic-tokens';
import { LangiumGrammarValidationRegistry, LangiumGrammarValidator } from './langium-grammar-validator';
import { LangiumGrammarCodeActionProvider } from './lsp/grammar-code-actions';
import { LangiumGrammarFoldingRangeProvider } from './lsp/grammar-folding-ranges';
import { LangiumGrammarFormatter } from './lsp/grammar-formatter';
import { LangiumGrammarReferences } from './references/grammar-references';
import { LangiumGrammarSemanticTokenProvider } from './lsp/grammar-semantic-tokens';
import { LangiumGrammarNameProvider } from './references/grammar-naming';
import { LangiumGrammarReferences } from './references/grammar-references';

export type LangiumGrammarAddedServices = {
validation: {
Expand Down Expand Up @@ -44,13 +44,14 @@ export const LangiumGrammarModule: Module<LangiumGrammarServices, PartialLangium
}
};

export function createLangiumGrammarServices(context: DefaultSharedModuleContext): {
shared: LangiumSharedServices,
grammar: LangiumGrammarServices
} {
export function createLangiumGrammarServices(context: DefaultSharedModuleContext,
sharedModule: Module<LangiumSharedServices, PartialLangiumSharedServices> = LangiumGrammarGeneratedSharedModule): {
shared: LangiumSharedServices,
grammar: LangiumGrammarServices
} {
const shared = inject(
createDefaultSharedModule(context),
LangiumGrammarGeneratedSharedModule
sharedModule,
);
const grammar = inject(
createDefaultModule({ shared }),
Expand Down
1 change: 1 addition & 0 deletions packages/langium/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ export * from './workspace/documents';
export * from './workspace/index-manager';
export * from './workspace/file-system-provider';
export * from './workspace/workspace-manager';
export * from './workspace/configuration';
8 changes: 8 additions & 0 deletions packages/langium/src/lsp/language-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export function startLanguageServer(services: LangiumSharedServices): void {
addHoverHandler(connection, services);
addSemanticTokenHandler(connection, services);
addExecuteCommandHandler(connection, services);
addConfigurationChangeHandler(connection, services);

connection.onInitialize(params => {
return services.lsp.LanguageServer.initialize(params);
Expand Down Expand Up @@ -291,6 +292,13 @@ export function addSemanticTokenHandler(connection: Connection, services: Langiu
services
));
}
export function addConfigurationChangeHandler(connection: Connection, services: LangiumSharedServices): void {
connection.onDidChangeConfiguration(change => {
if (change.settings) {
services.workspace.ConfigurationProvider.updateConfiguration(change);
}
});
}

export function addExecuteCommandHandler(connection: Connection, services: LangiumSharedServices): void {
const commandHandler = services.lsp.ExecuteCommandHandler;
Expand Down
2 changes: 2 additions & 0 deletions packages/langium/src/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type { DocumentValidator } from './validation/document-validator';
import type { ValidationRegistry } from './validation/validation-registry';
import type { AstNodeDescriptionProvider, ReferenceDescriptionProvider } from './workspace/ast-descriptions';
import type { AstNodeLocator } from './workspace/ast-node-locator';
import { ConfigurationProvider } from './workspace/configuration';
import type { DocumentBuilder } from './workspace/document-builder';
import type { LangiumDocumentFactory, LangiumDocuments, TextDocumentFactory } from './workspace/documents';
import type { FileSystemProvider } from './workspace/file-system-provider';
Expand Down Expand Up @@ -145,6 +146,7 @@ export type LangiumDefaultSharedServices = {
WorkspaceManager: WorkspaceManager
FileSystemProvider: FileSystemProvider
MutexLock: MutexLock
ConfigurationProvider: ConfigurationProvider
}
}

Expand Down
94 changes: 94 additions & 0 deletions packages/langium/src/workspace/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/******************************************************************************
* 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 { DidChangeConfigurationParams } from 'vscode-languageserver';
import { ConfigurationItem, DidChangeConfigurationNotification } from 'vscode-languageserver-protocol';
import { LangiumSharedServices } from '../services';

/* eslint-disable @typescript-eslint/no-explicit-any */

export interface ConfigurationProvider {
/**
* Returns a configuration value stored for the given language.
*
* @language the language id
* @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;
}

export class DefaultConfigurationProvider implements ConfigurationProvider {

protected settings: Record<string, Record<string, any>> = {};
protected workspaceConfig = false;
protected initialized = false;
protected readonly sharedServices: LangiumSharedServices;

constructor(services: LangiumSharedServices) {
this.sharedServices = services;
services.lsp.LanguageServer.onInitialize(params => {
this.workspaceConfig = params.capabilities.workspace?.configuration ?? false;
});
services.lsp.LanguageServer.onInitialized(_params => {
const languages = this.sharedServices.ServiceRegistry.all;
services.lsp.Connection?.client.register(DidChangeConfigurationNotification.type, {
// Listen to configuration changes for all languages
section: languages.map(lang => this.toSectionName(lang.LanguageMetaData.languageId))
});
});
}

protected async initialize(): Promise<void> {
const connection = this.sharedServices.lsp.Connection;
if (this.workspaceConfig && connection) {
const languages = this.sharedServices.ServiceRegistry.all;
const configToUpdate: ConfigurationItem[] = languages.map(lang => { return { section: this.toSectionName(lang.LanguageMetaData.languageId) }; });
// get workspace configurations (default scope URI)
const configs = await connection.workspace.getConfiguration(configToUpdate);
configToUpdate.forEach((conf, idx) => {
this.updateSectionConfiguration(conf.section!, configs[idx]);
});
}
this.initialized = true;
}

updateConfiguration(change: DidChangeConfigurationParams): void {
if(!change.settings) {
return;
}
Object.keys(change.settings).forEach(section => {
this.updateSectionConfiguration(section, change.settings[section]);
});
}

protected updateSectionConfiguration(section: string, configuration: any): void {
this.settings[section] = configuration;
}

async getConfiguration(language: string, configuration: string): Promise<any> {
if (!this.initialized) {
await this.initialize();
}
const sectionName = this.toSectionName(language);
if (!this.settings[sectionName]) {
this.settings[sectionName] = {};
}
return this.settings[sectionName][configuration];
}

protected toSectionName(languageId: string): string {
return `${languageId}`;
}
}

7 changes: 6 additions & 1 deletion packages/langium/src/workspace/workspace-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
protected readonly documentBuilder: DocumentBuilder;
protected readonly fileSystemProvider: FileSystemProvider;
protected readonly mutex: MutexLock;
protected folders: WorkspaceFolder[] | null;

constructor(services: LangiumSharedServices) {
this.serviceRegistry = services.ServiceRegistry;
Expand All @@ -46,9 +47,13 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
this.mutex = services.workspace.MutexLock;

services.lsp.LanguageServer.onInitialize(params => {
this.folders = params.workspaceFolders;
});

services.lsp.LanguageServer.onInitialized(_params => {
// Initialize the workspace even if there are no workspace folders
// We still want to load additional documents (language library or similar) during initialization
this.mutex.lock(token => this.initializeWorkspace(params.workspaceFolders ?? [], token));
this.mutex.lock(token => this.initializeWorkspace(this.folders ?? [], token));
});
}

Expand Down

0 comments on commit 4bc41ef

Please sign in to comment.