Skip to content

Introduce new swift.exclude setting #1693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion assets/test/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"lldb.verboseLogging": true,
"lldb.launch.terminal": "external",
"lldb-dap.detachOnError": true,
"swift.sourcekit-lsp.backgroundIndexing": "off"
"swift.sourcekit-lsp.backgroundIndexing": "off",
"swift.exclude": {
"**/excluded": true
}
}
15 changes: 15 additions & 0 deletions assets/test/excluded/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "excluded",
products: [
.library(name: "excluded", targets: ["excluded"]),
],
dependencies: [],
targets: [
.target(name: "excluded"),
]
)
6 changes: 6 additions & 0 deletions assets/test/excluded/Sources/excluded/excluded.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public struct excluded {
public private(set) var text = "Hello, World!"

public init() {
}
}
35 changes: 35 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,13 @@
"default": false,
"markdownDescription": "Disable the running of SourceKit-LSP.",
"markdownDeprecationMessage": "**Deprecated**: Please use `#swift.sourcekit-lsp.disable#` instead."
},
"swift.exclude": {
Copy link
Member

@matthewbastien matthewbastien Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards calling this swift.packages.exclude or swift.excludePackages given that it applies specifically to folders/packages. Gives a better indication of what's being excluded. We may also want to add exclusion for other things in the future and this will help differentiate.

"type": "object",
"additionalProperties": {
"type": "boolean"
},
"markdownDescription": "Configure glob patterns for excluding Swift package folders from getting activated. This will take precedence over the glob patterns provided to `files.exclude`."
}
}
},
Expand Down Expand Up @@ -1697,6 +1704,7 @@
"@types/lcov-parse": "^1.0.2",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.9",
"@types/micromatch": "^4.0.9",
"@types/mocha": "^10.0.10",
"@types/mock-fs": "^4.13.4",
"@types/node": "^20.19.2",
Expand All @@ -1723,6 +1731,7 @@
"fantasticon": "^3.0.0",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"micromatch": "^4.0.8",
"mocha": "^11.7.1",
"mock-fs": "^5.5.0",
"node-pty": "^1.0.0",
Expand Down
5 changes: 4 additions & 1 deletion src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { FolderContext } from "./FolderContext";
import { StatusItem } from "./ui/StatusItem";
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
import { swiftLibraryPathKey } from "./utilities/utilities";
import { isPathInsidePath } from "./utilities/filesystem";
import { isExcluded, isPathInsidePath } from "./utilities/filesystem";
import { LanguageClientToolchainCoordinator } from "./sourcekit-lsp/LanguageClientToolchainCoordinator";
import { TemporaryFolder } from "./utilities/tempFolder";
import { TaskManager } from "./tasks/TaskManager";
Expand Down Expand Up @@ -509,6 +509,9 @@ export class WorkspaceContext implements vscode.Disposable {

/** set focus based on the file */
async focusPackageUri(uri: vscode.Uri) {
if (isExcluded(uri)) {
return;
}
const packageFolder = await this.getPackageFolder(uri);
if (packageFolder instanceof FolderContext) {
await this.focusFolder(packageFolder);
Expand Down
6 changes: 6 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ const configuration = {
get disableSandbox(): boolean {
return vscode.workspace.getConfiguration("swift").get<boolean>("disableSandbox", false);
},
/** Workspace folder glob patterns to exclude */
get exclude(): Record<string, boolean> {
return vscode.workspace
.getConfiguration("swift")
.get<Record<string, boolean>>("exclude", {});
},
};

const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);
Expand Down
4 changes: 4 additions & 0 deletions src/sourcekit-lsp/LanguageClientToolchainCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FolderContext } from "../FolderContext";
import { LanguageClientFactory } from "./LanguageClientFactory";
import { LanguageClientManager } from "./LanguageClientManager";
import { FolderOperation, WorkspaceContext } from "../WorkspaceContext";
import { isExcluded } from "../utilities/filesystem";

/**
* Manages the creation of LanguageClient instances for workspace folders.
Expand Down Expand Up @@ -64,6 +65,9 @@ export class LanguageClientToolchainCoordinator implements vscode.Disposable {
if (!folder) {
return;
}
if (isExcluded(folder.workspaceFolder.uri)) {
return;
}
const singleServer = folder.swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0));
switch (operation) {
case FolderOperation.add: {
Expand Down
77 changes: 77 additions & 0 deletions src/utilities/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
//
//===----------------------------------------------------------------------===//

import { contains } from "micromatch";
import * as fs from "fs/promises";
import * as path from "path";
import * as vscode from "vscode";
import { convertPathToPattern, glob as fastGlob, Options } from "fast-glob";
import configuration from "../configuration";

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

Expand Down Expand Up @@ -79,3 +83,76 @@ export function expandFilePathTilde(
}
return path.join(directory, filepath.slice(1));
}

function getDefaultExcludeList(): Record<string, boolean> {
const config = vscode.workspace.getConfiguration("files");
const vscodeExcludeList = config.get<{ [key: string]: boolean }>("exclude", {});
const swiftExcludeList = configuration.exclude;
return { ...vscodeExcludeList, ...swiftExcludeList };
}

function getGlobPattern(excludeList: Record<string, boolean>): {
include: string[];
exclude: string[];
} {
const exclude: string[] = [];
const include: string[] = [];
for (const key of Object.keys(excludeList)) {
if (excludeList[key]) {
exclude.push(key);
} else {
include.push(key);
}
}
return { include, exclude };
}

export function isIncluded(
uri: vscode.Uri,
excludeList: Record<string, boolean> = getDefaultExcludeList()
): boolean {
let notExcluded = true;
let included = true;
for (const key of Object.keys(excludeList)) {
if (excludeList[key]) {
if (contains(uri.fsPath, key, { contains: true })) {
notExcluded = false;
included = false;
}
} else {
if (contains(uri.fsPath, key, { contains: true })) {
included = true;
}
}
}
if (notExcluded) {
return true;
}
return included;
}

export function isExcluded(
uri: vscode.Uri,
excludeList: Record<string, boolean> = getDefaultExcludeList()
): boolean {
return !isIncluded(uri, excludeList);
}

export async function globDirectory(uri: vscode.Uri, options?: Options): Promise<string[]> {
const { include, exclude } = getGlobPattern(getDefaultExcludeList());
const matches: string[] = await fastGlob(`${convertPathToPattern(uri.fsPath)}/*`, {
ignore: exclude,
absolute: true,
...options,
});
if (include.length > 0) {
matches.push(
...(await fastGlob(include, {
absolute: true,
cwd: uri.fsPath,
...options,
}))
);
}
return matches;
}
11 changes: 2 additions & 9 deletions src/utilities/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { pathExists } from "./filesystem";
import { convertPathToPattern, glob } from "fast-glob";
import { globDirectory, pathExists } from "./filesystem";
import { basename } from "path";

export async function searchForPackages(
Expand All @@ -34,13 +33,7 @@ export async function searchForPackages(
return;
}

const config = vscode.workspace.getConfiguration("files");
const vscodeExcludeList = config.get<{ [key: string]: boolean }>("exclude", {});
await glob(`${convertPathToPattern(folder.fsPath)}/*`, {
ignore: [...Object.keys(vscodeExcludeList).filter(k => vscodeExcludeList[k])],
absolute: true,
onlyDirectories: true,
}).then(async entries => {
await globDirectory(folder, { onlyDirectories: true }).then(async entries => {
for (const entry of entries) {
if (basename(entry) !== "." && basename(entry) !== "Packages") {
await search(vscode.Uri.file(entry));
Expand Down
32 changes: 32 additions & 0 deletions test/integration-tests/utilities/workspace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2024 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { searchForPackages } from "../../../src/utilities/workspace";
import { expect } from "chai";

suite("Workspace Utilities Test Suite", () => {
suite("searchForPackages", () => {
test("ignores excluded file", async () => {
const folders = await searchForPackages(
(vscode.workspace.workspaceFolders ?? [])[0]!.uri,
false,
true
);

expect(folders.find(f => f.fsPath.includes("defaultPackage"))).to.not.be.undefined;
expect(folders.find(f => f.fsPath.includes("excluded"))).to.be.undefined;
});
});
});
6 changes: 6 additions & 0 deletions test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ suite("LanguageClientManager Suite", () => {
const mockedVSCodeWindow = mockGlobalObject(vscode, "window");
const mockedVSCodeExtensions = mockGlobalObject(vscode, "extensions");
const mockedVSCodeWorkspace = mockGlobalObject(vscode, "workspace");
const excludeConfig = mockGlobalValue(configuration, "exclude");
let changeConfigEmitter: AsyncEventEmitter<vscode.ConfigurationChangeEvent>;
let createFilesEmitter: AsyncEventEmitter<vscode.FileCreateEvent>;
let deleteFilesEmitter: AsyncEventEmitter<vscode.FileDeleteEvent>;
Expand All @@ -92,6 +93,9 @@ suite("LanguageClientManager Suite", () => {
mockedVSCodeWorkspace.onDidCreateFiles.callsFake(createFilesEmitter.event);
deleteFilesEmitter = new AsyncEventEmitter();
mockedVSCodeWorkspace.onDidDeleteFiles.callsFake(deleteFilesEmitter.event);
mockedVSCodeWorkspace.getConfiguration
.withArgs("files")
.returns({ get: () => ({}) } as any);
// Mock the WorkspaceContext and SwiftToolchain
mockedBuildFlags = mockObject<BuildFlags>({
buildPathFlags: mockFn(s => s.returns([])),
Expand Down Expand Up @@ -208,6 +212,8 @@ suite("LanguageClientManager Suite", () => {
mockedLspConfig.serverArguments = [];
// Process environment variables
mockedEnvironment.setValue({});
// Exclusion
excludeConfig.setValue({});
});

suite("LanguageClientToolchainCoordinator", () => {
Expand Down
Loading