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

Commit 30b399d

Browse files
authored
Merge pull request #767 from rust-lang/workspaces-refactor
Refactor workspace handling
2 parents 0219b52 + c0d6451 commit 30b399d

File tree

9 files changed

+99
-89
lines changed

9 files changed

+99
-89
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ out
22
node_modules
33
.vscode-test
44
*.vsix
5+
rls*.log

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### Unreleased
22

3+
* Remove redundant `rust-client.nestedMultiRootConfigInOutermost` setting (originally used to work around non-multi-project limitations)
4+
* Ignore setting `rust-client.enableMultiProjectSetup` (it's always on by default)
35
* Fix support for multiple VSCode workspaces
46

57
### 0.7.2 - 2020-04-17
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "bare-lib-project"
3+
version = "0.1.0"
4+
authors = ["Example <[email protected]>"]
5+
edition = "2018"
6+
7+
[dependencies]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[cfg(test)]
2+
mod tests {
3+
#[test]
4+
fn it_works() {
5+
assert_eq!(2 + 2, 4);
6+
}
7+
}

package.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,9 @@
227227
"description": "Traces the communication between VS Code and the Rust language server.",
228228
"scope": "window"
229229
},
230-
"rust-client.nestedMultiRootConfigInOutermost": {
231-
"type": "boolean",
232-
"default": true,
233-
"description": "If one root workspace folder is nested in another root folder, look for the Rust config in the outermost root."
234-
},
235230
"rust-client.enableMultiProjectSetup": {
236-
"type": "boolean",
237-
"default": false,
231+
"type": ["boolean", "null"],
232+
"default": null,
238233
"description": "Allow multiple projects in the same folder, along with removing the constraint that the cargo.toml must be located at the root. (Experimental: might not work for certain setups)"
239234
},
240235
"rust.sysroot": {

src/configuration.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,6 @@ 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>(
123-
'rust-client.enableMultiProjectSetup',
124-
false,
125-
);
126-
}
127-
128121
// Added ignoreChannel for readChannel function. Otherwise we end in an infinite loop.
129122
public rustupConfig(ignoreChannel: boolean = false): RustupConfig {
130123
return {

src/extension.ts

Lines changed: 34 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fs from 'fs';
33
import * as path from 'path';
44
import {
55
commands,
6+
ConfigurationTarget,
67
Disposable,
78
ExtensionContext,
89
IndentAction,
@@ -28,8 +29,11 @@ import { checkForRls, ensureToolchain, rustupUpdate } from './rustup';
2829
import { startSpinner, stopSpinner } from './spinner';
2930
import { activateTaskProvider, Execution, runRlsCommand } from './tasks';
3031
import { withWsl } from './utils/child_process';
32+
import {
33+
getOuterMostWorkspaceFolder,
34+
nearestParentWorkspace,
35+
} from './utils/workspace';
3136
import { uriWindowsToWsl, uriWslToWindows } from './utils/wslpath';
32-
import * as workspace_util from './workspace_util';
3337

3438
/**
3539
* Parameter type to `window/progress` request as issued by the RLS.
@@ -55,6 +59,33 @@ export async function activate(context: ExtensionContext) {
5559
// Installed listeners don't fire immediately for already opened files, so
5660
// trigger an open event manually to fire up RLS instances where needed
5761
workspace.textDocuments.forEach(whenOpeningTextDocument);
62+
63+
// Migrate the users of multi-project setup for RLS to disable the setting
64+
// entirely (it's always on now)
65+
const config = workspace.getConfiguration();
66+
if (
67+
typeof config.get<boolean | null>(
68+
'rust-client.enableMultiProjectSetup',
69+
null,
70+
) === 'boolean'
71+
) {
72+
window
73+
.showWarningMessage(
74+
'The multi-project setup for RLS is always enabled, so the `rust-client.enableMultiProjectSetup` setting is now redundant',
75+
{ modal: false },
76+
{ title: 'Remove' },
77+
)
78+
.then(value => {
79+
if (value && value.title === 'Remove') {
80+
return config.update(
81+
'rust-client.enableMultiProjectSetup',
82+
null,
83+
ConfigurationTarget.Global,
84+
);
85+
}
86+
return;
87+
});
88+
}
5889
}
5990

6091
export async function deactivate() {
@@ -67,25 +98,12 @@ function whenOpeningTextDocument(document: TextDocument) {
6798
return;
6899
}
69100

70-
const uri = document.uri;
71-
let folder = workspace.getWorkspaceFolder(uri);
101+
let folder = workspace.getWorkspaceFolder(document.uri);
72102
if (!folder) {
73103
return;
74104
}
75105

76-
const inMultiProjectMode = workspace
77-
.getConfiguration()
78-
.get<boolean>('rust-client.enableMultiProjectSetup', false);
79-
80-
const inNestedOuterProjectMode = workspace
81-
.getConfiguration()
82-
.get<boolean>('rust-client.nestedMultiRootConfigInOutermost', true);
83-
84-
if (inMultiProjectMode) {
85-
folder = workspace_util.nearestParentWorkspace(folder, document.uri.fsPath);
86-
} else if (inNestedOuterProjectMode) {
87-
folder = getOuterMostWorkspaceFolder(folder);
88-
}
106+
folder = nearestParentWorkspace(folder, document.uri.fsPath);
89107

90108
if (!folder) {
91109
stopSpinner(`RLS: Cargo.toml missing`);
@@ -105,46 +123,7 @@ function whenOpeningTextDocument(document: TextDocument) {
105123
}
106124
}
107125

108-
// This is an intermediate, lazy cache used by `getOuterMostWorkspaceFolder`
109-
// and cleared when VSCode workspaces change.
110-
let _sortedWorkspaceFolders: string[] | undefined;
111-
112-
function sortedWorkspaceFolders(): string[] {
113-
// TODO: decouple the global state such that it can be moved to workspace_util
114-
if (!_sortedWorkspaceFolders && workspace.workspaceFolders) {
115-
_sortedWorkspaceFolders = workspace.workspaceFolders
116-
.map(folder => {
117-
let result = folder.uri.toString();
118-
if (result.charAt(result.length - 1) !== '/') {
119-
result = result + '/';
120-
}
121-
return result;
122-
})
123-
.sort((a, b) => {
124-
return a.length - b.length;
125-
});
126-
}
127-
return _sortedWorkspaceFolders || [];
128-
}
129-
130-
function getOuterMostWorkspaceFolder(folder: WorkspaceFolder): WorkspaceFolder {
131-
// TODO: decouple the global state such that it can be moved to workspace_util
132-
const sorted = sortedWorkspaceFolders();
133-
for (const element of sorted) {
134-
let uri = folder.uri.toString();
135-
if (uri.charAt(uri.length - 1) !== '/') {
136-
uri = uri + '/';
137-
}
138-
if (uri.startsWith(element)) {
139-
return workspace.getWorkspaceFolder(Uri.parse(element)) || folder;
140-
}
141-
}
142-
return folder;
143-
}
144-
145126
function whenChangingWorkspaceFolders(e: WorkspaceFoldersChangeEvent) {
146-
_sortedWorkspaceFolders = undefined;
147-
148127
// If a VSCode workspace has been added, check to see if it is part of an existing one, and
149128
// if not, and it is a Rust project (i.e., has a Cargo.toml), then create a new client.
150129
for (let folder of e.added) {
@@ -193,10 +172,6 @@ class ClientWorkspace {
193172
}
194173

195174
public async start() {
196-
if (!this.config.multiProjectEnabled) {
197-
warnOnMissingCargoToml();
198-
}
199-
200175
startSpinner('RLS', 'Starting');
201176

202177
const serverOptions: ServerOptions = async () => {
@@ -450,16 +425,6 @@ class ClientWorkspace {
450425
}
451426
}
452427

453-
async function warnOnMissingCargoToml() {
454-
const files = await workspace.findFiles('Cargo.toml');
455-
456-
if (files.length < 1) {
457-
window.showWarningMessage(
458-
'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.',
459-
);
460-
}
461-
}
462-
463428
/**
464429
* Tracks the most current VSCode workspace as opened by the user. Used by the
465430
* commands to know in which workspace these should be executed.

src/workspace_util.ts renamed to src/utils/workspace.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3-
import { Uri, WorkspaceFolder } from 'vscode';
3+
import { Uri, workspace, WorkspaceFolder } from 'vscode';
44

55
// searches up the folder structure until it finds a Cargo.toml
66
export function nearestParentWorkspace(
@@ -44,3 +44,30 @@ export function nearestParentWorkspace(
4444

4545
return curWorkspace;
4646
}
47+
48+
export function getOuterMostWorkspaceFolder(
49+
folder: WorkspaceFolder,
50+
): WorkspaceFolder {
51+
const sortedFoldersByPrefix = (workspace.workspaceFolders || [])
52+
.map(folder => normalizeUriToPathPrefix(folder.uri))
53+
.sort((a, b) => a.length - b.length);
54+
55+
const uri = normalizeUriToPathPrefix(folder.uri);
56+
57+
const outermostPath = sortedFoldersByPrefix.find(pre => uri.startsWith(pre));
58+
return outermostPath
59+
? workspace.getWorkspaceFolder(Uri.parse(outermostPath)) || folder
60+
: folder;
61+
}
62+
63+
/**
64+
* Transforms a given URI to a path prefix, namely ensures that each path
65+
* segment ends with a path separator `/`.
66+
*/
67+
function normalizeUriToPathPrefix(uri: Uri): string {
68+
let result = uri.toString();
69+
if (result.charAt(result.length - 1) !== '/') {
70+
result = result + '/';
71+
}
72+
return result;
73+
}

test/suite/extension.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,24 @@ const fixtureDir = path.resolve(
1010

1111
suite('Extension Tests', () => {
1212
test('cargo tasks are auto-detected', async () => {
13-
const projectPath = path.join(fixtureDir, 'bare-lib-project');
13+
const projectPath = fixtureDir;
1414
const projectUri = Uri.file(projectPath);
15+
const projects = [
16+
path.join(projectPath, 'bare-lib-project'),
17+
path.join(projectPath, 'another-lib-project'),
18+
];
1519

1620
await vscode.commands.executeCommand('vscode.openFolder', projectUri);
1721
await vscode.workspace.openTextDocument(
18-
Uri.file(path.join(projectPath, 'src', 'lib.rs')),
22+
Uri.file(path.join(projects[0], 'src', 'lib.rs')),
23+
);
24+
await vscode.workspace.openTextDocument(
25+
Uri.file(path.join(projects[1], 'src', 'lib.rs')),
1926
);
2027

2128
const expected = [
22-
{ subcommand: 'build', group: vscode.TaskGroup.Build },
29+
{ subcommand: 'build', group: vscode.TaskGroup.Build, cwd: projects[0] },
30+
{ subcommand: 'build', group: vscode.TaskGroup.Build, cwd: projects[1] },
2331
{ subcommand: 'check', group: vscode.TaskGroup.Build },
2432
{ subcommand: 'test', group: vscode.TaskGroup.Test },
2533
{ subcommand: 'clean', group: vscode.TaskGroup.Clean },
@@ -28,13 +36,18 @@ suite('Extension Tests', () => {
2836

2937
const tasks = await vscode.tasks.fetchTasks();
3038

31-
for (const { subcommand, group } of expected) {
39+
for (const { subcommand, group, cwd } of expected) {
3240
assert(
3341
tasks.some(
3442
task =>
3543
task.definition.type === 'cargo' &&
3644
task.definition.subcommand === subcommand &&
37-
task.group === group,
45+
task.group === group &&
46+
(!cwd ||
47+
cwd ===
48+
(task.execution &&
49+
task.execution.options &&
50+
task.execution.options.cwd)),
3851
),
3952
);
4053
}

0 commit comments

Comments
 (0)