@@ -28,6 +28,7 @@ import { startSpinner, stopSpinner } from './spinner';
28
28
import { activateTaskProvider , Execution , runRlsCommand } from './tasks' ;
29
29
import { withWsl } from './utils/child_process' ;
30
30
import { uriWindowsToWsl , uriWslToWindows } from './utils/wslpath' ;
31
+ import * as workspace_util from './workspace_util' ;
31
32
32
33
/**
33
34
* Parameter type to `window/progress` request as issued by the RLS.
@@ -44,10 +45,10 @@ interface ProgressParams {
44
45
export async function activate ( context : ExtensionContext ) {
45
46
context . subscriptions . push ( configureLanguage ( ) ) ;
46
47
47
- workspace . onDidOpenTextDocument ( doc => didOpenTextDocument ( doc , context ) ) ;
48
- workspace . textDocuments . forEach ( doc => didOpenTextDocument ( doc , context ) ) ;
48
+ workspace . onDidOpenTextDocument ( doc => whenOpeningTextDocument ( doc , context ) ) ;
49
+ workspace . textDocuments . forEach ( doc => whenOpeningTextDocument ( doc , context ) ) ;
49
50
workspace . onDidChangeWorkspaceFolders ( e =>
50
- didChangeWorkspaceFolders ( e , context ) ,
51
+ whenChangingWorkspaceFolders ( e , context ) ,
51
52
) ;
52
53
}
53
54
@@ -56,7 +57,7 @@ export async function deactivate() {
56
57
}
57
58
58
59
// Taken from https://github.com/Microsoft/vscode-extension-samples/blob/master/lsp-multi-server-sample/client/src/extension.ts
59
- function didOpenTextDocument (
60
+ function whenOpeningTextDocument (
60
61
document : TextDocument ,
61
62
context : ExtensionContext ,
62
63
) {
@@ -69,23 +70,36 @@ function didOpenTextDocument(
69
70
if ( ! folder ) {
70
71
return ;
71
72
}
72
- if (
73
- workspace
74
- . getConfiguration ( )
75
- . get < boolean > ( 'rust-client.nestedMultiRootConfigInOutermost' , true )
76
- ) {
73
+
74
+ const inMultiProjectMode = workspace
75
+ . getConfiguration ( )
76
+ . get < boolean > ( 'rust-client.enableMultiProjectSetup' , false ) ;
77
+
78
+ const inNestedOuterProjectMode = workspace
79
+ . getConfiguration ( )
80
+ . get < boolean > ( 'rust-client.nestedMultiRootConfigInOutermost' , true ) ;
81
+
82
+ if ( inMultiProjectMode ) {
83
+ folder = workspace_util . nearestParentWorkspace ( folder , document . uri . fsPath ) ;
84
+ } else if ( inNestedOuterProjectMode ) {
77
85
folder = getOuterMostWorkspaceFolder ( folder ) ;
78
86
}
79
- // folder = getCargoTomlWorkspace(folder, document.uri.fsPath);
87
+
80
88
if ( ! folder ) {
81
89
stopSpinner ( `RLS: Cargo.toml missing` ) ;
82
90
return ;
83
91
}
84
92
85
- if ( ! workspaces . has ( folder . uri ) ) {
93
+ const folderPath = folder . uri . toString ( ) ;
94
+
95
+ if ( ! workspaces . has ( folderPath ) ) {
86
96
const workspace = new ClientWorkspace ( folder ) ;
87
- workspaces . set ( folder . uri , workspace ) ;
97
+ activeWorkspace = workspace ;
98
+ workspaces . set ( folderPath , workspace ) ;
88
99
workspace . start ( context ) ;
100
+ } else {
101
+ const ws = workspaces . get ( folderPath ) ;
102
+ activeWorkspace = typeof ws === 'undefined' ? null : ws ;
89
103
}
90
104
}
91
105
@@ -94,6 +108,7 @@ function didOpenTextDocument(
94
108
let _sortedWorkspaceFolders : string [ ] | undefined ;
95
109
96
110
function sortedWorkspaceFolders ( ) : string [ ] {
111
+ // TODO: decouple the global state such that it can be moved to workspace_util
97
112
if ( ! _sortedWorkspaceFolders && workspace . workspaceFolders ) {
98
113
_sortedWorkspaceFolders = workspace . workspaceFolders
99
114
. map ( folder => {
@@ -110,39 +125,8 @@ function sortedWorkspaceFolders(): string[] {
110
125
return _sortedWorkspaceFolders || [ ] ;
111
126
}
112
127
113
- // function getCargoTomlWorkspace(cur_workspace: WorkspaceFolder, file_path: string): WorkspaceFolder {
114
- // if (!cur_workspace) {
115
- // return cur_workspace;
116
- // }
117
-
118
- // const workspace_root = path.parse(cur_workspace.uri.fsPath).dir;
119
- // const root_manifest = path.join(workspace_root, 'Cargo.toml');
120
- // if (fs.existsSync(root_manifest)) {
121
- // return cur_workspace;
122
- // }
123
-
124
- // let current = file_path;
125
-
126
- // while (true) {
127
- // const old = current;
128
- // current = path.dirname(current);
129
- // if (old == current) {
130
- // break;
131
- // }
132
- // if (workspace_root == path.parse(current).dir) {
133
- // break;
134
- // }
135
-
136
- // const cargo_path = path.join(current, 'Cargo.toml');
137
- // if (fs.existsSync(cargo_path)) {
138
- // return { ...cur_workspace, uri: Uri.parse(current) };
139
- // }
140
- // }
141
-
142
- // return cur_workspace;
143
- // }
144
-
145
128
function getOuterMostWorkspaceFolder ( folder : WorkspaceFolder ) : WorkspaceFolder {
129
+ // TODO: decouple the global state such that it can be moved to workspace_util
146
130
const sorted = sortedWorkspaceFolders ( ) ;
147
131
for ( const element of sorted ) {
148
132
let uri = folder . uri . toString ( ) ;
@@ -156,7 +140,7 @@ function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
156
140
return folder ;
157
141
}
158
142
159
- function didChangeWorkspaceFolders (
143
+ function whenChangingWorkspaceFolders (
160
144
e : WorkspaceFoldersChangeEvent ,
161
145
context : ExtensionContext ,
162
146
) {
@@ -166,13 +150,13 @@ function didChangeWorkspaceFolders(
166
150
// if not, and it is a Rust project (i.e., has a Cargo.toml), then create a new client.
167
151
for ( let folder of e . added ) {
168
152
folder = getOuterMostWorkspaceFolder ( folder ) ;
169
- if ( workspaces . has ( folder . uri ) ) {
153
+ if ( workspaces . has ( folder . uri . toString ( ) ) ) {
170
154
continue ;
171
155
}
172
156
for ( const f of fs . readdirSync ( folder . uri . fsPath ) ) {
173
157
if ( f === 'Cargo.toml' ) {
174
158
const workspace = new ClientWorkspace ( folder ) ;
175
- workspaces . set ( folder . uri , workspace ) ;
159
+ workspaces . set ( folder . uri . toString ( ) , workspace ) ;
176
160
workspace . start ( context ) ;
177
161
break ;
178
162
}
@@ -181,15 +165,18 @@ function didChangeWorkspaceFolders(
181
165
182
166
// If a workspace is removed which is a Rust workspace, kill the client.
183
167
for ( const folder of e . removed ) {
184
- const ws = workspaces . get ( folder . uri ) ;
168
+ const ws = workspaces . get ( folder . uri . toString ( ) ) ;
185
169
if ( ws ) {
186
- workspaces . delete ( folder . uri ) ;
170
+ workspaces . delete ( folder . uri . toString ( ) ) ;
187
171
ws . stop ( ) ;
188
172
}
189
173
}
190
174
}
191
175
192
- const workspaces : Map < Uri , ClientWorkspace > = new Map ( ) ;
176
+ // Don't use URI as it's unreliable the same path might not become the same URI.
177
+ const workspaces : Map < string , ClientWorkspace > = new Map ( ) ;
178
+ let activeWorkspace : ClientWorkspace | null ;
179
+ let commandsRegistered : boolean = false ;
193
180
194
181
// We run one RLS and one corresponding language client per workspace folder
195
182
// (VSCode workspace, not Cargo workspace). This class contains all the per-client
@@ -209,21 +196,31 @@ class ClientWorkspace {
209
196
}
210
197
211
198
public async start ( context : ExtensionContext ) {
212
- warnOnMissingCargoToml ( ) ;
199
+ if ( ! this . config . multiProjectEnabled ) {
200
+ warnOnMissingCargoToml ( ) ;
201
+ }
213
202
214
203
startSpinner ( 'RLS' , 'Starting' ) ;
215
204
216
205
const serverOptions : ServerOptions = async ( ) => {
217
206
await this . autoUpdate ( ) ;
218
207
return this . makeRlsProcess ( ) ;
219
208
} ;
209
+
210
+ const pattern = this . config . multiProjectEnabled
211
+ ? `${ this . folder . uri . path } /**`
212
+ : undefined ;
213
+ const collectionName = this . config . multiProjectEnabled
214
+ ? `rust ${ this . folder . uri . toString ( ) } `
215
+ : 'rust' ;
220
216
const clientOptions : LanguageClientOptions = {
221
217
// Register the server for Rust files
218
+
222
219
documentSelector : [
223
- { language : 'rust' , scheme : 'file' } ,
224
- { language : 'rust' , scheme : 'untitled' } ,
220
+ { language : 'rust' , scheme : 'file' , pattern } ,
221
+ { language : 'rust' , scheme : 'untitled' , pattern } ,
225
222
] ,
226
- diagnosticCollectionName : 'rust' ,
223
+ diagnosticCollectionName : collectionName ,
227
224
synchronize : { configurationSection : 'rust' } ,
228
225
// Controls when to focus the channel rather than when to reveal it in the drop-down list
229
226
revealOutputChannelOn : this . config . revealOutputChannelOn ,
@@ -259,13 +256,17 @@ class ClientWorkspace {
259
256
clientOptions ,
260
257
) ;
261
258
259
+ const selector = this . config . multiProjectEnabled
260
+ ? { language : 'rust' , scheme : 'file' , pattern }
261
+ : { language : 'rust' } ;
262
+
262
263
this . setupProgressCounter ( ) ;
263
- this . registerCommands ( context ) ;
264
+ this . registerCommands ( context , this . config . multiProjectEnabled ) ;
264
265
this . disposables . push ( activateTaskProvider ( this . folder ) ) ;
265
266
this . disposables . push ( this . lc . start ( ) ) ;
266
267
this . disposables . push (
267
268
languages . registerSignatureHelpProvider (
268
- { language : 'rust' } ,
269
+ selector ,
269
270
new SignatureHelpProvider ( this . lc ) ,
270
271
'(' ,
271
272
',' ,
@@ -279,31 +280,45 @@ class ClientWorkspace {
279
280
}
280
281
281
282
this . disposables . forEach ( d => d . dispose ( ) ) ;
283
+ commandsRegistered = false ;
282
284
}
283
285
284
- private registerCommands ( context : ExtensionContext ) {
286
+ private registerCommands (
287
+ context : ExtensionContext ,
288
+ multiProjectEnabled : boolean ,
289
+ ) {
285
290
if ( ! this . lc ) {
286
291
return ;
287
292
}
293
+ if ( multiProjectEnabled && commandsRegistered ) {
294
+ return ;
295
+ }
288
296
297
+ commandsRegistered = true ;
289
298
const rustupUpdateDisposable = commands . registerCommand (
290
299
'rls.update' ,
291
300
( ) => {
292
- return rustupUpdate ( this . config . rustupConfig ( ) ) ;
301
+ const ws =
302
+ multiProjectEnabled && activeWorkspace ? activeWorkspace : this ;
303
+ return rustupUpdate ( ws . config . rustupConfig ( ) ) ;
293
304
} ,
294
305
) ;
295
306
this . disposables . push ( rustupUpdateDisposable ) ;
296
307
297
308
const restartServer = commands . registerCommand ( 'rls.restart' , async ( ) => {
298
- await this . stop ( ) ;
299
- return this . start ( context ) ;
309
+ const ws =
310
+ multiProjectEnabled && activeWorkspace ? activeWorkspace : this ;
311
+ await ws . stop ( ) ;
312
+ return ws . start ( context ) ;
300
313
} ) ;
301
314
this . disposables . push ( restartServer ) ;
302
315
303
316
this . disposables . push (
304
- commands . registerCommand ( 'rls.run' , ( cmd : Execution ) =>
305
- runRlsCommand ( this . folder , cmd ) ,
306
- ) ,
317
+ commands . registerCommand ( 'rls.run' , ( cmd : Execution ) => {
318
+ const ws =
319
+ multiProjectEnabled && activeWorkspace ? activeWorkspace : this ;
320
+ runRlsCommand ( ws . folder , cmd ) ;
321
+ } ) ,
307
322
) ;
308
323
}
309
324
@@ -471,7 +486,7 @@ async function warnOnMissingCargoToml() {
471
486
472
487
if ( files . length < 1 ) {
473
488
window . showWarningMessage (
474
- 'A Cargo.toml file must be at the root of the workspace in order to support all features' ,
489
+ 'A Cargo.toml file must be at the root of the workspace in order to support all features. Alternatively set rust-client.enableMultiProjectSetup=true in settings. ' ,
475
490
) ;
476
491
}
477
492
}
0 commit comments