Skip to content

Commit f95c1e9

Browse files
committed
Ensure folders are added to sourcekit-lsp in multi root workspaces
`addFolder` in the LSP client was checking to see if the folder being added was a root folder. If the user is working in a multi root workspace document symbols would not be provided if they were for any folder except the first one added. This also prevented the test explorer from properly initializing. Check to see if the folder being added is in the list of VS Code workspace folders and associate the folder with the onDocumentSymbols request from the LSP. Issue: #1669
1 parent 6e5c04a commit f95c1e9

File tree

7 files changed

+206
-211
lines changed

7 files changed

+206
-211
lines changed

src/FolderContext.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ export class FolderContext implements vscode.Disposable {
177177
}
178178

179179
/** Refresh the tests in the test explorer for this folder */
180-
refreshTestExplorer() {
180+
async refreshTestExplorer() {
181181
if (this.testExplorer?.controller.resolveHandler) {
182-
void this.testExplorer.controller.resolveHandler(undefined);
182+
return this.testExplorer.controller.resolveHandler(undefined);
183183
}
184184
}
185185

@@ -211,6 +211,25 @@ export class FolderContext implements vscode.Disposable {
211211
});
212212
return target;
213213
}
214+
215+
/**
216+
* Called whenever we have new document symbols
217+
*/
218+
onDocumentSymbols(
219+
document: vscode.TextDocument,
220+
symbols: vscode.DocumentSymbol[] | null | undefined
221+
) {
222+
const uri = document?.uri;
223+
if (
224+
this.testExplorer &&
225+
symbols &&
226+
uri &&
227+
uri.scheme === "file" &&
228+
isPathInsidePath(uri.fsPath, this.folder.fsPath)
229+
) {
230+
void this.testExplorer.getDocumentTests(this, uri, symbols);
231+
}
232+
}
214233
}
215234

