@@ -13,9 +13,9 @@ use tower_lsp_server::{
1313 lsp_types:: {
1414 CodeActionParams , CodeActionResponse , ConfigurationItem , Diagnostic ,
1515 DidChangeConfigurationParams , DidChangeTextDocumentParams , DidChangeWatchedFilesParams ,
16- DidCloseTextDocumentParams , DidOpenTextDocumentParams , DidSaveTextDocumentParams ,
17- ExecuteCommandParams , InitializeParams , InitializeResult , InitializedParams , ServerInfo ,
18- Uri , WorkspaceEdit ,
16+ DidChangeWorkspaceFoldersParams , DidCloseTextDocumentParams , DidOpenTextDocumentParams ,
17+ DidSaveTextDocumentParams , ExecuteCommandParams , InitializeParams , InitializeResult ,
18+ InitializedParams , ServerInfo , Uri , WorkspaceEdit ,
1919 } ,
2020} ;
2121use worker:: WorkspaceWorker ;
@@ -79,26 +79,89 @@ impl Options {
7979}
8080
8181impl LanguageServer for Backend {
82- #[ expect( deprecated) ] // TODO: FIXME
82+ #[ expect( deprecated) ] // `params.root_uri` is deprecated, we are only falling back to it if no workspace folder is provided
8383 async fn initialize ( & self , params : InitializeParams ) -> Result < InitializeResult > {
84+ // initialization_options can be anything, so we are requesting `workspace/configuration` when no initialize options are provided
8485 let options = params. initialization_options . and_then ( |mut value| {
8586 let settings = value. get_mut ( "settings" ) ?. take ( ) ;
8687 serde_json:: from_value :: < Options > ( settings) . ok ( )
8788 } ) ;
8889
89- // ToDo: add support for multiple workspace folders
90- // maybe fallback when the client does not support it
91- let root_worker =
92- WorkspaceWorker :: new ( & params. root_uri . unwrap ( ) , options. clone ( ) . unwrap_or_default ( ) ) ;
93-
94- * self . workspace_workers . lock ( ) . await = vec ! [ root_worker] ;
95-
96- if let Some ( value) = options {
90+ if let Some ( value) = & options {
9791 info ! ( "initialize: {value:?}" ) ;
9892 info ! ( "language server version: {:?}" , env!( "CARGO_PKG_VERSION" ) ) ;
9993 }
10094
10195 let capabilities = Capabilities :: from ( params. capabilities ) ;
96+
97+ if let Some ( workspace_folder) = params. workspace_folders . as_ref ( ) {
98+ if workspace_folder. is_empty ( ) {
99+ return Err ( Error :: invalid_params ( "workspace folder is empty" ) ) ;
100+ }
101+
102+ let mut workers = vec ! [ ] ;
103+ // when we have only one workspace folder and the client already passed the configuration
104+ if workspace_folder. len ( ) == 1 && options. is_some ( ) {
105+ let root_worker =
106+ WorkspaceWorker :: new ( & workspace_folder. first ( ) . unwrap ( ) . uri , options. unwrap ( ) ) ;
107+ workers. push ( root_worker) ;
108+ // else check if the client support workspace configuration requests
109+ // and we can request the configuration for each workspace folder
110+ } else if capabilities. workspace_configuration {
111+ let configs = self
112+ . request_workspace_configuration (
113+ workspace_folder. iter ( ) . map ( |w| w. uri . clone ( ) ) . collect ( ) ,
114+ )
115+ . await ;
116+ for ( index, folder) in workspace_folder. iter ( ) . enumerate ( ) {
117+ let workspace_options = configs
118+ . get ( index)
119+ // when there is no valid index fallback to the initialize options
120+ . unwrap_or ( & options)
121+ . clone ( )
122+ // no valid index or initialize option, still fallback to default
123+ . unwrap_or_default ( ) ;
124+
125+ workers. push ( WorkspaceWorker :: new ( & folder. uri , workspace_options) ) ;
126+ }
127+ } else {
128+ for folder in workspace_folder {
129+ workers. push ( WorkspaceWorker :: new (
130+ & folder. uri ,
131+ options. clone ( ) . unwrap_or_default ( ) ,
132+ ) ) ;
133+ }
134+ }
135+
136+ * self . workspace_workers . lock ( ) . await = workers;
137+ // fallback to root uri if no workspace folder is provided
138+ } else if let Some ( root_uri) = params. root_uri . as_ref ( ) {
139+ // use the initialize options if the client does not support workspace configuration or already provided one
140+ let root_options = if options. is_some ( ) {
141+ options. clone ( ) . unwrap ( )
142+ // check if the client support workspace configuration requests
143+ } else if capabilities. workspace_configuration {
144+ let configs = self . request_workspace_configuration ( vec ! [ root_uri. clone( ) ] ) . await ;
145+ configs
146+ . first ( )
147+ // options is already none, no need to pass it here
148+ . unwrap_or ( & None )
149+ // no valid index or initialize option, still fallback to default
150+ . clone ( )
151+ . unwrap_or_default ( )
152+ // no initialize options provided and the client does not support workspace configuration
153+ // fallback to default
154+ } else {
155+ Options :: default ( )
156+ } ;
157+
158+ let root_worker = WorkspaceWorker :: new ( root_uri, root_options) ;
159+ * self . workspace_workers . lock ( ) . await = vec ! [ root_worker] ;
160+ // one of the two (workspace folder or root_uri) must be provided
161+ } else {
162+ return Err ( Error :: invalid_params ( "no workspace folder or root uri" ) ) ;
163+ }
164+
102165 self . capabilities . set ( capabilities. clone ( ) ) . map_err ( |err| {
103166 let message = match err {
104167 SetError :: AlreadyInitializedError ( _) => {
@@ -117,51 +180,55 @@ impl LanguageServer for Backend {
117180 } )
118181 }
119182
183+ async fn initialized ( & self , _params : InitializedParams ) {
184+ debug ! ( "oxc initialized." ) ;
185+ }
186+
187+ async fn shutdown ( & self ) -> Result < ( ) > {
188+ self . clear_all_diagnostics ( ) . await ;
189+ Ok ( ( ) )
190+ }
191+
120192 async fn did_change_configuration ( & self , params : DidChangeConfigurationParams ) {
121193 let workers = self . workspace_workers . lock ( ) . await ;
194+ let params_options = serde_json:: from_value :: < Options > ( params. settings ) . ok ( ) ;
122195
123- // when we have only workspace folder, apply to it
124- // ToDo: check if this is really safe because the client could still pass an empty settings
125- if workers. len ( ) == 1 {
196+ // when we have only workspace folder and the client provided us the configuration
197+ // we can just update the worker with the new configuration
198+ if workers. len ( ) == 1 && params_options . is_some ( ) {
126199 let worker = workers. first ( ) . unwrap ( ) ;
127- worker. did_change_configuration ( params . settings ) . await ;
200+ worker. did_change_configuration ( & params_options . unwrap ( ) ) . await ;
128201
129202 // else check if the client support workspace configuration requests so we can only restart only the needed workers
130203 } else if self
131204 . capabilities
132205 . get ( )
133206 . is_some_and ( |capabilities| capabilities. workspace_configuration )
134207 {
135- let mut config_items = vec ! [ ] ;
136- for worker in workers. iter ( ) {
137- let Some ( uri) = worker. get_root_uri ( ) else {
138- continue ;
139- } ;
140- // ToDo: this is broken in VSCode. Check how we can get the language server configuration from the client
141- // changing `section` to `oxc` will return the client configuration.
142- config_items. push ( ConfigurationItem {
143- scope_uri : Some ( uri) ,
144- section : Some ( "oxc_language_server" . into ( ) ) ,
145- } ) ;
146- }
147-
148- let Ok ( configs) = self . client . configuration ( config_items) . await else {
149- debug ! ( "failed to get configuration" ) ;
150- return ;
151- } ;
152-
208+ let configs = self
209+ . request_workspace_configuration (
210+ workers. iter ( ) . map ( worker:: WorkspaceWorker :: get_root_uri) . collect ( ) ,
211+ )
212+ . await ;
153213 // we expect that the client is sending all the configuration items in order and completed
154214 // this is a LSP specification and errors should be reported on the client side
155215 for ( index, worker) in workers. iter ( ) . enumerate ( ) {
156- let config = & configs[ index] ;
157- worker. did_change_configuration ( config. clone ( ) ) . await ;
216+ // get the index or fallback to the initialize options
217+ let config = configs. get ( index) . unwrap_or ( & params_options) ;
218+
219+ // change anything
220+ let Some ( config) = config else {
221+ continue ;
222+ } ;
223+
224+ worker. did_change_configuration ( config) . await ;
158225 }
159226
160227 // we have multiple workspace folders and the client does not support workspace configuration requests
161- // we assume that every workspace is under effect
162- } else {
228+ // the client must provide a configuration change or else we do not know what to do
229+ } else if params_options . is_some ( ) {
163230 for worker in workers. iter ( ) {
164- worker. did_change_configuration ( params . settings . clone ( ) ) . await ;
231+ worker. did_change_configuration ( & params_options . clone ( ) . unwrap ( ) ) . await ;
165232 }
166233 }
167234 }
@@ -205,13 +272,36 @@ impl LanguageServer for Backend {
205272 self . publish_all_diagnostics ( x) . await ;
206273 }
207274
208- async fn initialized ( & self , _params : InitializedParams ) {
209- debug ! ( "oxc initialized." ) ;
210- }
275+ async fn did_change_workspace_folders ( & self , params : DidChangeWorkspaceFoldersParams ) {
276+ let workers = self . workspace_workers . lock ( ) . await ;
277+ let mut cleared_diagnostics = vec ! [ ] ;
278+ for folder in params. event . removed {
279+ let Some ( ( index, worker) ) = workers
280+ . iter ( )
281+ . enumerate ( )
282+ . find ( |( _, worker) | worker. is_responsible_for_uri ( & folder. uri ) )
283+ else {
284+ continue ;
285+ } ;
286+ cleared_diagnostics. extend ( worker. get_clear_diagnostics ( ) . await ) ;
287+ self . workspace_workers . lock ( ) . await . remove ( index) ;
288+ }
211289
212- async fn shutdown ( & self ) -> Result < ( ) > {
213- self . clear_all_diagnostics ( ) . await ;
214- Ok ( ( ) )
290+ self . publish_all_diagnostics ( & cleared_diagnostics) . await ;
291+
292+ // ToDo: check if the client support workspace configuration requests
293+ let configurations = self
294+ . request_workspace_configuration (
295+ params. event . added . iter ( ) . map ( |w| w. uri . clone ( ) ) . collect ( ) ,
296+ )
297+ . await ;
298+
299+ for ( index, folder) in params. event . added . iter ( ) . enumerate ( ) {
300+ let option = configurations. get ( index) . unwrap_or ( & None ) ;
301+ let option = option. clone ( ) . unwrap_or ( Options :: default ( ) ) ;
302+
303+ self . workspace_workers . lock ( ) . await . push ( WorkspaceWorker :: new ( & folder. uri , option) ) ;
304+ }
215305 }
216306
217307 async fn did_save ( & self , params : DidSaveTextDocumentParams ) {
@@ -346,6 +436,37 @@ impl LanguageServer for Backend {
346436}
347437
348438impl Backend {
439+ /// Request the workspace configuration from the client
440+ /// and return the options for each workspace folder.
441+ /// The check if the client support workspace configuration, should be done before.
442+ async fn request_workspace_configuration ( & self , uris : Vec < Uri > ) -> Vec < Option < Options > > {
443+ let length = uris. len ( ) ;
444+ let config_items = uris
445+ . into_iter ( )
446+ . map ( |uri| ConfigurationItem {
447+ scope_uri : Some ( uri) ,
448+ section : Some ( "oxc_language_server" . into ( ) ) ,
449+ } )
450+ . collect :: < Vec < _ > > ( ) ;
451+
452+ let Ok ( configs) = self . client . configuration ( config_items) . await else {
453+ debug ! ( "failed to get configuration" ) ;
454+ // return none for each workspace folder
455+ return vec ! [ None ; length] ;
456+ } ;
457+
458+ let mut options = vec ! [ ] ;
459+ for config in configs {
460+ options. push ( serde_json:: from_value :: < Options > ( config) . ok ( ) ) ;
461+ }
462+
463+ debug_assert ! (
464+ options. len( ) == length,
465+ "the number of configuration items should be the same as the number of workspace folders"
466+ ) ;
467+
468+ options
469+ }
349470 // clears all diagnostics for workspace folders
350471 async fn clear_all_diagnostics ( & self ) {
351472 let mut cleared_diagnostics = vec ! [ ] ;
0 commit comments