Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.

Commit f8d41bb

Browse files
committed
reintroduce multi project setup
* enable multiple projects with multiple rls running * change the URI key back to string for workspaces (as it's unreliable) * add selectors such that only the correct rls is used for a given project * take great care to ensure the old setup works normally * add a configuration and turn it off by default.
1 parent 6f65f3b commit f8d41bb

File tree

3 files changed

+101
-51
lines changed

3 files changed

+101
-51
lines changed

package.json

+5
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@
219219
"default": true,
220220
"description": "If one root workspace folder is nested in another root folder, look for the Rust config in the outermost root."
221221
},
222+
"rust-client.enableMultiProjectSetup": {
223+
"type": "boolean",
224+
"default": false,
225+
"description": "Allow multiple projects in the same folder, along with remove the constraint that the cargo.toml must be located at the root. (Experimental: might not work for certain setups)"
226+
},
222227
"rust.sysroot": {
223228
"type": [
224229
"string",

src/configuration.ts

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ export class RLSConfiguration {
118118
return this.configuration.get<string>('rust-client.rlsPath');
119119
}
120120

121+
public get multiProjectEnabled(): boolean {
122+
return this.configuration.get<boolean>('rust-client.enableMultiProjectSetup', false);
123+
}
124+
121125
// Added ignoreChannel for readChannel function. Otherwise we end in an infinite loop.
122126
public rustupConfig(ignoreChannel: boolean = false): RustupConfig {
123127
return {

src/extension.ts

+92-51
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,37 @@ function didOpenTextDocument(
6969
if (!folder) {
7070
return;
7171
}
72+
7273
if (
74+
workspace
75+
.getConfiguration()
76+
.get<boolean>('rust-client.enableMultiProjectSetup', false)
77+
) {
78+
folder = nearestCargoTomlWorkspace(folder, document.uri.fsPath);
79+
} else if (
7380
workspace
7481
.getConfiguration()
7582
.get<boolean>('rust-client.nestedMultiRootConfigInOutermost', true)
7683
) {
7784
folder = getOuterMostWorkspaceFolder(folder);
7885
}
79-
// folder = getCargoTomlWorkspace(folder, document.uri.fsPath);
86+
8087
if (!folder) {
8188
stopSpinner(`RLS: Cargo.toml missing`);
8289
return;
8390
}
8491

85-
if (!workspaces.has(folder.uri)) {
92+
const folderPath = folder.uri.toString();
93+
94+
if (!workspaces.has(folderPath)) {
95+
8696
const workspace = new ClientWorkspace(folder);
87-
workspaces.set(folder.uri, workspace);
97+
activeWorkspace = workspace;
98+
workspaces.set(folderPath, workspace);
8899
workspace.start(context);
100+
} else {
101+
const ws = workspaces.get(folderPath);
102+
activeWorkspace = typeof ws === "undefined" ? null : ws;
89103
}
90104
}
91105

@@ -110,37 +124,41 @@ function sortedWorkspaceFolders(): string[] {
110124
return _sortedWorkspaceFolders || [];
111125
}
112126

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-
// }
127+
function nearestCargoTomlWorkspace(
128+
curWorkspace: WorkspaceFolder,
129+
filePath: string,
130+
): WorkspaceFolder {
131+
if (!curWorkspace) {
132+
return curWorkspace;
133+
}
134+
135+
const workspaceRoot = path.parse(curWorkspace.uri.fsPath).dir;
136+
const rootManifest = path.join(workspaceRoot, 'Cargo.toml');
137+
if (fs.existsSync(rootManifest)) {
138+
return curWorkspace;
139+
}
140+
141+
let current = filePath;
142+
143+
while (true) {
144+
const old = current;
145+
current = path.dirname(current);
146+
if (old === current) {
147+
break;
148+
}
149+
if (workspaceRoot === path.parse(current).dir) {
150+
break;
151+
}
152+
153+
const cargoPath = path.join(current, 'Cargo.toml');
154+
const uri = Uri.file(current);
155+
if (fs.existsSync(cargoPath)) {
156+
return { ...curWorkspace, uri };
157+
}
158+
}
159+
160+
return curWorkspace;
161+
}
144162

145163
function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
146164
const sorted = sortedWorkspaceFolders();
@@ -166,13 +184,13 @@ function didChangeWorkspaceFolders(
166184
// if not, and it is a Rust project (i.e., has a Cargo.toml), then create a new client.
167185
for (let folder of e.added) {
168186
folder = getOuterMostWorkspaceFolder(folder);
169-
if (workspaces.has(folder.uri)) {
187+
if (workspaces.has(folder.uri.toString())) {
170188
continue;
171189
}
172190
for (const f of fs.readdirSync(folder.uri.fsPath)) {
173191
if (f === 'Cargo.toml') {
174192
const workspace = new ClientWorkspace(folder);
175-
workspaces.set(folder.uri, workspace);
193+
workspaces.set(folder.uri.toString(), workspace);
176194
workspace.start(context);
177195
break;
178196
}
@@ -181,15 +199,18 @@ function didChangeWorkspaceFolders(
181199

182200
// If a workspace is removed which is a Rust workspace, kill the client.
183201
for (const folder of e.removed) {
184-
const ws = workspaces.get(folder.uri);
202+
const ws = workspaces.get(folder.uri.toString());
185203
if (ws) {
186-
workspaces.delete(folder.uri);
204+
workspaces.delete(folder.uri.toString());
187205
ws.stop();
188206
}
189207
}
190208
}
191209

192-
const workspaces: Map<Uri, ClientWorkspace> = new Map();
210+
// Don't use URI as it's unreliable the same path might not become the same URI.
211+
const workspaces: Map<string, ClientWorkspace> = new Map();
212+
let activeWorkspace: ClientWorkspace | null;
213+
let commandsUnregistered: boolean = true;
193214

194215
// We run one RLS and one corresponding language client per workspace folder
195216
// (VSCode workspace, not Cargo workspace). This class contains all the per-client
@@ -209,21 +230,28 @@ class ClientWorkspace {
209230
}
210231

211232
public async start(context: ExtensionContext) {
212-
warnOnMissingCargoToml();
233+
if (!this.config.multiProjectEnabled) {
234+
warnOnMissingCargoToml();
235+
}
236+
213237

214238
startSpinner('RLS', 'Starting');
215239

216240
const serverOptions: ServerOptions = async () => {
217241
await this.autoUpdate();
218242
return this.makeRlsProcess();
219243
};
244+
245+
const pattern = this.config.multiProjectEnabled ? `${this.folder.uri.path}/**` : undefined;
246+
const collectionName = this.config.multiProjectEnabled ? `rust ${this.folder.uri.toString()}` : 'rust';
220247
const clientOptions: LanguageClientOptions = {
221248
// Register the server for Rust files
249+
222250
documentSelector: [
223-
{ language: 'rust', scheme: 'file' },
224-
{ language: 'rust', scheme: 'untitled' },
251+
{ language: 'rust', scheme: 'file', pattern },
252+
{ language: 'rust', scheme: 'untitled', pattern },
225253
],
226-
diagnosticCollectionName: 'rust',
254+
diagnosticCollectionName: collectionName,
227255
synchronize: { configurationSection: 'rust' },
228256
// Controls when to focus the channel rather than when to reveal it in the drop-down list
229257
revealOutputChannelOn: this.config.revealOutputChannelOn,
@@ -259,13 +287,15 @@ class ClientWorkspace {
259287
clientOptions,
260288
);
261289

290+
const selector = this.config.multiProjectEnabled ? { language: 'rust', scheme: 'file', pattern } : { language: 'rust' };
291+
262292
this.setupProgressCounter();
263-
this.registerCommands(context);
293+
this.registerCommands(context, this.config.multiProjectEnabled);
264294
this.disposables.push(activateTaskProvider(this.folder));
265295
this.disposables.push(this.lc.start());
266296
this.disposables.push(
267297
languages.registerSignatureHelpProvider(
268-
{ language: 'rust' },
298+
selector,
269299
new SignatureHelpProvider(this.lc),
270300
'(',
271301
',',
@@ -279,30 +309,41 @@ class ClientWorkspace {
279309
}
280310

281311
this.disposables.forEach(d => d.dispose());
312+
commandsUnregistered = true;
282313
}
283314

284-
private registerCommands(context: ExtensionContext) {
315+
private registerCommands(context: ExtensionContext, multiProjectEnabled: boolean) {
285316
if (!this.lc) {
286317
return;
287318
}
319+
if (multiProjectEnabled && !commandsUnregistered) {
320+
return;
321+
}
288322

323+
commandsUnregistered = false;
289324
const rustupUpdateDisposable = commands.registerCommand(
290325
'rls.update',
291326
() => {
292-
return rustupUpdate(this.config.rustupConfig());
327+
const ws = multiProjectEnabled && activeWorkspace ? activeWorkspace : this;
328+
return rustupUpdate(ws.config.rustupConfig());
293329
},
294330
);
295331
this.disposables.push(rustupUpdateDisposable);
296332

297333
const restartServer = commands.registerCommand('rls.restart', async () => {
298-
await this.stop();
299-
return this.start(context);
334+
const ws = multiProjectEnabled && activeWorkspace ? activeWorkspace : this;
335+
await ws.stop();
336+
return ws.start(context);
337+
300338
});
301339
this.disposables.push(restartServer);
302340

303341
this.disposables.push(
304-
commands.registerCommand('rls.run', (cmd: Execution) =>
305-
runRlsCommand(this.folder, cmd),
342+
commands.registerCommand('rls.run', (cmd: Execution) => {
343+
const ws = multiProjectEnabled && activeWorkspace ? activeWorkspace : this;
344+
runRlsCommand(ws.folder, cmd)
345+
},
346+
306347
),
307348
);
308349
}

0 commit comments

Comments
 (0)