@@ -104,47 +104,52 @@ export class ClientCollection {
104104/**
105105 * creates a new client to replace one that crashed.
106106 */
107- public replace ( client :cpptools . Client , transferFileOwnership :boolean ) :cpptools . Client | undefined {
108- let key :string | undefined ;
109- for ( const pair of this . languageClients ) {
110- if ( pair [ 1 ] === client ) {
111- key = pair [ 0 ] ;
112- break ;
113- }
114- }
107+ public async recreateClients ( transferFileOwnership :boolean ) :Promise < void > {
108+
109+ // Swap out the map, so we are not changing it while iterating over it.
110+ const oldLanguageClients :Map < string , cpptools . Client > = this . languageClients ;
111+ this . languageClients = new Map < string , cpptools . Client > ( ) ;
115112
116- if ( key ) {
117- this . languageClients . delete ( key ) ;
113+ for ( const pair of oldLanguageClients ) {
114+ const client : cpptools . Client = pair [ 1 ] ;
118115
116+ let newClient :cpptools . Client ;
119117if ( transferFileOwnership ) {
120- // This will create a new Client for the workspace since we removed the old one from this.languageClients.
118+ newClient = this . createClient ( client . RootFolder , true ) ;
121119client . TrackedDocuments . forEach ( document => this . transferOwnership ( document , client ) ) ;
122- client . TrackedDocuments . clear ( ) ;
123120} else {
124- // Create an empty client that will continue to "own" files matching this workspace, but ignore all messages from VS Code.
125- this . languageClients . set ( key , cpptools . createNullClient ( ) ) ;
121+ newClient = cpptools . createNullClient ( ) ;
122+ }
123+
124+ if ( this . activeClient === client ) {
125+ // It cannot be undefined. If there is an active document, we activate it later.
126+ this . activeClient = newClient ;
126127}
127128
128- if ( this . activeClient === client && this . activeDocument ) {
129- this . activeClient = this . getClientFor ( this . activeDocument . uri ) ;
130- this . activeClient . activeDocumentChanged ( this . activeDocument ) ;
129+ if ( this . defaultClient === client ) {
130+ this . defaultClient = newClient ;
131131}
132132
133133client . dispose ( ) ;
134- return this . languageClients . get ( key ) ;
135- } else {
136- console . assert ( key , "unable to locate language client" ) ;
137- return undefined ;
134+ }
135+
136+ if ( this . activeDocument ) {
137+ this . activeClient = this . getClientFor ( this . activeDocument . uri ) ;
138+ await this . activeClient . activeDocumentChanged ( this . activeDocument ) ;
139+ this . activeClient . activate ( ) ;
138140}
139141}
140142
141- private onDidChangeWorkspaceFolders ( e ?:vscode . WorkspaceFoldersChangeEvent ) :void {
143+ private async onDidChangeWorkspaceFolders ( e ?:vscode . WorkspaceFoldersChangeEvent ) :Promise < void > {
142144const folderCount :number = vscode . workspace . workspaceFolders ?vscode . workspace . workspaceFolders . length :0 ;
143145if ( folderCount > 1 ) {
144146telemetry . logLanguageServerEvent ( "workspaceFoldersChange" , { "count" :folderCount . toString ( ) } ) ;
145147}
146148
147149if ( e !== undefined ) {
150+ let oldDefaultClient :cpptools . Client | undefined ;
151+ let needNewDefaultClient :boolean = false ;
152+
148153e . removed . forEach ( folder => {
149154const path :string = util . asFolder ( folder . uri ) ;
150155const client :cpptools . Client | undefined = this . languageClients . get ( path ) ;
@@ -154,30 +159,90 @@ export class ClientCollection {
154159// Transfer ownership of the client's documents to another client.
155160// (this includes calling textDocument/didOpen on the new client so that the server knows it's open too)
156161client . TrackedDocuments . forEach ( document => this . transferOwnership ( document , client ) ) ;
157- client . TrackedDocuments . clear ( ) ;
158-
159- if ( this . activeClient === client && this . activeDocument ) {
160- // Need to make a different client the active client.
161- this . activeClient = this . getClientFor ( this . activeDocument . uri ) ;
162- this . activeClient . activeDocumentChanged ( this . activeDocument ) ;
163- // may not need this, the navigation UI should not have changed.
164- // this.activeClient.selectionChanged(Range.create(vscode.window.activeTextEditor.selection.start, vscode.window.activeTextEditor.selection.end);
162+
163+ if ( this . activeClient === client ) {
164+ this . activeClient . deactivate ( ) ;
165165}
166166
167- client . dispose ( ) ;
167+ // Defer selecting a new default client until all adds and removes have been processed.
168+ if ( this . defaultClient === client ) {
169+ oldDefaultClient = client ;
170+ } else {
171+ client . dispose ( ) ;
172+ }
168173}
169174} ) ;
175+
170176e . added . forEach ( folder => {
171177const path :string = util . asFolder ( folder . uri ) ;
172178const client :cpptools . Client | undefined = this . languageClients . get ( path ) ;
173179if ( ! client ) {
174- const newClient :cpptools . Client = cpptools . createClient ( this , folder ) ;
175- this . languageClients . set ( path , newClient ) ;
176- newClient . deactivate ( ) ; // e.g. prevent the current config from switching.
177- const defaultClient :cpptools . DefaultClient = < cpptools . DefaultClient > newClient ;
178- defaultClient . sendAllSettings ( ) ;
180+ this . createClient ( folder , true ) ;
179181}
180182} ) ;
183+
184+ // Note: VS Code currently reloads the extension whenever the primary (first) workspace folder is added or removed.
185+ // So the following handling of changes to defaultClient are likely unnecessary, unless the behavior of
186+ // VS Code changes. The handling of changes to activeClient are still needed.
187+
188+ if ( oldDefaultClient ) {
189+ const uri :vscode . Uri | undefined = oldDefaultClient . RootUri ;
190+ // uri will be set.
191+ if ( uri ) {
192+ // Check if there is now another more appropriate client to use.
193+ this . defaultClient = this . getClientFor ( uri ) ;
194+ needNewDefaultClient = this . defaultClient === oldDefaultClient ;
195+ }
196+ oldDefaultClient . dispose ( ) ;
197+ } else {
198+ const defaultDefaultClient :cpptools . Client | undefined = this . languageClients . get ( defaultClientKey ) ;
199+ if ( defaultDefaultClient ) {
200+ // If there is an entry in languageClients for defaultClientKey, we were not previously tracking any workspace folders.
201+ this . languageClients . delete ( defaultClientKey ) ;
202+ needNewDefaultClient = true ;
203+ if ( this . activeClient === this . defaultClient ) {
204+ // Redundant deactivation should be OK.
205+ this . activeClient . deactivate ( ) ;
206+ }
207+ }
208+ }
209+
210+ if ( needNewDefaultClient ) {
211+ // Use the first workspaceFolder, if any.
212+ if ( vscode . workspace . workspaceFolders && vscode . workspace . workspaceFolders . length > 0 ) {
213+ const key :string = util . asFolder ( vscode . workspace . workspaceFolders [ 0 ] . uri ) ;
214+ const client :cpptools . Client | undefined = this . languageClients . get ( key ) ;
215+ if ( ! client ) {
216+ // This should not occur. If there is a workspace folder, we should have a client for it by now.
217+ throw new Error ( "Failed to construct default client" ) ;
218+ }
219+ this . defaultClient = client ;
220+ } else {
221+ // The user removed the last workspace folder. Create a new default defaultClient/activeClient
222+ // using the same approach as used in the constructor.
223+ this . defaultClient = cpptools . createClient ( this ) ;
224+ this . languageClients . set ( defaultClientKey , this . defaultClient ) ;
225+ this . defaultClient . deactivate ( ) ;
226+ if ( this . activeDocument ) {
227+ // Only change activeClient if it would not be changed by the later check.
228+ this . activeClient . deactivate ( ) ;
229+ this . activeClient = this . defaultClient ;
230+ }
231+ }
232+ }
233+
234+ // Ensure the best client for the currently active document is activated.
235+ if ( this . activeDocument ) {
236+ const newActiveClient :cpptools . Client = this . getClientFor ( this . activeDocument . uri ) ;
237+ // If a client is newly created here, it will be activated by default.
238+ if ( this . activeClient !== newActiveClient ) {
239+ // Redundant deactivate should be OK.
240+ this . activeClient . deactivate ( ) ;
241+ this . activeClient = newActiveClient ;
242+ await this . activeClient . activeDocumentChanged ( this . activeDocument ) ;
243+ this . activeClient . activate ( ) ;
244+ }
245+ }
181246}
182247}
183248
@@ -190,6 +255,10 @@ export class ClientCollection {
190255
191256public getClientFor ( uri :vscode . Uri ) :cpptools . Client {
192257const folder :vscode . WorkspaceFolder | undefined = uri ?vscode . workspace . getWorkspaceFolder ( uri ) :undefined ;
258+ return this . getClientForFolder ( folder ) ;
259+ }
260+
261+ public getClientForFolder ( folder ?:vscode . WorkspaceFolder ) :cpptools . Client {
193262if ( ! folder ) {
194263return this . defaultClient ;
195264} else {
@@ -198,13 +267,21 @@ export class ClientCollection {
198267if ( client ) {
199268return client ;
200269}
201- const newClient :cpptools . Client = cpptools . createClient ( this , folder ) ;
202- this . languageClients . set ( key , newClient ) ;
203- getCustomConfigProviders ( ) . forEach ( provider => newClient . onRegisterCustomConfigurationProvider ( provider ) ) ;
204- const defaultClient :cpptools . DefaultClient = < cpptools . DefaultClient > newClient ;
205- defaultClient . sendAllSettings ( ) ;
206- return newClient ;
270+ return this . createClient ( folder ) ;
271+ }
272+ }
273+
274+ public createClient ( folder ?:vscode . WorkspaceFolder , deactivated ?:boolean ) :cpptools . Client {
275+ const newClient :cpptools . Client = cpptools . createClient ( this , folder ) ;
276+ if ( deactivated ) {
277+ newClient . deactivate ( ) ; // e.g. prevent the current config from switching.
207278}
279+ const key :string = folder ?util . asFolder ( folder . uri ) :defaultClientKey ;
280+ this . languageClients . set ( key , newClient ) ;
281+ getCustomConfigProviders ( ) . forEach ( provider => newClient . onRegisterCustomConfigurationProvider ( provider ) ) ;
282+ const defaultClient :cpptools . DefaultClient = < cpptools . DefaultClient > newClient ;
283+ defaultClient . sendAllSettings ( ) ;
284+ return newClient ;
208285}
209286
210287private onDidCloseTextDocument ( document :vscode . TextDocument ) :void {