44 * terms of the MIT License, which is available in the project root.
55 ******************************************************************************/
66
7- import type {
8- ConfigurationItem , DidChangeConfigurationParams , DidChangeConfigurationRegistrationOptions , InitializeParams ,
9- InitializedParams
10- } from 'vscode-languageserver-protocol' ;
7+ import type { ConfigurationItem , DidChangeConfigurationParams , DidChangeConfigurationRegistrationOptions , InitializeParams , InitializedParams } from 'vscode-languageserver-protocol' ;
118import type { ServiceRegistry } from '../service-registry.js' ;
129import type { LangiumSharedCoreServices } from '../services.js' ;
1310
@@ -46,7 +43,7 @@ export interface ConfigurationProvider {
4643
4744export interface ConfigurationInitializedParams extends InitializedParams {
4845 register ?: ( params : DidChangeConfigurationRegistrationOptions ) => void ,
49- getConfiguration ?: ( configuration : ConfigurationItem [ ] ) => Promise < any >
46+ fetchConfiguration ?: ( configuration : ConfigurationItem [ ] ) => Promise < any >
5047}
5148
5249/**
@@ -57,6 +54,7 @@ export class DefaultConfigurationProvider implements ConfigurationProvider {
5754 protected readonly serviceRegistry : ServiceRegistry ;
5855 protected settings : Record < string , Record < string , any > > = { } ;
5956 protected workspaceConfig = false ;
57+ protected fetchConfiguration : ( ( configurations : ConfigurationItem [ ] ) => Promise < any > ) | undefined ;
6058
6159 constructor ( services : LangiumSharedCoreServices ) {
6260 this . serviceRegistry = services . ServiceRegistry ;
@@ -79,20 +77,34 @@ export class DefaultConfigurationProvider implements ConfigurationProvider {
7977 } ) ;
8078 }
8179
82- if ( params . getConfiguration ) {
83- // params.getConfiguration(...) is a function to be provided by the calling language server for the sake of
84- // decoupling this implementation from the concrete LSP implementations, specifically the LSP Connection
80+ // params.fetchConfiguration(...) is a function to be provided by the calling language server for the sake of
81+ // decoupling this implementation from the concrete LSP implementations, specifically the LSP Connection
82+ this . fetchConfiguration = params . fetchConfiguration ;
8583
86- const configToUpdate = this . serviceRegistry . all . map ( lang => < ConfigurationItem > {
87- // Fetch the configuration changes for all languages
88- section : this . toSectionName ( lang . LanguageMetaData . languageId )
89- } ) ;
90- // get workspace configurations (default scope URI)
91- const configs = await params . getConfiguration ( configToUpdate ) ;
92- configToUpdate . forEach ( ( conf , idx ) => {
93- this . updateSectionConfiguration ( conf . section ! , configs [ idx ] ) ;
94- } ) ;
95- }
84+ // awaiting the fetch of the initial configuration data must not happen here, because it would block the
85+ // initialization of the language server, and may lead to out-of-order processing of subsequent messages from the language client;
86+ // fetching the initial configuration in a non-blocking manner might cause a read-before-write problem
87+ // in case the workspace initialization relies on that configuration data (which is the case for the grammar language server);
88+ // consequently, we fetch the initial configuration data on blocking manner on-demand, when the first configuration value is read,
89+ // see below in #getConfiguration(...);
90+ }
91+ }
92+
93+ protected async initializeConfiguration ( ) : Promise < void > {
94+ if ( this . fetchConfiguration ) {
95+ const configToUpdate = this . serviceRegistry . all . map ( lang => < ConfigurationItem > {
96+ // Fetch the configuration changes for all languages
97+ section : this . toSectionName ( lang . LanguageMetaData . languageId )
98+ } ) ;
99+
100+ // get workspace configurations (default scope URI)
101+ const configs = await this . fetchConfiguration ( configToUpdate ) ;
102+ configToUpdate . forEach ( ( conf , idx ) => {
103+ this . updateSectionConfiguration ( conf . section ! , configs [ idx ] ) ;
104+ } ) ;
105+
106+ // reset the 'fetchConfiguration' to 'undefined' again in order to prevent further 'fetch' attempts
107+ this . fetchConfiguration = undefined ;
96108 }
97109 }
98110
@@ -122,6 +134,8 @@ export class DefaultConfigurationProvider implements ConfigurationProvider {
122134 * @param configuration Configuration name
123135 */
124136 async getConfiguration ( language : string , configuration : string ) : Promise < any > {
137+ await this . initializeConfiguration ( ) ;
138+
125139 const sectionName = this . toSectionName ( language ) ;
126140 if ( this . settings [ sectionName ] ) {
127141 return this . settings [ sectionName ] [ configuration ] ;
0 commit comments