216235
export interface EditedPackage {

src/TestExplorer/TestExplorer.ts

Lines changed: 46 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import * as vscode from "vscode";
1616
import { FolderContext } from "../FolderContext";
1717
import { getErrorDescription } from "../utilities/utilities";
18-
import { isPathInsidePath } from "../utilities/filesystem";
1918
import { FolderOperation, WorkspaceContext } from "../WorkspaceContext";
2019
import { TestRunProxy, TestRunner } from "./TestRunner";
2120
import { LSPTestDiscovery } from "./LSPTestDiscovery";
@@ -136,45 +135,11 @@ export class TestExplorer {
136135
const disposable = workspaceContext.onDidChangeFolders(({ folder, operation }) => {
137136
switch (operation) {
138137
case FolderOperation.add:
139-
if (folder) {
140-
void folder.swiftPackage.getTargets(TargetType.test).then(targets => {
141-
if (targets.length === 0) {
142-
return;
143-
}
144-
145-
folder.addTestExplorer();
146-
// discover tests in workspace but only if disableAutoResolve is not on.
147-
// discover tests will kick off a resolve if required
148-
if (!configuration.folder(folder.workspaceFolder).disableAutoResolve) {
149-
void folder.testExplorer?.discoverTestsInWorkspace(
150-
tokenSource.token
151-
);
152-
}
153-
});
154-
}
155-
break;
156138
case FolderOperation.packageUpdated:
157139
if (folder) {
158-
void folder.swiftPackage.getTargets(TargetType.test).then(targets => {
159-
const hasTestTargets = targets.length > 0;
160-
if (hasTestTargets && !folder.hasTestExplorer()) {
161-
folder.addTestExplorer();
162-
// discover tests in workspace but only if disableAutoResolve is not on.
163-
// discover tests will kick off a resolve if required
164-
if (
165-
!configuration.folder(folder.workspaceFolder).disableAutoResolve
166-
) {
167-
void folder.testExplorer?.discoverTestsInWorkspace(
168-
tokenSource.token
169-
);
170-
}
171-
} else if (!hasTestTargets && folder.hasTestExplorer()) {
172-
folder.removeTestExplorer();
173-
} else if (folder.hasTestExplorer()) {
174-
folder.refreshTestExplorer();
175-
}
176-
});
140+
void this.setupTestExplorerForFolder(folder, tokenSource.token);
177141
}
142+
break;
178143
}
179144
});
180145
return {
@@ -185,6 +150,28 @@ export class TestExplorer {
185150
};
186151
}
187152

153+
/**
154+
* Configures a test explorer for the given folder.
155+
* If the folder has test targets, and there is no existing test explorer,
156+
* it will create a test explorer and discover tests.
157+
* If the folder has no test targets, it will remove any existing test explorer.
158+
* If the folder has test targets and an existing test explorer, it will refresh the tests.
159+
*/
160+
private static async setupTestExplorerForFolder(
161+
folder: FolderContext,
162+
token: vscode.CancellationToken
163+
) {
164+
const targets = await folder.swiftPackage.getTargets(TargetType.test);
165+
const hasTestTargets = targets.length > 0;
166+
if (hasTestTargets && !folder.hasTestExplorer()) {
167+
await folder.addTestExplorer().discoverTestsInWorkspace(token);
168+
} else if (hasTestTargets && folder.hasTestExplorer()) {
169+
await folder.refreshTestExplorer();
170+
} else if (!hasTestTargets && folder.hasTestExplorer()) {
171+
folder.removeTestExplorer();
172+
}
173+
}
174+
188175
/**
189176
* Sets the `swift.tests` context variable which is used by commands
190177
* to determine if the test item belongs to the Swift extension.
@@ -196,45 +183,29 @@ export class TestExplorer {
196183
});
197184
}
198185

199-
/**
200-
* Called whenever we have new document symbols
201-
*/
202-
static onDocumentSymbols(
186+
async getDocumentTests(
203187
folder: FolderContext,
204-
document: vscode.TextDocument,
205-
symbols: vscode.DocumentSymbol[] | null | undefined
206-
) {
207-
const uri = document?.uri;
208-
const testExplorer = folder?.testExplorer;
209-
if (testExplorer && symbols && uri && uri.scheme === "file") {
210-
if (isPathInsidePath(uri.fsPath, folder.folder.fsPath)) {
211-
void folder.swiftPackage.getTarget(uri.fsPath).then(target => {
212-
if (target && target.type === "test") {
213-
testExplorer.lspTestDiscovery
214-
.getDocumentTests(folder.swiftPackage, uri)
215-
.then(tests => {
216-
TestDiscovery.updateTestsForTarget(
217-
testExplorer.controller,
218-
{ id: target.c99name, label: target.name },
219-
tests,
220-
uri
221-
);
222-
testExplorer.onTestItemsDidChangeEmitter.fire(
223-
testExplorer.controller
224-
);
225-
})
226-
// Fallback to parsing document symbols for XCTests only
227-
.catch(() => {
228-
const tests = parseTestsFromDocumentSymbols(
229-
target.name,
230-
symbols,
231-
uri
232-
);
233-
testExplorer.updateTests(testExplorer.controller, tests, uri);
234-
});
235-
}
236-
});
237-
}
188+
uri: vscode.Uri,
189+
symbols: vscode.DocumentSymbol[]
190+
): Promise<void> {
191+
const target = await folder.swiftPackage.getTarget(uri.fsPath);
192+
if (!target || target.type !== "test") {
193+
return;
194+
}
195+
196+
try {
197+
const tests = await this.lspTestDiscovery.getDocumentTests(folder.swiftPackage, uri);
198+
TestDiscovery.updateTestsForTarget(
199+
this.controller,
200+
{ id: target.c99name, label: target.name },
201+
tests,
202+
uri
203+
);
204+
this.onTestItemsDidChangeEmitter.fire(this.controller);
205+
} catch {
206+
// Fallback to parsing document symbols for XCTests only
207+
const tests = parseTestsFromDocumentSymbols(target.name, symbols, uri);
208+
this.updateTests(this.controller, tests, uri);
238209
}
239210
}
240211

src/WorkspaceContext.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { isValidWorkspaceFolder, searchForPackages } from "./utilities/workspace
3737
import { SwiftPluginTaskProvider } from "./tasks/SwiftPluginTaskProvider";
3838
import { SwiftTaskProvider } from "./tasks/SwiftTaskProvider";
3939
import { LLDBDebugConfigurationProvider } from "./debugger/debugAdapterFactory";
40-
import { TestExplorer } from "./TestExplorer/TestExplorer";
4140

4241
/**
4342
* Context for whole workspace. Holds array of contexts for each workspace folder
@@ -82,7 +81,7 @@ export class WorkspaceContext implements vscode.Disposable {
8281
this.buildStatus = new SwiftBuildStatus(this.statusItem);
8382
this.languageClientManager = new LanguageClientToolchainCoordinator(this, {
8483
onDocumentSymbols: (folder, document, symbols) => {
85-
TestExplorer.onDocumentSymbols(folder, document, symbols);
84+
folder.onDocumentSymbols(document, symbols);
8685
},
8786
});
8887
this.tasks = new TaskManager(this);

src/sourcekit-lsp/LanguageClientManager.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ export class LanguageClientManager implements vscode.Disposable {
9797
private singleServerSupport: boolean;
9898
// used by single server support to keep a record of the project folders
9999
// that are not at the root of their workspace
100-
public subFolderWorkspaces: vscode.Uri[];
100+
public subFolderWorkspaces: FolderContext[] = [];
101+
private addedFolders: FolderContext[] = [];
101102
private namedOutputChannels: Map<string, LSPOutputChannel> = new Map();
102103
private swiftVersion: Version;
103104
private activeDocumentManager = new LSPActiveDocumentManager();
@@ -122,7 +123,6 @@ export class LanguageClientManager implements vscode.Disposable {
122123
this.swiftVersion = folderContext.swiftVersion;
123124
this.singleServerSupport = this.swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0));
124125
this.subscriptions = [];
125-
this.subFolderWorkspaces = [];
126126

127127
// on change config restart server
128128
const onChangeConfig = vscode.workspace.onDidChangeConfiguration(event => {
@@ -244,9 +244,9 @@ export class LanguageClientManager implements vscode.Disposable {
244244
async addFolder(folderContext: FolderContext) {
245245
if (!folderContext.isRootFolder) {
246246
await this.useLanguageClient(async client => {
247-
const uri = folderContext.folder;
248-
this.subFolderWorkspaces.push(folderContext.folder);
247+
this.subFolderWorkspaces.push(folderContext);
249248

249+
const uri = folderContext.folder;
250250
const workspaceFolder = {
251251
uri: client.code2ProtocolConverter.asUri(uri),
252252
name: FolderContext.uriName(uri),
@@ -256,13 +256,16 @@ export class LanguageClientManager implements vscode.Disposable {
256256
});
257257
});
258258
}
259+
this.addedFolders.push(folderContext);
259260
}
260261

261262
async removeFolder(folderContext: FolderContext) {
262263
if (!folderContext.isRootFolder) {
263264
await this.useLanguageClient(async client => {
264265
const uri = folderContext.folder;
265-
this.subFolderWorkspaces = this.subFolderWorkspaces.filter(item => item !== uri);
266+
this.subFolderWorkspaces = this.subFolderWorkspaces.filter(
267+
item => item.folder !== uri
268+
);
266269

267270
const workspaceFolder = {
268271
uri: client.code2ProtocolConverter.asUri(uri),
@@ -273,13 +276,14 @@ export class LanguageClientManager implements vscode.Disposable {
273276
});
274277
});
275278
}
279+
this.addedFolders = this.addedFolders.filter(item => item.folder !== folderContext.folder);
276280
}
277281

278282
private async addSubFolderWorkspaces(client: LanguageClient) {
279-
for (const uri of this.subFolderWorkspaces) {
283+
for (const folderContext of this.subFolderWorkspaces) {
280284
const workspaceFolder = {
281-
uri: client.code2ProtocolConverter.asUri(uri),
282-
name: FolderContext.uriName(uri),
285+
uri: client.code2ProtocolConverter.asUri(folderContext.folder),
286+
name: FolderContext.uriName(folderContext.folder),
283287
};
284288
await client.sendNotification(DidChangeWorkspaceFoldersNotification.type, {
285289
event: { added: [workspaceFolder], removed: [] },
@@ -440,7 +444,17 @@ export class LanguageClientManager implements vscode.Disposable {
440444
this.activeDocumentManager,
441445
errorHandler,
442446
(document, symbols) => {
443-
this.options.onDocumentSymbols?.(this.folderContext, document, symbols);
447+
const documentFolderContext = [this.folderContext, ...this.addedFolders].find(
448+
folderContext => document.uri.fsPath.startsWith(folderContext.folder.fsPath)
449+
);
450+
if (!documentFolderContext) {
451+
this.languageClientOutputChannel?.log(
452+
"Unable to find folder for document: " + document.uri.fsPath,
453+
"WARN"
454+
);
455+
return;
456+
}
457+
this.options.onDocumentSymbols?.(documentFolderContext, document, symbols);
444458
}
445459
);
446460

test/integration-tests/ExtensionActivation.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ suite("Extension Activation/Deactivation Tests", () => {
116116
assert(folder);
117117

118118
const languageClient = workspaceContext.languageClientManager.get(folder);
119-
const lspWorkspaces = languageClient.subFolderWorkspaces.map(({ fsPath }) => fsPath);
119+
const lspWorkspaces = languageClient.subFolderWorkspaces.map(
120+
({ folder }) => folder.fsPath
121+
);
120122
assertContains(lspWorkspaces, testAssetUri("cmake").fsPath);
121123
});
122124

@@ -125,7 +127,9 @@ suite("Extension Activation/Deactivation Tests", () => {
125127
assert(folder);
126128

127129
const languageClient = workspaceContext.languageClientManager.get(folder);
128-
const lspWorkspaces = languageClient.subFolderWorkspaces.map(({ fsPath }) => fsPath);
130+
const lspWorkspaces = languageClient.subFolderWorkspaces.map(
131+
({ folder }) => folder.fsPath
132+
);
129133
assertContains(lspWorkspaces, testAssetUri("cmake-compile-flags").fsPath);
130134
});
131135
});

test/integration-tests/commands/runTestMultipleTimes.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ suite("Test Multiple Times Command Test Suite", () => {
2525

2626
activateExtensionForSuite({
2727
async setup(ctx) {
28-
folderContext = await folderInRootWorkspace("diagnostics", ctx);
28+
folderContext = await folderInRootWorkspace("defaultPackage", ctx);
2929
folderContext.addTestExplorer();
3030

3131
const item = folderContext.testExplorer?.controller.createTestItem(

0 commit comments

Comments
 (0)