@@ -620,7 +620,7 @@ def _ServerToClientRequest( self, request ):
620620 elif method == 'workspace/workspaceFolders' :
621621 self .SendResponse (
622622 lsp .Accept ( request ,
623- lsp .WorkspaceFolders ( self ._project_directory ) ) )
623+ lsp .WorkspaceFolders ( * self ._server_workspace_dirs ) ) )
624624 else : # method unknown - reject
625625 self .SendResponse ( lsp .Reject ( request , lsp .Errors .MethodNotFound ) )
626626 return
@@ -890,6 +890,7 @@ class LanguageServerCompleter( Completer ):
890890 - ConvertNotificationToMessage
891891 - GetCompleterName
892892 - GetProjectDirectory
893+ - GetWorkspaceForFilepath
893894 - GetProjectRootFiles
894895 - GetTriggerCharacters
895896 - GetDefaultNotificationHandler
@@ -1043,12 +1044,14 @@ def ServerReset( self ):
10431044 self ._initialize_event = threading .Event ()
10441045 self ._on_initialize_complete_handlers = []
10451046 self ._server_capabilities = None
1047+ self ._workspace_notification_supported = False
10461048 self ._is_completion_provider = False
10471049 self ._resolve_completion_items = False
10481050 self ._project_directory = None
10491051 self ._settings = {}
10501052 self ._extra_conf_dir = None
10511053 self ._semantic_token_atlas = None
1054+ self ._server_workspace_dirs = set ()
10521055
10531056
10541057 def GetCompleterName ( self ):
@@ -1076,6 +1079,7 @@ def _StartServerNoLock( self, request_data ):
10761079 self .GetCommandLine () )
10771080
10781081 self ._project_directory = self .GetProjectDirectory ( request_data )
1082+ self ._server_workspace_dirs .add ( self ._project_directory )
10791083
10801084 if self ._connection_type == 'tcp' :
10811085 if self .GetCommandLine ():
@@ -2147,6 +2151,14 @@ def _RefreshFileContentsUnderLock( self, file_name, contents, file_types ):
21472151 action )
21482152
21492153 if action == lsp .ServerFileState .OPEN_FILE :
2154+ # First check if we need to inform the server of a new workspace.
2155+ if self ._workspace_notification_supported :
2156+ workspace_for_file = self .GetWorkspaceForFilepath ( file_name )
2157+ if workspace_for_file not in self ._server_workspace_dirs :
2158+ self ._server_workspace_dirs .add ( workspace_for_file )
2159+ msg = lsp .DidChangeWorkspaceFolders ( workspace_for_file )
2160+ self .GetConnection ().SendNotification ( msg )
2161+
21502162 msg = lsp .DidOpenTextDocument ( file_state , file_types , contents )
21512163
21522164 self .GetConnection ().SendNotification ( msg )
@@ -2283,7 +2295,7 @@ def GetProjectDirectory( self, request_data ):
22832295 - If the user specified 'project_directory' in their extra conf
22842296 'Settings', use that.
22852297 - try to find files from GetProjectRootFiles and use the
2286- first directory from there
2298+ first directory from there. (From GetWorkspaceForFilepath)
22872299 - if there's an extra_conf file, use that directory
22882300 - otherwise if we know the client's cwd, use that
22892301 - otherwise use the directory of the file that we just opened
@@ -2298,20 +2310,36 @@ def GetProjectDirectory( self, request_data ):
22982310 return utils .AbsolutePath ( self ._settings [ 'project_directory' ],
22992311 self ._extra_conf_dir )
23002312
2301- project_root_files = self .GetProjectRootFiles ()
2302- if project_root_files :
2303- for folder in utils .PathsToAllParentFolders ( request_data [ 'filepath' ] ):
2304- for root_file in project_root_files :
2305- if os .path .isfile ( os .path .join ( folder , root_file ) ):
2306- return folder
2313+ filepath = request_data [ 'filepath' ]
2314+ workspace_path = self .GetWorkspaceForFilepath ( filepath , strict = True )
2315+ if workspace_path :
2316+ return workspace_path
23072317
23082318 if self ._extra_conf_dir :
23092319 return self ._extra_conf_dir
23102320
23112321 if 'working_dir' in request_data :
23122322 return request_data [ 'working_dir' ]
23132323
2314- return os .path .dirname ( request_data [ 'filepath' ] )
2324+ return os .path .dirname ( filepath )
2325+
2326+
2327+ def GetWorkspaceForFilepath ( self , filepath , strict = False ):
2328+ """Return the workspace of the provided filepath. This could be a subproject
2329+ or a completely unrelated project to the root directory.
2330+ Like GetProjectDirectory, can be overridden by a concrete LSP completer.
2331+ By default we try to find the first parent directory that contains any file
2332+ mentioned in GetProjectRootFiles().
2333+ `strict` function argument was useful for allowing GetProjectDirectory to
2334+ reuse this implementation.
2335+ """
2336+ project_root_files = self .GetProjectRootFiles ()
2337+ if project_root_files :
2338+ for folder in utils .PathsToAllParentFolders ( filepath ):
2339+ for root_file in project_root_files :
2340+ if os .path .isfile ( os .path .join ( folder , root_file ) ):
2341+ return folder
2342+ return None if strict else os .path .dirname ( filepath )
23152343
23162344
23172345 def _SendInitialize ( self , request_data ):
@@ -2333,10 +2361,15 @@ def _SendInitialize( self, request_data ):
23332361 # the settings on the Initialize request are somehow subtly different from
23342362 # the settings supplied in didChangeConfiguration, though it's not exactly
23352363 # clear how/where that is specified.
2364+ additional_workspace_dirs = self ._settings .get (
2365+ 'additional_workspace_dirs' ,
2366+ [] )
2367+ self ._server_workspace_dirs .update ( additional_workspace_dirs )
23362368 msg = lsp .Initialize ( request_id ,
23372369 self ._project_directory ,
23382370 self .ExtraCapabilities (),
2339- self ._settings .get ( 'ls' , {} ) )
2371+ self ._settings .get ( 'ls' , {} ),
2372+ additional_workspace_dirs )
23402373
23412374 def response_handler ( response , message ):
23422375 if message is None :
@@ -2382,6 +2415,9 @@ def _HandleInitializeInPollThread( self, response ):
23822415 when the initialize request receives a response."""
23832416 with self ._server_info_mutex :
23842417 self ._server_capabilities = response [ 'result' ][ 'capabilities' ]
2418+ self ._workspace_notification_supported = (
2419+ _ServerSupportsWorkspaceFoldersChangeNotif (
2420+ self ._server_capabilities ) )
23852421 self ._resolve_completion_items = self ._ShouldResolveCompletionItems ()
23862422
23872423 if self ._resolve_completion_items :
@@ -2925,6 +2961,8 @@ def ServerStateDescription():
29252961 ServerStateDescription () ),
29262962 responses .DebugInfoItem ( 'Project Directory' ,
29272963 self ._project_directory ),
2964+ responses .DebugInfoItem ( 'Open Workspaces' ,
2965+ self ._server_workspace_dirs ),
29282966 responses .DebugInfoItem (
29292967 'Settings' ,
29302968 json .dumps ( self ._settings .get ( 'ls' , {} ),
@@ -3565,6 +3603,12 @@ def DecodeModifiers( self, tokenModifiers ):
35653603 return tokens
35663604
35673605
3606+ def _ServerSupportsWorkspaceFoldersChangeNotif ( server_capabilities ):
3607+ workspace = server_capabilities .get ( 'workspace' , {} )
3608+ workspace_folders = workspace .get ( 'workspaceFolders' , {} )
3609+ return _IsCapabilityProvided ( workspace_folders , 'changeNotifications' )
3610+
3611+
35683612def _IsCapabilityProvided ( capabilities , query ):
35693613 capability = capabilities .get ( query )
35703614 return bool ( capability ) or capability == {}
0 commit comments