Skip to content

Commit 75d13d0

Browse files
authored
Introduce new swift.excludePathsFromActivation setting (#1693)
* Introduce new `swift.exclude` setting This will make sure the specified folders are not initialized and added to the `WorkspaceContext`. SourceKit-LSP will still initialize if the an excluded file is open in the editor Issue: #636 * Fix pattern matching when overriding exclusion/inclusion * Unit tests for pattern matching * Add user documentation Include multi-project setup notes as excluding only makes sense in that context * Integration test to make sure search exclusion works * Fix failing unit tests * Rename setting to `excludePathsFromActivation` * CHANGELOG entry
1 parent abf1b4c commit 75d13d0

File tree

15 files changed

+320
-12
lines changed

15 files changed

+320
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- Added code lenses to run suites/tests, configurable with the `swift.showTestCodeLenses` setting ([#1698](https://github.com/swiftlang/vscode-swift/pull/1698))
8+
- New `swift.excludePathsFromActivation` setting to ignore specified sub-folders from being activated as projects ([#1693](https://github.com/swiftlang/vscode-swift/pull/1693))
89

910
## 2.8.0 - 2025-07-14
1011

assets/test/.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
"lldb.verboseLogging": true,
1111
"lldb.launch.terminal": "external",
1212
"lldb-dap.detachOnError": true,
13-
"swift.sourcekit-lsp.backgroundIndexing": "off"
13+
"swift.sourcekit-lsp.backgroundIndexing": "off",
14+
"swift.excludePathsFromActivation": {
15+
"**/excluded": true
16+
}
1417
}

assets/test/excluded/Package.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// swift-tools-version: 5.6
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "excluded",
8+
products: [
9+
.library(name: "excluded", targets: ["excluded"]),
10+
],
11+
dependencies: [],
12+
targets: [
13+
.target(name: "excluded"),
14+
]
15+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public struct excluded {
2+
public private(set) var text = "Hello, World!"
3+
4+
public init() {
5+
}
6+
}

package-lock.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,13 @@
796796
"default": false,
797797
"markdownDescription": "Disable the running of SourceKit-LSP.",
798798
"markdownDeprecationMessage": "**Deprecated**: Please use `#swift.sourcekit-lsp.disable#` instead."
799+
},
800+
"swift.excludePathsFromActivation": {
801+
"type": "object",
802+
"additionalProperties": {
803+
"type": "boolean"
804+
},
805+
"markdownDescription": "Configure glob patterns for excluding Swift package folders from getting activated. This will take precedence over the glob patterns provided to `files.exclude`."
799806
}
800807
}
801808
},
@@ -1778,6 +1785,7 @@
17781785
"@types/lcov-parse": "^1.0.2",
17791786
"@types/lodash.debounce": "^4.0.9",
17801787
"@types/lodash.throttle": "^4.1.9",
1788+
"@types/micromatch": "^4.0.9",
17811789
"@types/mocha": "^10.0.10",
17821790
"@types/mock-fs": "^4.13.4",
17831791
"@types/node": "^20.19.7",
@@ -1806,6 +1814,7 @@
18061814
"lint-staged": "^16.1.2",
18071815
"lodash.debounce": "^4.0.8",
18081816
"lodash.throttle": "^4.1.1",
1817+
"micromatch": "^4.0.8",
18091818
"mocha": "^11.7.1",
18101819
"mock-fs": "^5.5.0",
18111820
"node-pty": "^1.0.0",

src/WorkspaceContext.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext";
1818
import { StatusItem } from "./ui/StatusItem";
1919
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
2020
import { swiftLibraryPathKey } from "./utilities/utilities";
21-
import { isPathInsidePath } from "./utilities/filesystem";
21+
import { isExcluded, isPathInsidePath } from "./utilities/filesystem";
2222
import { LanguageClientToolchainCoordinator } from "./sourcekit-lsp/LanguageClientToolchainCoordinator";
2323
import { TemporaryFolder } from "./utilities/tempFolder";
2424
import { TaskManager } from "./tasks/TaskManager";
@@ -509,6 +509,9 @@ export class WorkspaceContext implements vscode.Disposable {
509509

510510
/** set focus based on the file */
511511
async focusPackageUri(uri: vscode.Uri) {
512+
if (isExcluded(uri)) {
513+
return;
514+
}
512515
const packageFolder = await this.getPackageFolder(uri);
513516
if (packageFolder instanceof FolderContext) {
514517
await this.focusFolder(packageFolder);

src/configuration.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,12 @@ const configuration = {
490490
get disableSandbox(): boolean {
491491
return vscode.workspace.getConfiguration("swift").get<boolean>("disableSandbox", false);
492492
},
493+
/** Workspace folder glob patterns to exclude */
494+
get excludePathsFromActivation(): Record<string, boolean> {
495+
return vscode.workspace
496+
.getConfiguration("swift")
497+
.get<Record<string, boolean>>("excludePathsFromActivation", {});
498+
},
493499
};
494500

495501
const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);

src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { FolderContext } from "../FolderContext";
1818
import { LanguageClientFactory } from "./LanguageClientFactory";
1919
import { LanguageClientManager } from "./LanguageClientManager";
2020
import { FolderOperation, WorkspaceContext } from "../WorkspaceContext";
21+
import { isExcluded } from "../utilities/filesystem";
2122

2223
/**
2324
* Manages the creation of LanguageClient instances for workspace folders.
@@ -64,6 +65,9 @@ export class LanguageClientToolchainCoordinator implements vscode.Disposable {
6465
if (!folder) {
6566
return;
6667
}
68+
if (isExcluded(folder.workspaceFolder.uri)) {
69+
return;
70+
}
6771
const singleServer = folder.swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0));
6872
switch (operation) {
6973
case FolderOperation.add: {

src/utilities/filesystem.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import { contains } from "micromatch";
1516
import * as fs from "fs/promises";
1617
import * as path from "path";
18+
import * as vscode from "vscode";
19+
import { convertPathToPattern, glob as fastGlob, Options } from "fast-glob";
20+
import configuration from "../configuration";
1721

1822
export const validFileTypes = ["swift", "c", "cpp", "h", "hpp", "m", "mm"];
1923

@@ -79,3 +83,76 @@ export function expandFilePathTilde(
7983
}
8084
return path.join(directory, filepath.slice(1));
8185
}
86+
87+
function getDefaultExcludeList(): Record<string, boolean> {
88+
const config = vscode.workspace.getConfiguration("files");
89+
const vscodeExcludeList = config.get<{ [key: string]: boolean }>("exclude", {});
90+
const swiftExcludeList = configuration.excludePathsFromActivation;
91+
return { ...vscodeExcludeList, ...swiftExcludeList };
92+
}
93+
94+
function getGlobPattern(excludeList: Record<string, boolean>): {
95+
include: string[];
96+
exclude: string[];
97+
} {
98+
const exclude: string[] = [];
99+
const include: string[] = [];
100+
for (const key of Object.keys(excludeList)) {
101+
if (excludeList[key]) {
102+
exclude.push(key);
103+
} else {
104+
include.push(key);
105+
}
106+
}
107+
return { include, exclude };
108+
}
109+
110+
export function isIncluded(
111+
uri: vscode.Uri,
112+
excludeList: Record<string, boolean> = getDefaultExcludeList()
113+
): boolean {
114+
let notExcluded = true;
115+
let included = true;
116+
for (const key of Object.keys(excludeList)) {
117+
if (excludeList[key]) {
118+
if (contains(uri.fsPath, key, { contains: true })) {
119+
notExcluded = false;
120+
included = false;
121+
}
122+
} else {
123+
if (contains(uri.fsPath, key, { contains: true })) {
124+
included = true;
125+
}
126+
}
127+
}
128+
if (notExcluded) {
129+
return true;
130+
}
131+
return included;
132+
}
133+
134+
export function isExcluded(
135+
uri: vscode.Uri,
136+
excludeList: Record<string, boolean> = getDefaultExcludeList()
137+
): boolean {
138+
return !isIncluded(uri, excludeList);
139+
}
140+
141+
export async function globDirectory(uri: vscode.Uri, options?: Options): Promise<string[]> {
142+
const { include, exclude } = getGlobPattern(getDefaultExcludeList());
143+
const matches: string[] = await fastGlob(`${convertPathToPattern(uri.fsPath)}/*`, {
144+
ignore: exclude,
145+
absolute: true,
146+
...options,
147+
});
148+
if (include.length > 0) {
149+
matches.push(
150+
...(await fastGlob(include, {
151+
absolute: true,
152+
cwd: uri.fsPath,
153+
...options,
154+
}))
155+
);
156+
}
157+
return matches;
158+
}

0 commit comments

Comments
 (0)