From fceefd6ef2a9b291ce48381322087120c681814e Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 28 Feb 2025 09:26:03 -0500 Subject: [PATCH] Revert "Project Panel (#1382)" This reverts commit 5753b6b1a4c7f54028fb91e63ab63efc1dc70248. --- assets/test/targets/Package.swift | 48 -- .../targets/Plugins/PluginTarget/main.swift | 9 - .../targets/Snippets/AnotherSnippet.swift | 1 - assets/test/targets/Snippets/Snippet.swift | 1 - .../CommandPluginTarget.swift | 0 .../Sources/ExecutableTarget/main.swift | 1 - .../Sources/LibraryTarget/Targets.swift | 9 - .../Tests/AnotherTests/AnotherTests.swift | 8 - .../Tests/TargetsTests/TargetsTests.swift | 8 - package.json | 105 +--- src/PackageWatcher.ts | 12 - src/SwiftPackage.ts | 2 +- src/SwiftSnippets.ts | 50 +- src/TestExplorer/TestRunner.ts | 22 +- src/WorkspaceContext.ts | 52 -- src/commands.ts | 59 +- src/commands/build.ts | 33 +- src/commands/openInExternalEditor.ts | 2 +- src/commands/openInWorkspace.ts | 2 +- .../{runAllTests.ts => runParallelTests.ts} | 16 +- src/commands/runTask.ts | 43 -- src/debugger/launch.ts | 1 - src/extension.ts | 16 +- src/ui/PackageDependencyProvider.ts | 269 +++++++++ src/ui/ProjectPanelProvider.ts | 558 ------------------ src/ui/StatusItem.ts | 4 +- .../commands/dependency.test.ts | 20 +- .../ui/PackageDependencyProvider.test.ts | 166 ++++++ .../ui/ProjectPanelProvider.test.ts | 300 ---------- .../utilities/testutilities.ts | 4 +- .../ui/PackageDependencyProvider.test.ts | 2 +- 31 files changed, 521 insertions(+), 1302 deletions(-) delete mode 100644 assets/test/targets/Package.swift delete mode 100644 assets/test/targets/Plugins/PluginTarget/main.swift delete mode 100644 assets/test/targets/Snippets/AnotherSnippet.swift delete mode 100644 assets/test/targets/Snippets/Snippet.swift delete mode 100644 assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift delete mode 100644 assets/test/targets/Sources/ExecutableTarget/main.swift delete mode 100644 assets/test/targets/Sources/LibraryTarget/Targets.swift delete mode 100644 assets/test/targets/Tests/AnotherTests/AnotherTests.swift delete mode 100644 assets/test/targets/Tests/TargetsTests/TargetsTests.swift rename src/commands/{runAllTests.ts => runParallelTests.ts} (64%) delete mode 100644 src/commands/runTask.ts create mode 100644 src/ui/PackageDependencyProvider.ts delete mode 100644 src/ui/ProjectPanelProvider.ts create mode 100644 test/integration-tests/ui/PackageDependencyProvider.test.ts delete mode 100644 test/integration-tests/ui/ProjectPanelProvider.test.ts diff --git a/assets/test/targets/Package.swift b/assets/test/targets/Package.swift deleted file mode 100644 index 35cda10ab..000000000 --- a/assets/test/targets/Package.swift +++ /dev/null @@ -1,48 +0,0 @@ -// 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: "targets", - products: [ - .library( - name: "LibraryTarget", - targets: ["LibraryTarget"] - ), - .executable( - name: "ExecutableTarget", - targets: ["ExecutableTarget"] - ), - .plugin( - name: "PluginTarget", - targets: ["PluginTarget"] - ), - ], - dependencies: [ - .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), - .package(path: "../defaultPackage"), - ], - targets: [ - .target( - name: "LibraryTarget" - ), - .executableTarget( - name: "ExecutableTarget" - ), - .plugin( - name: "PluginTarget", - capability: .command( - intent: .custom(verb: "testing", description: "A plugin for testing plugins") - ) - ), - .testTarget( - name: "TargetsTests", - dependencies: ["LibraryTarget"] - ), - .testTarget( - name: "AnotherTests", - dependencies: ["LibraryTarget"] - ), - ] -) diff --git a/assets/test/targets/Plugins/PluginTarget/main.swift b/assets/test/targets/Plugins/PluginTarget/main.swift deleted file mode 100644 index 8a2a8680f..000000000 --- a/assets/test/targets/Plugins/PluginTarget/main.swift +++ /dev/null @@ -1,9 +0,0 @@ -import PackagePlugin -import Foundation - -@main -struct MyCommandPlugin: CommandPlugin { - func performCommand(context: PluginContext, arguments: [String]) throws { - print("Plugin Target Hello World") - } -} \ No newline at end of file diff --git a/assets/test/targets/Snippets/AnotherSnippet.swift b/assets/test/targets/Snippets/AnotherSnippet.swift deleted file mode 100644 index 25f53dfa6..000000000 --- a/assets/test/targets/Snippets/AnotherSnippet.swift +++ /dev/null @@ -1 +0,0 @@ -print("Another Snippet Hello World") \ No newline at end of file diff --git a/assets/test/targets/Snippets/Snippet.swift b/assets/test/targets/Snippets/Snippet.swift deleted file mode 100644 index cdd7d267c..000000000 --- a/assets/test/targets/Snippets/Snippet.swift +++ /dev/null @@ -1 +0,0 @@ -print("Snippet Hello World") \ No newline at end of file diff --git a/assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift b/assets/test/targets/Sources/CommandPluginTarget/CommandPluginTarget.swift deleted file mode 100644 index e69de29bb..000000000 diff --git a/assets/test/targets/Sources/ExecutableTarget/main.swift b/assets/test/targets/Sources/ExecutableTarget/main.swift deleted file mode 100644 index 2fcea7ab3..000000000 --- a/assets/test/targets/Sources/ExecutableTarget/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("Executable Target Hello World!") \ No newline at end of file diff --git a/assets/test/targets/Sources/LibraryTarget/Targets.swift b/assets/test/targets/Sources/LibraryTarget/Targets.swift deleted file mode 100644 index 37c4f8832..000000000 --- a/assets/test/targets/Sources/LibraryTarget/Targets.swift +++ /dev/null @@ -1,9 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book - -public func foo() { - print("foo") -} -public func bar() { - print("bar") -} \ No newline at end of file diff --git a/assets/test/targets/Tests/AnotherTests/AnotherTests.swift b/assets/test/targets/Tests/AnotherTests/AnotherTests.swift deleted file mode 100644 index 8aa96db8b..000000000 --- a/assets/test/targets/Tests/AnotherTests/AnotherTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -import LibraryTarget -import XCTest - -class AnotherTests: XCTestCase { - func testExample() { - bar() - } -} \ No newline at end of file diff --git a/assets/test/targets/Tests/TargetsTests/TargetsTests.swift b/assets/test/targets/Tests/TargetsTests/TargetsTests.swift deleted file mode 100644 index 089304193..000000000 --- a/assets/test/targets/Tests/TargetsTests/TargetsTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -import LibraryTarget -import XCTest - -class TargetsTests: XCTestCase { - func testExample() { - foo() - } -} \ No newline at end of file diff --git a/package.json b/package.json index 9101e61f0..112ae5d89 100644 --- a/package.json +++ b/package.json @@ -222,14 +222,12 @@ { "command": "swift.runSnippet", "title": "Run Swift Snippet", - "category": "Swift", - "icon": "$(play)" + "category": "Swift" }, { "command": "swift.debugSnippet", "title": "Debug Swift Snippet", - "category": "Swift", - "icon": "$(debug)" + "category": "Swift" }, { "command": "swift.runPluginTask", @@ -268,27 +266,8 @@ }, { "command": "swift.runAllTestsParallel", - "title": "Run Tests in Parallel", - "category": "Test", - "icon": "$(testing-run-all-icon)" - }, - { - "command": "swift.runAllTests", - "title": "Run Tests", - "category": "Test", - "icon": "$(testing-run-icon)" - }, - { - "command": "swift.debugAllTests", - "title": "Debug Tests", - "category": "Test", - "icon": "$(testing-debug-icon)" - }, - { - "command": "swift.coverAllTests", - "title": "Run Tests with Coverage", - "category": "Test", - "icon": "$(debug-coverage)" + "title": "Run All Tests in Parallel", + "category": "Test" } ], "configuration": [ @@ -931,18 +910,6 @@ { "command": "swift.runAllTestsParallel", "when": "swift.isActivated" - }, - { - "command": "swift.runAllTests", - "when": "swift.isActivated" - }, - { - "command": "swift.debugAllTests", - "when": "swift.isActivated" - }, - { - "command": "swift.coverAllTests", - "when": "swift.isActivated" } ], "editor/context": [ @@ -1004,90 +971,50 @@ "view/title": [ { "command": "swift.updateDependencies", - "when": "view == projectPanel", + "when": "view == packageDependencies", "group": "navigation@1" }, { "command": "swift.resolveDependencies", - "when": "view == projectPanel", + "when": "view == packageDependencies", "group": "navigation@2" }, { "command": "swift.resetPackage", - "when": "view == projectPanel", + "when": "view == packageDependencies", "group": "navigation@3" }, { "command": "swift.flatDependenciesList", - "when": "view == projectPanel && !swift.flatDependenciesList", + "when": "view == packageDependencies && !swift.flatDependenciesList", "group": "navigation@4" }, { "command": "swift.nestedDependenciesList", - "when": "view == projectPanel && swift.flatDependenciesList", + "when": "view == packageDependencies && swift.flatDependenciesList", "group": "navigation@5" } ], "view/item/context": [ { "command": "swift.useLocalDependency", - "when": "view == projectPanel && viewItem == remote" + "when": "view == packageDependencies && viewItem == remote" }, { "command": "swift.uneditDependency", - "when": "view == projectPanel && viewItem == editing" + "when": "view == packageDependencies && viewItem == editing" }, { "command": "swift.openInWorkspace", - "when": "view == projectPanel && viewItem == editing" + "when": "view == packageDependencies && viewItem == editing" }, { "command": "swift.openInWorkspace", - "when": "view == projectPanel && viewItem == local" + "when": "view == packageDependencies && viewItem == local" }, { "command": "swift.openExternal", - "when": "view == projectPanel && (viewItem == 'editing' || viewItem == 'remote')" - }, - { - "command": "swift.run", - "when": "view == projectPanel && viewItem == 'runnable'", - "group": "inline@0" - }, - { - "command": "swift.debug", - "when": "view == projectPanel && viewItem == 'runnable'", - "group": "inline@1" - }, - { - "command": "swift.runSnippet", - "when": "view == projectPanel && viewItem == 'snippet_runnable'", - "group": "inline@0" - }, - { - "command": "swift.debugSnippet", - "when": "view == projectPanel && viewItem == 'snippet_runnable'", - "group": "inline@1" - }, - { - "command": "swift.runAllTests", - "when": "view == projectPanel && viewItem == 'test_runnable'", - "group": "inline@0" - }, - { - "command": "swift.debugAllTests", - "when": "view == projectPanel && viewItem == 'test_runnable'", - "group": "inline@1" - }, - { - "command": "swift.runAllTestsParallel", - "when": "view == projectPanel && viewItem == 'test_runnable'", - "group": "inline@2" - }, - { - "command": "swift.coverAllTests", - "when": "view == projectPanel && viewItem == 'test_runnable'", - "group": "inline@3" + "when": "view == packageDependencies && viewItem != local" } ] }, @@ -1284,8 +1211,8 @@ "views": { "explorer": [ { - "id": "projectPanel", - "name": "Swift Project", + "id": "packageDependencies", + "name": "Package Dependencies", "icon": "$(archive)", "when": "swift.hasPackage" } diff --git a/src/PackageWatcher.ts b/src/PackageWatcher.ts index 86b4840e2..946fe3b5e 100644 --- a/src/PackageWatcher.ts +++ b/src/PackageWatcher.ts @@ -27,7 +27,6 @@ export class PackageWatcher { private packageFileWatcher?: vscode.FileSystemWatcher; private resolvedFileWatcher?: vscode.FileSystemWatcher; private workspaceStateFileWatcher?: vscode.FileSystemWatcher; - private snippetWatcher?: vscode.FileSystemWatcher; constructor( private folderContext: FolderContext, @@ -42,7 +41,6 @@ export class PackageWatcher { this.packageFileWatcher = this.createPackageFileWatcher(); this.resolvedFileWatcher = this.createResolvedFileWatcher(); this.workspaceStateFileWatcher = this.createWorkspaceStateFileWatcher(); - this.snippetWatcher = this.createSnippetFileWatcher(); } /** @@ -53,7 +51,6 @@ export class PackageWatcher { this.packageFileWatcher?.dispose(); this.resolvedFileWatcher?.dispose(); this.workspaceStateFileWatcher?.dispose(); - this.snippetWatcher?.dispose(); } private createPackageFileWatcher(): vscode.FileSystemWatcher { @@ -90,15 +87,6 @@ export class PackageWatcher { return watcher; } - private createSnippetFileWatcher(): vscode.FileSystemWatcher { - const watcher = vscode.workspace.createFileSystemWatcher( - new vscode.RelativePattern(this.folderContext.folder, "Snippets/*.swift") - ); - watcher.onDidCreate(async () => await this.handlePackageSwiftChange()); - watcher.onDidDelete(async () => await this.handlePackageSwiftChange()); - return watcher; - } - /** * Handles a create or change event for **Package.swift**. * diff --git a/src/SwiftPackage.ts b/src/SwiftPackage.ts index 8df08f560..ed8b23c53 100644 --- a/src/SwiftPackage.ts +++ b/src/SwiftPackage.ts @@ -41,7 +41,7 @@ export interface Target { c99name: string; path: string; sources: string[]; - type: "executable" | "test" | "library" | "snippet" | "plugin"; + type: "executable" | "test" | "library" | "snippet"; } /** Swift Package Manager dependency */ diff --git a/src/SwiftSnippets.ts b/src/SwiftSnippets.ts index 22898b94a..f7fa4cccb 100644 --- a/src/SwiftSnippets.ts +++ b/src/SwiftSnippets.ts @@ -48,44 +48,29 @@ export function setSnippetContextKey(ctx: WorkspaceContext) { * If current file is a Swift Snippet run it * @param ctx Workspace Context */ -export async function runSnippet( - ctx: WorkspaceContext, - snippet?: string -): Promise { - return await debugSnippetWithOptions(ctx, { noDebug: true }, snippet); +export async function runSnippet(ctx: WorkspaceContext): Promise { + return await debugSnippetWithOptions(ctx, { noDebug: true }); } /** * If current file is a Swift Snippet run it in the debugger * @param ctx Workspace Context */ -export async function debugSnippet( - ctx: WorkspaceContext, - snippet?: string -): Promise { - return await debugSnippetWithOptions(ctx, {}, snippet); +export async function debugSnippet(ctx: WorkspaceContext): Promise { + return await debugSnippetWithOptions(ctx, {}); } export async function debugSnippetWithOptions( ctx: WorkspaceContext, - options: vscode.DebugSessionOptions, - snippet?: string + options: vscode.DebugSessionOptions ): Promise { - // create build task - let snippetName: string; - if (snippet) { - snippetName = snippet; - } else if (ctx.currentDocument) { - snippetName = path.basename(ctx.currentDocument.fsPath, ".swift"); - } else { - return false; - } - const folderContext = ctx.currentFolder; - if (!folderContext) { - return false; + if (!ctx.currentDocument || !folderContext) { + return; } + // create build task + const snippetName = path.basename(ctx.currentDocument.fsPath, ".swift"); const snippetBuildTask = createSwiftTask( ["build", "--product", snippetName], `Build ${snippetName}`, @@ -99,29 +84,26 @@ export async function debugSnippetWithOptions( }, ctx.toolchain ); - const snippetDebugConfig = createSnippetConfiguration(snippetName, folderContext); - try { - ctx.buildStarted(snippetName, snippetDebugConfig, options); + try { // queue build task and when it is complete run executable in the debugger return await folderContext.taskQueue .queueOperation(new TaskOperation(snippetBuildTask)) .then(result => { if (result === 0) { + const snippetDebugConfig = createSnippetConfiguration( + snippetName, + folderContext + ); return debugLaunchConfig( folderContext.workspaceFolder, snippetDebugConfig, options ); } - }) - .then(result => { - ctx.buildFinished(snippetName, snippetDebugConfig, options); - return result; }); - } catch (error) { - ctx.outputChannel.appendLine(`Failed to debug snippet: ${error}`); + } catch { // ignore error if task failed to run - return false; + return; } } diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index ecd742c61..c42294239 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -520,18 +520,6 @@ export class TestRunner { ]; } - /** - * Extracts a list of unique test Targets from the list of test items. - */ - private testTargets(items: vscode.TestItem[]): string[] { - const targets = new Set(); - for (const item of items) { - const target = item.id.split(".")[0]; - targets.add(target); - } - return Array.from(targets); - } - /** * Test run handler. Run a series of tests and extracts the results from the output * @param shouldDebug Should we run the debugger @@ -539,9 +527,6 @@ export class TestRunner { * @returns When complete */ async runHandler() { - const testTargets = this.testTargets(this.testArgs.testItems); - this.workspaceContext.testsStarted(this.folderContext, this.testKind, testTargets); - const runState = new TestRunnerTestRunState(this.testRun); const cancellationDisposable = this.testRun.token.onCancellationRequested(() => { @@ -566,8 +551,6 @@ export class TestRunner { cancellationDisposable.dispose(); await this.testRun.end(); - - this.workspaceContext.testsFinished(this.folderContext, this.testKind, testTargets); } /** Run test session without attaching to a debugger */ @@ -996,6 +979,11 @@ export class TestRunner { ); } + // show test results pane + vscode.commands.executeCommand( + "testing.showMostRecentOutput" + ); + const terminateSession = vscode.debug.onDidTerminateDebugSession(() => { this.workspaceContext.outputChannel.logDiagnostic( diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 648b6776d..b3a635815 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -33,7 +33,6 @@ import { SwiftToolchain } from "./toolchain/toolchain"; import { DiagnosticsManager } from "./DiagnosticsManager"; import { DocumentationManager } from "./documentation/DocumentationManager"; import { DocCDocumentationRequest, ReIndexProjectRequest } from "./sourcekit-lsp/extensions"; -import { TestKind } from "./TestExplorer/TestKind"; /** * Context for whole workspace. Holds array of contexts for each workspace folder @@ -54,17 +53,6 @@ export class WorkspaceContext implements vscode.Disposable { private lastFocusUri: vscode.Uri | undefined; private initialisationFinished = false; - private readonly testStartEmitter = new vscode.EventEmitter(); - private readonly testFinishEmitter = new vscode.EventEmitter(); - - public onDidStartTests = this.testStartEmitter.event; - public onDidFinishTests = this.testFinishEmitter.event; - - private readonly buildStartEmitter = new vscode.EventEmitter(); - private readonly buildFinishEmitter = new vscode.EventEmitter(); - public onDidStartBuild = this.buildStartEmitter.event; - public onDidFinishBuild = this.buildFinishEmitter.event; - private constructor( extensionContext: vscode.ExtensionContext, public tempFolder: TemporaryFolder, @@ -348,30 +336,6 @@ export class WorkspaceContext implements vscode.Disposable { await this.fireEvent(folderContext, FolderOperation.focus); } - public testsFinished(folder: FolderContext, kind: TestKind, targets: string[]) { - this.testFinishEmitter.fire({ kind, folder, targets }); - } - - public testsStarted(folder: FolderContext, kind: TestKind, targets: string[]) { - this.testStartEmitter.fire({ kind, folder, targets }); - } - - public buildStarted( - targetName: string, - launchConfig: vscode.DebugConfiguration, - options: vscode.DebugSessionOptions - ) { - this.buildStartEmitter.fire({ targetName, launchConfig, options }); - } - - public buildFinished( - targetName: string, - launchConfig: vscode.DebugConfiguration, - options: vscode.DebugSessionOptions - ) { - this.buildFinishEmitter.fire({ targetName, launchConfig, options }); - } - /** * catch workspace folder changes and add or remove folders based on those changes * @param event workspace folder event @@ -630,20 +594,6 @@ export class WorkspaceContext implements vscode.Disposable { private swiftFileObservers = new Set<(listener: SwiftFileEvent) => unknown>(); } -/** Test events for test run begin/end */ -interface TestEvent { - kind: TestKind; - folder: FolderContext; - targets: string[]; -} - -/** Build events for build + run start/stop */ -interface BuildEvent { - targetName: string; - launchConfig: vscode.DebugConfiguration; - options: vscode.DebugSessionOptions; -} - /** Workspace Folder Operation types */ export enum FolderOperation { // Package folder has been added @@ -662,8 +612,6 @@ export enum FolderOperation { workspaceStateUpdated = "workspaceStateUpdated", // .build/workspace-state.json has been updated packageViewUpdated = "packageViewUpdated", - // Package plugins list has been updated - pluginsUpdated = "pluginsUpdated", } /** Workspace Folder Event */ diff --git a/src/commands.ts b/src/commands.ts index a28979167..b88e6cecb 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import { WorkspaceContext } from "./WorkspaceContext"; -import { PackageNode } from "./ui/ProjectPanelProvider"; +import { PackageNode } from "./ui/PackageDependencyProvider"; import { SwiftToolchain } from "./toolchain/toolchain"; import { debugSnippet, runSnippet } from "./SwiftSnippets"; import { showToolchainSelectionQuickPick } from "./ui/ToolchainSelection"; @@ -38,10 +38,8 @@ import { updateDependencies } from "./commands/dependencies/update"; import { runPluginTask } from "./commands/runPluginTask"; import { runTestMultipleTimes } from "./commands/testMultipleTimes"; import { newSwiftFile } from "./commands/newFile"; -import { runAllTests } from "./commands/runAllTests"; +import { runAllTestsParallel } from "./commands/runParallelTests"; import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList"; -import { runTask } from "./commands/runTask"; -import { TestKind } from "./TestExplorer/TestKind"; /** * References: @@ -79,15 +77,8 @@ export enum Commands { RESET_PACKAGE = "swift.resetPackage", USE_LOCAL_DEPENDENCY = "swift.useLocalDependency", UNEDIT_DEPENDENCY = "swift.uneditDependency", - RUN_TASK = "swift.runTask", RUN_PLUGIN_TASK = "swift.runPluginTask", - RUN_SNIPPET = "swift.runSnippet", - DEBUG_SNIPPET = "swift.debugSnippet", PREVIEW_DOCUMENTATION = "swift.previewDocumentation", - RUN_ALL_TESTS = "swift.runAllTests", - RUN_ALL_TESTS_PARALLEL = "swift.runAllTestsParallel", - DEBUG_ALL_TESTS = "swift.debugAllTests", - COVER_ALL_TESTS = "swift.coverAllTests", } /** @@ -102,12 +93,8 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { vscode.commands.registerCommand(Commands.UPDATE_DEPENDENCIES, () => updateDependencies(ctx) ), - vscode.commands.registerCommand(Commands.RUN, target => - runBuild(ctx, ...unwrapTreeItem(target)) - ), - vscode.commands.registerCommand(Commands.DEBUG, target => - debugBuild(ctx, ...unwrapTreeItem(target)) - ), + vscode.commands.registerCommand(Commands.RUN, () => runBuild(ctx)), + vscode.commands.registerCommand(Commands.DEBUG, () => debugBuild(ctx)), vscode.commands.registerCommand(Commands.CLEAN_BUILD, () => cleanBuild(ctx)), vscode.commands.registerCommand(Commands.RUN_TESTS_MULTIPLE_TIMES, item => { if (ctx.currentFolder) { @@ -128,14 +115,9 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { return openPackage(ctx.toolchain.swiftVersion, ctx.currentFolder.folder); } }), - vscode.commands.registerCommand(Commands.RUN_SNIPPET, target => - runSnippet(ctx, ...unwrapTreeItem(target)) - ), - vscode.commands.registerCommand(Commands.DEBUG_SNIPPET, target => - debugSnippet(ctx, ...unwrapTreeItem(target)) - ), + vscode.commands.registerCommand("swift.runSnippet", () => runSnippet(ctx)), + vscode.commands.registerCommand("swift.debugSnippet", () => debugSnippet(ctx)), vscode.commands.registerCommand(Commands.RUN_PLUGIN_TASK, () => runPluginTask()), - vscode.commands.registerCommand(Commands.RUN_TASK, name => runTask(ctx, name)), vscode.commands.registerCommand("swift.restartLSPServer", () => ctx.languageClientManager.restart() ), @@ -174,20 +156,8 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { ), vscode.commands.registerCommand("swift.captureDiagnostics", () => captureDiagnostics(ctx)), vscode.commands.registerCommand( - Commands.RUN_ALL_TESTS_PARALLEL, - async item => await runAllTests(ctx, TestKind.parallel, ...unwrapTreeItem(item)) - ), - vscode.commands.registerCommand( - Commands.RUN_ALL_TESTS, - async item => await runAllTests(ctx, TestKind.standard, ...unwrapTreeItem(item)) - ), - vscode.commands.registerCommand( - Commands.DEBUG_ALL_TESTS, - async item => await runAllTests(ctx, TestKind.debug, ...unwrapTreeItem(item)) - ), - vscode.commands.registerCommand( - Commands.COVER_ALL_TESTS, - async item => await runAllTests(ctx, TestKind.coverage, ...unwrapTreeItem(item)) + "swift.runAllTestsParallel", + async () => await runAllTestsParallel(ctx) ), vscode.commands.registerCommand( Commands.PREVIEW_DOCUMENTATION, @@ -201,16 +171,3 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { ), ]; } - -/** - * Certain commands can be called via a vscode TreeView, which will pass a {@link CommandNode} object. - * If the command is called via a command palette or other means, the target will be a string. - */ -function unwrapTreeItem(target?: string | { args: string[] }): string[] { - if (typeof target === "object" && target !== null && "args" in target) { - return target.args ?? []; - } else if (typeof target === "string") { - return [target]; - } - return []; -} diff --git a/src/commands/build.ts b/src/commands/build.ts index 1ce2744d3..03e66489a 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -18,20 +18,19 @@ import { createSwiftTask, SwiftTaskProvider } from "../tasks/SwiftTaskProvider"; import { debugLaunchConfig, getLaunchConfiguration } from "../debugger/launch"; import { executeTaskWithUI } from "./utilities"; import { FolderContext } from "../FolderContext"; -import { Target } from "../SwiftPackage"; /** * Executes a {@link vscode.Task task} to run swift target. */ -export async function runBuild(ctx: WorkspaceContext, target?: string) { - return await debugBuildWithOptions(ctx, { noDebug: true }, target); +export async function runBuild(ctx: WorkspaceContext) { + return await debugBuildWithOptions(ctx, { noDebug: true }); } /** * Executes a {@link vscode.Task task} to debug swift target. */ -export async function debugBuild(ctx: WorkspaceContext, target?: string) { - return await debugBuildWithOptions(ctx, {}, target); +export async function debugBuild(ctx: WorkspaceContext) { + return await debugBuildWithOptions(ctx, {}); } /** @@ -71,8 +70,7 @@ export async function folderCleanBuild(folderContext: FolderContext) { */ export async function debugBuildWithOptions( ctx: WorkspaceContext, - options: vscode.DebugSessionOptions, - targetName?: string + options: vscode.DebugSessionOptions ) { const current = ctx.currentFolder; if (!current) { @@ -82,19 +80,13 @@ export async function debugBuildWithOptions( return; } - let target: Target | undefined; - if (targetName) { - target = current.swiftPackage.targets.find(target => target.name === targetName); - } else { - const file = vscode.window.activeTextEditor?.document.fileName; - if (!file) { - ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); - return; - } - - target = current.swiftPackage.getTarget(file); + const file = vscode.window.activeTextEditor?.document.fileName; + if (!file) { + ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); + return; } + const target = current.swiftPackage.getTarget(file); if (!target) { ctx.outputChannel.appendLine("debugBuildWithOptions: No active target"); return; @@ -109,9 +101,6 @@ export async function debugBuildWithOptions( const launchConfig = getLaunchConfiguration(target.name, current); if (launchConfig) { - ctx.buildStarted(target.name, launchConfig, options); - const result = await debugLaunchConfig(current.workspaceFolder, launchConfig, options); - ctx.buildFinished(target.name, launchConfig, options); - return result; + return debugLaunchConfig(current.workspaceFolder, launchConfig, options); } } diff --git a/src/commands/openInExternalEditor.ts b/src/commands/openInExternalEditor.ts index 6dc621765..29f4114ab 100644 --- a/src/commands/openInExternalEditor.ts +++ b/src/commands/openInExternalEditor.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { PackageNode } from "../ui/ProjectPanelProvider"; +import { PackageNode } from "../ui/PackageDependencyProvider"; /** * Opens the supplied `PackageNode` externally using the default application. diff --git a/src/commands/openInWorkspace.ts b/src/commands/openInWorkspace.ts index dda3903c0..7b4b9601d 100644 --- a/src/commands/openInWorkspace.ts +++ b/src/commands/openInWorkspace.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import { PackageNode } from "../ui/ProjectPanelProvider"; +import { PackageNode } from "../ui/PackageDependencyProvider"; /** * Open a local package in workspace diff --git a/src/commands/runAllTests.ts b/src/commands/runParallelTests.ts similarity index 64% rename from src/commands/runAllTests.ts rename to src/commands/runParallelTests.ts index f629417ad..11970b803 100644 --- a/src/commands/runAllTests.ts +++ b/src/commands/runParallelTests.ts @@ -17,29 +17,23 @@ import { TestKind } from "../TestExplorer/TestKind"; import { WorkspaceContext } from "../WorkspaceContext"; import { flattenTestItemCollection } from "../TestExplorer/TestUtils"; -export async function runAllTests(ctx: WorkspaceContext, testKind: TestKind, target?: string) { +export async function runAllTestsParallel(ctx: WorkspaceContext) { const testExplorer = ctx.currentFolder?.testExplorer; if (testExplorer === undefined) { return; } - const profile = testExplorer.testRunProfiles.find(profile => profile.label === testKind); + const profile = testExplorer.testRunProfiles.find( + profile => profile.label === TestKind.parallel + ); if (profile === undefined) { return; } - let tests = flattenTestItemCollection(testExplorer.controller.items); - - // If a target is specified, filter the tests to only run those that match the target. - if (target) { - const targetRegex = new RegExp(`^${target}(\\.|$)`); - tests = tests.filter(test => targetRegex.test(test.id)); - } + const tests = flattenTestItemCollection(testExplorer.controller.items); const tokenSource = new vscode.CancellationTokenSource(); await profile.runHandler( new vscode.TestRunRequest(tests, undefined, profile), tokenSource.token ); - - await vscode.commands.executeCommand("testing.showMostRecentOutput"); } diff --git a/src/commands/runTask.ts b/src/commands/runTask.ts deleted file mode 100644 index 3bca73535..000000000 --- a/src/commands/runTask.ts +++ /dev/null @@ -1,43 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2021-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 { WorkspaceContext } from "../WorkspaceContext"; -import { TaskOperation } from "../tasks/TaskQueue"; -import { SwiftPluginTaskProvider } from "../tasks/SwiftPluginTaskProvider"; - -export const runTask = async (ctx: WorkspaceContext, name: string) => { - if (!ctx.currentFolder) { - return; - } - - const tasks = await vscode.tasks.fetchTasks(); - let task = tasks.find(task => task.name === name); - if (!task) { - const pluginTaskProvider = new SwiftPluginTaskProvider(ctx); - const pluginTasks = await pluginTaskProvider.provideTasks( - new vscode.CancellationTokenSource().token - ); - task = pluginTasks.find(task => task.name === name); - } - - if (!task) { - vscode.window.showErrorMessage(`Task "${name}" not found`); - return; - } - - return ctx.currentFolder.taskQueue - .queueOperation(new TaskOperation(task)) - .then(result => result === 0); -}; diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index b91836828..2b0d2a08b 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -181,7 +181,6 @@ export function createSnippetConfiguration( args: [], cwd: folder, env: swiftRuntimeEnv(true), - runType: "snippet", ...CI_DISABLE_ASLR, }; } diff --git a/src/extension.ts b/src/extension.ts index 15e45be46..661696230 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,7 +18,7 @@ import "source-map-support/register"; import * as vscode from "vscode"; import * as commands from "./commands"; import * as debug from "./debugger/launch"; -import { ProjectPanelProvider } from "./ui/ProjectPanelProvider"; +import { PackageDependenciesProvider } from "./ui/PackageDependencyProvider"; import { SwiftTaskProvider } from "./tasks/SwiftTaskProvider"; import { FolderOperation, WorkspaceContext } from "./WorkspaceContext"; import { FolderContext } from "./FolderContext"; @@ -160,13 +160,13 @@ export async function activate(context: vscode.ExtensionContext): Promise { ); }); - // project panel provider - const projectPanelProvider = new ProjectPanelProvider(workspaceContext); - const dependenciesView = vscode.window.createTreeView("projectPanel", { - treeDataProvider: projectPanelProvider, + // dependency view + const dependenciesProvider = new PackageDependenciesProvider(workspaceContext); + const dependenciesView = vscode.window.createTreeView("packageDependencies", { + treeDataProvider: dependenciesProvider, showCollapseAll: true, }); - projectPanelProvider.observeFolders(dependenciesView); + dependenciesProvider.observeFolders(dependenciesView); // observer that will resolve package and build launch configurations const resolvePackageObserver = workspaceContext.onDidChangeFolders( @@ -189,7 +189,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { } else { await resolveFolderDependencies(folder, true); } - if ( workspace.toolchain.swiftVersion.isGreaterThanOrEqual( new Version(5, 6, 0) @@ -202,7 +201,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { async () => { await folder.loadSwiftPlugins(); workspace.updatePluginContextKey(); - folder.fireEvent(FolderOperation.pluginsUpdated); } ); } @@ -254,7 +252,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { testExplorerObserver, swiftModuleDocumentProvider, dependenciesView, - projectPanelProvider, + dependenciesProvider, logObserver, languageStatusItem, pluginTaskProvider, diff --git a/src/ui/PackageDependencyProvider.ts b/src/ui/PackageDependencyProvider.ts new file mode 100644 index 000000000..6cd038935 --- /dev/null +++ b/src/ui/PackageDependencyProvider.ts @@ -0,0 +1,269 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021 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 * as fs from "fs/promises"; +import * as path from "path"; +import configuration from "../configuration"; +import { WorkspaceContext } from "../WorkspaceContext"; +import { FolderOperation } from "../WorkspaceContext"; +import contextKeys from "../contextKeys"; +import { Dependency, ResolvedDependency } from "../SwiftPackage"; + +/** + * References: + * + * - Contributing views: + * https://code.visualstudio.com/api/references/contribution-points#contributes.views + * - Contributing welcome views: + * https://code.visualstudio.com/api/references/contribution-points#contributes.viewsWelcome + * - Implementing a TreeView: + * https://code.visualstudio.com/api/extension-guides/tree-view + */ + +/** + * Returns a {@link FileNode} for every file or subdirectory + * in the given directory. + */ +async function getChildren(directoryPath: string, parentId?: string): Promise { + const contents = await fs.readdir(directoryPath); + const results: FileNode[] = []; + const excludes = configuration.excludePathsFromPackageDependencies; + for (const fileName of contents) { + if (excludes.includes(fileName)) { + continue; + } + const filePath = path.join(directoryPath, fileName); + const stats = await fs.stat(filePath); + results.push(new FileNode(fileName, filePath, stats.isDirectory(), parentId)); + } + return results.sort((first, second) => { + if (first.isDirectory === second.isDirectory) { + // If both nodes are of the same type, sort them by name. + return first.name.localeCompare(second.name); + } else { + // Otherwise, sort directories first. + return first.isDirectory ? -1 : 1; + } + }); +} + +/** + * A package in the Package Dependencies {@link vscode.TreeView TreeView}. + */ +export class PackageNode { + private id: string; + + constructor( + private dependency: ResolvedDependency, + private childDependencies: (dependency: Dependency) => ResolvedDependency[], + private parentId?: string + ) { + this.id = + (this.parentId ? `${this.parentId}->` : "") + + `${this.name}-${this.dependency.version ?? ""}`; + } + + get name(): string { + return this.dependency.identity; + } + + get location(): string { + return this.dependency.location; + } + + get type(): string { + return this.dependency.type; + } + + get path(): string { + return this.dependency.path ?? ""; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); + item.id = this.id; + item.description = this.dependency.version; + item.iconPath = + this.dependency.type === "editing" + ? new vscode.ThemeIcon("edit") + : new vscode.ThemeIcon("package"); + item.contextValue = this.dependency.type; + item.accessibilityInformation = { label: `Package ${this.name}` }; + item.tooltip = this.path; + return item; + } + + async getChildren(): Promise { + const [childDeps, files] = await Promise.all([ + this.childDependencies(this.dependency), + getChildren(this.dependency.path, this.id), + ]); + const childNodes = childDeps.map( + dep => new PackageNode(dep, this.childDependencies, this.id) + ); + + // Show dependencies first, then files. + return [...childNodes, ...files]; + } +} + +/** + * A file or directory in the Package Dependencies {@link vscode.TreeView TreeView}. + */ +export class FileNode { + private id: string; + + constructor( + public name: string, + public path: string, + public isDirectory: boolean, + private parentId?: string + ) { + this.id = (this.parentId ? `${this.parentId}->` : "") + `${this.path}`; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem( + this.name, + this.isDirectory + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + ); + item.id = this.id; + item.resourceUri = vscode.Uri.file(this.path); + item.tooltip = this.path; + if (!this.isDirectory) { + item.command = { + command: "vscode.open", + arguments: [item.resourceUri], + title: "Open File", + }; + item.accessibilityInformation = { label: `File ${this.name}` }; + } else { + item.accessibilityInformation = { label: `Folder ${this.name}` }; + } + return item; + } + + async getChildren(): Promise { + return await getChildren(this.path, this.id); + } +} + +/** + * A node in the Package Dependencies {@link vscode.TreeView TreeView}. + * + * Can be either a {@link PackageNode} or a {@link FileNode}. + */ +type TreeNode = PackageNode | FileNode; + +/** + * A {@link vscode.TreeDataProvider TreeDataProvider} for the Package Dependencies {@link vscode.TreeView TreeView}. + */ +export class PackageDependenciesProvider implements vscode.TreeDataProvider { + private didChangeTreeDataEmitter = new vscode.EventEmitter< + TreeNode | undefined | null | void + >(); + private workspaceObserver?: vscode.Disposable; + + onDidChangeTreeData = this.didChangeTreeDataEmitter.event; + + constructor(private workspaceContext: WorkspaceContext) { + // default context key to false. These will be updated as folders are given focus + contextKeys.hasPackage = false; + contextKeys.packageHasDependencies = false; + } + + dispose() { + this.workspaceObserver?.dispose(); + } + + observeFolders(treeView: vscode.TreeView) { + this.workspaceObserver = this.workspaceContext.onDidChangeFolders( + ({ folder, operation }) => { + switch (operation) { + case FolderOperation.focus: + if (!folder) { + return; + } + treeView.title = `Package Dependencies (${folder.name})`; + this.didChangeTreeDataEmitter.fire(); + break; + case FolderOperation.unfocus: + treeView.title = `Package Dependencies`; + this.didChangeTreeDataEmitter.fire(); + break; + case FolderOperation.workspaceStateUpdated: + case FolderOperation.resolvedUpdated: + case FolderOperation.packageViewUpdated: + if (!folder) { + return; + } + if (folder === this.workspaceContext.currentFolder) { + this.didChangeTreeDataEmitter.fire(); + } + } + } + ); + } + + getTreeItem(element: TreeNode): vscode.TreeItem { + return element.toTreeItem(); + } + + async getChildren(element?: TreeNode): Promise { + const folderContext = this.workspaceContext.currentFolder; + if (!folderContext) { + return []; + } + if (!element) { + if (contextKeys.flatDependenciesList) { + const existenceMap = new Map(); + const gatherChildren = ( + dependencies: ResolvedDependency[] + ): ResolvedDependency[] => { + const result: ResolvedDependency[] = []; + for (const dep of dependencies) { + if (!existenceMap.has(dep.identity)) { + result.push(dep); + existenceMap.set(dep.identity, true); + } + const childDeps = folderContext.swiftPackage.childDependencies(dep); + result.push(...gatherChildren(childDeps)); + } + return result; + }; + + const rootDeps = folderContext.swiftPackage.rootDependencies(); + const allDeps = gatherChildren(rootDeps); + return allDeps.map(dependency => new PackageNode(dependency, () => [])); + } else { + return folderContext.swiftPackage + .rootDependencies() + .map( + dependency => + new PackageNode( + dependency, + folderContext.swiftPackage.childDependencies.bind( + folderContext.swiftPackage + ) + ) + ); + } + } else { + return await element.getChildren(); + } + } +} diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts deleted file mode 100644 index 793bebe1f..000000000 --- a/src/ui/ProjectPanelProvider.ts +++ /dev/null @@ -1,558 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2021 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 * as fs from "fs/promises"; -import * as path from "path"; -import configuration from "../configuration"; -import { WorkspaceContext } from "../WorkspaceContext"; -import { FolderOperation } from "../WorkspaceContext"; -import contextKeys from "../contextKeys"; -import { Dependency, ResolvedDependency, Target } from "../SwiftPackage"; -import { SwiftPluginTaskProvider } from "../tasks/SwiftPluginTaskProvider"; - -const LOADING_ICON = "loading~spin"; -/** - * References: - * - * - Contributing views: - * https://code.visualstudio.com/api/references/contribution-points#contributes.views - * - Contributing welcome views: - * https://code.visualstudio.com/api/references/contribution-points#contributes.viewsWelcome - * - Implementing a TreeView: - * https://code.visualstudio.com/api/extension-guides/tree-view - */ - -/** - * Returns a {@link FileNode} for every file or subdirectory - * in the given directory. - */ -async function getChildren(directoryPath: string, parentId?: string): Promise { - const contents = await fs.readdir(directoryPath); - const results: FileNode[] = []; - const excludes = configuration.excludePathsFromPackageDependencies; - for (const fileName of contents) { - if (excludes.includes(fileName)) { - continue; - } - const filePath = path.join(directoryPath, fileName); - const stats = await fs.stat(filePath); - results.push(new FileNode(fileName, filePath, stats.isDirectory(), parentId)); - } - return results.sort((first, second) => { - if (first.isDirectory === second.isDirectory) { - // If both nodes are of the same type, sort them by name. - return first.name.localeCompare(second.name); - } else { - // Otherwise, sort directories first. - return first.isDirectory ? -1 : 1; - } - }); -} - -/** - * A package in the Package Dependencies {@link vscode.TreeView TreeView}. - */ -export class PackageNode { - private id: string; - - constructor( - private dependency: ResolvedDependency, - private childDependencies: (dependency: Dependency) => ResolvedDependency[], - private parentId?: string - ) { - this.id = - (this.parentId ? `${this.parentId}->` : "") + - `${this.name}-${this.dependency.version ?? ""}`; - } - - get name(): string { - return this.dependency.identity; - } - - get location(): string { - return this.dependency.location; - } - - get type(): string { - return this.dependency.type; - } - - get path(): string { - return this.dependency.path ?? ""; - } - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); - item.id = this.id; - item.description = this.dependency.version; - item.iconPath = new vscode.ThemeIcon(this.icon()); - item.contextValue = this.dependency.type; - item.accessibilityInformation = { label: `Package ${this.name}` }; - item.tooltip = this.path; - return item; - } - - icon() { - if (this.dependency.type === "editing") { - return "edit"; - } - if (this.dependency.type === "local") { - return "notebook-render-output"; - } - return "package"; - } - - async getChildren(): Promise { - const [childDeps, files] = await Promise.all([ - this.childDependencies(this.dependency), - getChildren(this.dependency.path, this.id), - ]); - const childNodes = childDeps.map( - dep => new PackageNode(dep, this.childDependencies, this.id) - ); - - // Show dependencies first, then files. - return [...childNodes, ...files]; - } -} - -/** - * A file or directory in the Package Dependencies {@link vscode.TreeView TreeView}. - */ -export class FileNode { - private id: string; - - constructor( - public name: string, - public path: string, - public isDirectory: boolean, - private parentId?: string - ) { - this.id = (this.parentId ? `${this.parentId}->` : "") + `${this.path}`; - } - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem( - this.name, - this.isDirectory - ? vscode.TreeItemCollapsibleState.Collapsed - : vscode.TreeItemCollapsibleState.None - ); - item.id = this.id; - item.resourceUri = vscode.Uri.file(this.path); - item.tooltip = this.path; - if (!this.isDirectory) { - item.command = { - command: "vscode.open", - arguments: [item.resourceUri], - title: "Open File", - }; - item.accessibilityInformation = { label: `File ${this.name}` }; - } else { - item.accessibilityInformation = { label: `Folder ${this.name}` }; - } - return item; - } - - async getChildren(): Promise { - return await getChildren(this.path, this.id); - } -} - -class TaskNode { - constructor( - public type: string, - public name: string, - private active: boolean - ) {} - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.None); - item.id = `${this.type}-${this.name}`; - item.iconPath = new vscode.ThemeIcon(this.active ? LOADING_ICON : "play"); - item.contextValue = "task"; - item.accessibilityInformation = { label: this.name }; - item.command = { - command: "swift.runTask", - arguments: [this.name], - title: "Run Task", - }; - return item; - } - - getChildren(): TreeNode[] { - return []; - } -} - -/* - * Prefix a unique string on the test target name to avoid confusing it - * with another target that may share the same name. Targets can't start with % - * so this is guarenteed to be unique. - */ -function testTaskName(name: string): string { - return `%test-${name}`; -} - -function snippetTaskName(name: string): string { - return `%snippet-${name}`; -} - -class TargetNode { - constructor( - public target: Target, - private activeTasks: Set - ) {} - - get name(): string { - return this.target.name; - } - - get args(): string[] { - return [this.name]; - } - - toTreeItem(): vscode.TreeItem { - const name = this.target.name; - const hasChildren = this.getChildren().length > 0; - const item = new vscode.TreeItem( - name, - hasChildren - ? vscode.TreeItemCollapsibleState.Expanded - : vscode.TreeItemCollapsibleState.None - ); - item.id = `${this.target.type}:${name}`; - item.iconPath = new vscode.ThemeIcon(this.icon()); - item.contextValue = this.contextValue(); - item.accessibilityInformation = { label: name }; - return item; - } - - private icon(): string { - if (this.activeTasks.has(this.name)) { - return LOADING_ICON; - } - - switch (this.target.type) { - case "executable": - return "output"; - case "library": - return "library"; - case "test": - if (this.activeTasks.has(testTaskName(this.name))) { - return LOADING_ICON; - } - return "test-view-icon"; - case "snippet": - if (this.activeTasks.has(snippetTaskName(this.name))) { - return LOADING_ICON; - } - return "notebook"; - case "plugin": - return "plug"; - } - } - - private contextValue(): string | undefined { - switch (this.target.type) { - case "executable": - return "runnable"; - case "snippet": - return "snippet_runnable"; - case "test": - return "test_runnable"; - default: - return undefined; - } - } - - getChildren(): TreeNode[] { - return []; - } -} - -class HeaderNode { - constructor( - private id: string, - public name: string, - private icon: string, - private _getChildren: () => Promise - ) {} - - get path(): string { - return ""; - } - - toTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Collapsed); - item.id = `${this.id}-${this.name}`; - item.iconPath = new vscode.ThemeIcon(this.icon); - item.contextValue = "header"; - item.accessibilityInformation = { label: this.name }; - return item; - } - - getChildren(): Promise { - return this._getChildren(); - } -} - -/** - * A node in the Package Dependencies {@link vscode.TreeView TreeView}. - * - * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode} or {@link HeaderNode}. - */ -type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode; - -/** - * A {@link vscode.TreeDataProvider TreeDataProvider} for project dependencies, tasks and commands {@link vscode.TreeView TreeView}. - */ -export class ProjectPanelProvider implements vscode.TreeDataProvider { - private didChangeTreeDataEmitter = new vscode.EventEmitter< - TreeNode | undefined | null | void - >(); - private workspaceObserver?: vscode.Disposable; - private disposables: vscode.Disposable[] = []; - private activeTasks: Set = new Set(); - - onDidChangeTreeData = this.didChangeTreeDataEmitter.event; - - constructor(private workspaceContext: WorkspaceContext) { - // default context key to false. These will be updated as folders are given focus - contextKeys.hasPackage = false; - contextKeys.packageHasDependencies = false; - - this.observeTasks(workspaceContext); - } - - dispose() { - this.workspaceObserver?.dispose(); - } - - observeTasks(ctx: WorkspaceContext) { - this.disposables.push( - vscode.tasks.onDidStartTask(e => { - const taskId = e.execution.task.detail ?? e.execution.task.name; - this.activeTasks.add(taskId); - this.didChangeTreeDataEmitter.fire(); - }), - vscode.tasks.onDidEndTask(e => { - const taskId = e.execution.task.detail ?? e.execution.task.name; - this.activeTasks.delete(taskId); - this.didChangeTreeDataEmitter.fire(); - }), - ctx.onDidStartBuild(e => { - if (e.launchConfig.runType === "snippet") { - this.activeTasks.add(snippetTaskName(e.targetName)); - } else { - this.activeTasks.add(e.targetName); - } - this.didChangeTreeDataEmitter.fire(); - }), - ctx.onDidFinishBuild(e => { - if (e.launchConfig.runType === "snippet") { - this.activeTasks.delete(snippetTaskName(e.targetName)); - } else { - this.activeTasks.delete(e.targetName); - } - this.didChangeTreeDataEmitter.fire(); - }), - ctx.onDidStartTests(e => { - for (const target of e.targets) { - this.activeTasks.add(testTaskName(target)); - } - this.didChangeTreeDataEmitter.fire(); - }), - ctx.onDidFinishTests(e => { - for (const target of e.targets) { - this.activeTasks.delete(testTaskName(target)); - } - this.didChangeTreeDataEmitter.fire(); - }) - ); - } - - observeFolders(treeView: vscode.TreeView) { - this.workspaceObserver = this.workspaceContext.onDidChangeFolders( - ({ folder, operation }) => { - switch (operation) { - case FolderOperation.focus: - if (!folder) { - return; - } - treeView.title = `Swift Project (${folder.name})`; - this.didChangeTreeDataEmitter.fire(); - break; - case FolderOperation.unfocus: - treeView.title = `Swift Project`; - this.didChangeTreeDataEmitter.fire(); - break; - case FolderOperation.workspaceStateUpdated: - case FolderOperation.resolvedUpdated: - case FolderOperation.packageViewUpdated: - case FolderOperation.pluginsUpdated: - if (!folder) { - return; - } - if (folder === this.workspaceContext.currentFolder) { - this.didChangeTreeDataEmitter.fire(); - } - } - } - ); - } - - getTreeItem(element: TreeNode): vscode.TreeItem { - return element.toTreeItem(); - } - - async getChildren(element?: TreeNode): Promise { - const folderContext = this.workspaceContext.currentFolder; - if (!folderContext) { - return []; - } - - if (element) { - return element.getChildren(); - } - - const dependencies = this.dependencies(); - const snippets = this.snippets(); - const commands = await this.commands(); - - // TODO: Control ordering - return [ - ...(dependencies.length > 0 - ? [ - new HeaderNode( - "dependencies", - "Dependencies", - "circuit-board", - this.wrapInAsync(this.dependencies.bind(this)) - ), - ] - : []), - new HeaderNode("targets", "Targets", "book", this.wrapInAsync(this.targets.bind(this))), - new HeaderNode("tasks", "Tasks", "debug-continue-small", this.tasks.bind(this)), - ...(snippets.length > 0 - ? [ - new HeaderNode("snippets", "Snippets", "notebook", () => - Promise.resolve(snippets) - ), - ] - : []), - ...(commands.length > 0 - ? [ - new HeaderNode("commands", "Commands", "debug-line-by-line", () => - Promise.resolve(commands) - ), - ] - : []), - ]; - } - - private dependencies(): TreeNode[] { - const folderContext = this.workspaceContext.currentFolder; - if (!folderContext) { - return []; - } - const pkg = folderContext.swiftPackage; - if (contextKeys.flatDependenciesList) { - const existenceMap = new Map(); - const gatherChildren = (dependencies: ResolvedDependency[]): ResolvedDependency[] => { - const result: ResolvedDependency[] = []; - for (const dep of dependencies) { - if (!existenceMap.has(dep.identity)) { - result.push(dep); - existenceMap.set(dep.identity, true); - } - const childDeps = pkg.childDependencies(dep); - result.push(...gatherChildren(childDeps)); - } - return result; - }; - - const rootDeps = pkg.rootDependencies(); - const allDeps = gatherChildren(rootDeps); - return allDeps.map(dependency => new PackageNode(dependency, () => [])); - } else { - const childDeps = pkg.childDependencies.bind(pkg); - return pkg.rootDependencies().map(dep => new PackageNode(dep, childDeps)); - } - } - - private targets(): TreeNode[] { - const folderContext = this.workspaceContext.currentFolder; - if (!folderContext) { - return []; - } - const targetSort = (node: TargetNode) => `${node.target.type}-${node.name}`; - return ( - folderContext.swiftPackage.targets - // Snipepts are shown under the Snippets header - .filter(target => target.type !== "snippet") - .map(target => new TargetNode(target, this.activeTasks)) - .sort((a, b) => targetSort(a).localeCompare(targetSort(b))) - ); - } - - private async tasks(): Promise { - const tasks = await vscode.tasks.fetchTasks(); - return ( - tasks - // Plugin tasks are shown under the Commands header - .filter(task => task.source !== "swift-plugin") - .map( - task => - new TaskNode( - "task", - task.name, - this.activeTasks.has(task.detail ?? task.name) - ) - ) - .sort((a, b) => a.name.localeCompare(b.name)) - ); - } - - private async commands(): Promise { - const provider = new SwiftPluginTaskProvider(this.workspaceContext); - const tasks = await provider.provideTasks(new vscode.CancellationTokenSource().token); - return tasks - .map( - task => - new TaskNode( - "command", - task.name, - this.activeTasks.has(task.detail ?? task.name) - ) - ) - .sort((a, b) => a.name.localeCompare(b.name)); - } - - private snippets(): TreeNode[] { - const folderContext = this.workspaceContext.currentFolder; - if (!folderContext) { - return []; - } - return folderContext.swiftPackage.targets - .filter(target => target.type === "snippet") - .flatMap(target => new TargetNode(target, this.activeTasks)) - .sort((a, b) => a.name.localeCompare(b.name)); - } - - private wrapInAsync(fn: () => T): () => Promise { - return async () => fn(); - } -} diff --git a/src/ui/StatusItem.ts b/src/ui/StatusItem.ts index 3d78ec29a..107429845 100644 --- a/src/ui/StatusItem.ts +++ b/src/ui/StatusItem.ts @@ -116,9 +116,9 @@ export class StatusItem { private showTask(task: RunningTask, message?: string) { message = message ?? task.name; if (typeof task.task !== "string") { - this.show(`$(loading~spin) ${message}`, message, "workbench.action.tasks.showTasks"); + this.show(`$(sync~spin) ${message}`, message, "workbench.action.tasks.showTasks"); } else { - this.show(`$(loading~spin) ${message}`, message); + this.show(`$(sync~spin) ${message}`, message); } } diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 771d3c8a4..afa7e40e4 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -14,7 +14,10 @@ import { expect } from "chai"; import * as vscode from "vscode"; -import { PackageNode, ProjectPanelProvider } from "../../../src/ui/ProjectPanelProvider"; +import { + PackageDependenciesProvider, + PackageNode, +} from "../../../src/ui/PackageDependencyProvider"; import { testAssetUri } from "../../fixtures"; import { FolderContext } from "../../../src/FolderContext"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -25,8 +28,8 @@ import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout - // 120 seconds for each test should be more than enough - this.timeout(120 * 1000); + // 60 seconds for each test should be more than enough + this.timeout(60 * 1000); let defaultContext: FolderContext; let depsContext: FolderContext; @@ -54,12 +57,12 @@ suite("Dependency Commmands Test Suite", function () { }); suite("Swift: Use Local Dependency", function () { - let treeProvider: ProjectPanelProvider; + let treeProvider: PackageDependenciesProvider; setup(async () => { await workspaceContext.focusFolder(depsContext); await executeTaskAndWaitForResult((await getBuildAllTask(depsContext)) as SwiftTask); - treeProvider = new ProjectPanelProvider(workspaceContext); + treeProvider = new PackageDependenciesProvider(workspaceContext); }); teardown(() => { @@ -67,11 +70,8 @@ suite("Dependency Commmands Test Suite", function () { }); async function getDependency() { - const headers = await treeProvider.getChildren(); - const header = headers.find(n => n.name === "Dependencies") as PackageNode; - expect(header).to.not.be.undefined; - const children = await header.getChildren(); - return children.find(n => n.name === "swift-markdown") as PackageNode; + const items = await treeProvider.getChildren(); + return items.find(n => n.name === "swift-markdown") as PackageNode; } // Wait for the dependency to switch to the expected state. diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts new file mode 100644 index 000000000..72b7ddaa3 --- /dev/null +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -0,0 +1,166 @@ +//===----------------------------------------------------------------------===// +// +// 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 { expect } from "chai"; +import * as vscode from "vscode"; +import * as path from "path"; +import { + PackageDependenciesProvider, + PackageNode, +} from "../../../src/ui/PackageDependencyProvider"; +import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; +import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; +import { testAssetPath } from "../../fixtures"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; +import contextKeys from "../../../src/contextKeys"; +import { FolderContext } from "../../../src/FolderContext"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; + +suite("PackageDependencyProvider Test Suite", function () { + let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; + let treeProvider: PackageDependenciesProvider; + this.timeout(3 * 60 * 1000); // Allow up to 3 minutes to build + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); + await folderContext.reload(); + treeProvider = new PackageDependenciesProvider(workspaceContext); + await workspaceContext.focusFolder(folderContext); + }, + async teardown() { + contextKeys.flatDependenciesList = false; + treeProvider.dispose(); + }, + }); + + setup(async () => { + await workspaceContext.focusFolder(folderContext); + }); + + test("Includes remote dependency", async () => { + contextKeys.flatDependenciesList = false; + const items = await treeProvider.getChildren(); + + const dep = items.find(n => n.name === "swift-markdown") as PackageNode; + expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; + expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); + assertPathsEqual( + dep?.path, + path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown") + ); + }); + + test("Includes local dependency", async () => { + const items = await treeProvider.getChildren(); + + const dep = items.find(n => n.name === "defaultpackage") as PackageNode; + expect( + dep, + `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` + ).to.not.be.undefined; + assertPathsEqual(dep?.location, testAssetPath("defaultPackage")); + assertPathsEqual(dep?.path, testAssetPath("defaultPackage")); + }); + + test("Lists local dependency file structure", async () => { + contextKeys.flatDependenciesList = false; + const items = await treeProvider.getChildren(); + + const dep = items.find(n => n.name === "defaultpackage") as PackageNode; + expect( + dep, + `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` + ).to.not.be.undefined; + + const folders = await treeProvider.getChildren(dep); + const folder = folders.find(n => n.name === "Sources"); + expect(folder).to.not.be.undefined; + + assertPathsEqual(folder?.path, path.join(testAssetPath("defaultPackage"), "Sources")); + + const childFolders = await treeProvider.getChildren(folder); + const childFolder = childFolders.find(n => n.name === "PackageExe"); + expect(childFolder).to.not.be.undefined; + + assertPathsEqual( + childFolder?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe") + ); + + const files = await treeProvider.getChildren(childFolder); + const file = files.find(n => n.name === "main.swift"); + expect(file).to.not.be.undefined; + + assertPathsEqual( + file?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe/main.swift") + ); + }); + + test("Lists remote dependency file structure", async () => { + contextKeys.flatDependenciesList = false; + const items = await treeProvider.getChildren(); + + const dep = items.find(n => n.name === "swift-markdown") as PackageNode; + expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; + + const folders = await treeProvider.getChildren(dep); + const folder = folders.find(n => n.name === "Sources"); + expect(folder).to.not.be.undefined; + + const depPath = path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown"); + assertPathsEqual(folder?.path, path.join(depPath, "Sources")); + + const childFolders = await treeProvider.getChildren(folder); + const childFolder = childFolders.find(n => n.name === "CAtomic"); + expect(childFolder).to.not.be.undefined; + + assertPathsEqual(childFolder?.path, path.join(depPath, "Sources/CAtomic")); + + const files = await treeProvider.getChildren(childFolder); + const file = files.find(n => n.name === "CAtomic.c"); + expect(file).to.not.be.undefined; + + assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); + }); + + test("Shows a flat dependency list", async () => { + contextKeys.flatDependenciesList = true; + const items = await treeProvider.getChildren(); + expect(items.length).to.equal(3); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "swift-cmark")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + + test("Shows a nested dependency list", async () => { + contextKeys.flatDependenciesList = false; + const items = await treeProvider.getChildren(); + expect(items.length).to.equal(2); + expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; + expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; + }); + + function assertPathsEqual(path1: string | undefined, path2: string | undefined) { + expect(path1).to.not.be.undefined; + expect(path2).to.not.be.undefined; + // Convert to vscode.Uri to normalize paths, including drive letter capitalization on Windows. + expect(vscode.Uri.file(path1!).fsPath).to.equal(vscode.Uri.file(path2!).fsPath); + } +}); diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts deleted file mode 100644 index e1cb5c433..000000000 --- a/test/integration-tests/ui/ProjectPanelProvider.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 { expect } from "chai"; -import { beforeEach, afterEach } from "mocha"; -import * as vscode from "vscode"; -import * as path from "path"; -import { ProjectPanelProvider, PackageNode, FileNode } from "../../../src/ui/ProjectPanelProvider"; -import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities/tasks"; -import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; -import { testAssetPath } from "../../fixtures"; -import { - activateExtensionForSuite, - folderInRootWorkspace, - updateSettings, -} from "../utilities/testutilities"; -import contextKeys from "../../../src/contextKeys"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Version } from "../../../src/utilities/version"; - -suite("ProjectPanelProvider Test Suite", function () { - let workspaceContext: WorkspaceContext; - let treeProvider: ProjectPanelProvider; - this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build - - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - await waitForNoRunningTasks(); - const folderContext = await folderInRootWorkspace("targets", workspaceContext); - await vscode.workspace.openTextDocument( - path.join(folderContext.folder.fsPath, "Package.swift") - ); - const buildAllTask = await getBuildAllTask(folderContext); - buildAllTask.definition.dontTriggerTestDiscovery = true; - await executeTaskAndWaitForResult(buildAllTask as SwiftTask); - await folderContext.loadSwiftPlugins(); - treeProvider = new ProjectPanelProvider(workspaceContext); - await workspaceContext.focusFolder(folderContext); - }, - async teardown() { - contextKeys.flatDependenciesList = false; - treeProvider.dispose(); - }, - testAssets: ["targets"], - }); - - let resetSettings: (() => Promise) | undefined; - beforeEach(async function () { - resetSettings = await updateSettings({ - "swift.debugger.debugAdapter": "CodeLLDB", - }); - }); - - afterEach(async () => { - if (resetSettings) { - await resetSettings(); - } - }); - - test("Includes top level nodes", async () => { - const commands = await treeProvider.getChildren(); - const commandNames = commands.map(n => n.name); - expect(commandNames).to.deep.equal([ - "Dependencies", - "Targets", - "Tasks", - "Snippets", - "Commands", - ]); - }); - - suite("Targets", () => { - test("Includes targets", async () => { - const targets = await getHeaderChildren("Targets"); - const targetNames = targets.map(target => target.name); - expect( - targetNames, - `Expected to find dependencies target, but instead items were ${targetNames}` - ).to.deep.equal([ - "ExecutableTarget", - "LibraryTarget", - "PluginTarget", - "AnotherTests", - "TargetsTests", - ]); - }); - }); - - suite("Tasks", () => { - beforeEach(async () => { - await waitForNoRunningTasks(); - }); - - async function getBuildAllTask() { - // In Swift 5.10 and below the build tasks are disabled while other tasks that could modify .build are running. - // Typically because the extension has just started up in tests its `swift test list` that runs to gather tests - // for the test explorer. If we're running 5.10 or below, poll for the build all task for up to 60 seconds. - if (workspaceContext.toolchain.swiftVersion.isLessThan(new Version(6, 0, 0))) { - const startTime = Date.now(); - let task: PackageNode | undefined; - while (!task && Date.now() - startTime < 45 * 1000) { - const tasks = await getHeaderChildren("Tasks"); - task = tasks.find(n => n.name === "Build All (targets)") as PackageNode; - await new Promise(resolve => setTimeout(resolve, 1000)); - } - return task; - } else { - const tasks = await getHeaderChildren("Tasks"); - return tasks.find(n => n.name === "Build All (targets)") as PackageNode; - } - } - - test("Includes tasks", async () => { - const dep = await getBuildAllTask(); - expect(dep).to.not.be.undefined; - }); - - test("Executes a task", async () => { - const task = await getBuildAllTask(); - expect(task).to.not.be.undefined; - const treeItem = task?.toTreeItem(); - expect(treeItem?.command).to.not.be.undefined; - expect(treeItem?.command?.arguments).to.not.be.undefined; - if (treeItem && treeItem.command && treeItem.command.arguments) { - const command = treeItem.command.command; - const args = treeItem.command.arguments; - const result = await vscode.commands.executeCommand(command, ...args); - expect(result).to.be.true; - } - }); - }); - - suite("Snippets", () => { - test("Includes snippets", async () => { - const snippets = await getHeaderChildren("Snippets"); - const snippetNames = snippets.map(n => n.name); - expect(snippetNames).to.deep.equal(["AnotherSnippet", "Snippet"]); - }); - - test("Executes a snippet", async () => { - const snippets = await getHeaderChildren("Snippets"); - const snippet = snippets.find(n => n.name === "Snippet"); - expect(snippet).to.not.be.undefined; - - const result = await vscode.commands.executeCommand("swift.runSnippet", snippet?.name); - expect(result).to.be.true; - }); - }); - - suite("Commands", () => { - test("Includes commands", async () => { - const commands = await getHeaderChildren("Commands"); - const commandNames = commands.map(n => n.name); - expect(commandNames).to.deep.equal(["PluginTarget"]); - }); - - test("Executes a command", async () => { - const commands = await getHeaderChildren("Commands"); - const command = commands.find(n => n.name === "PluginTarget"); - expect(command).to.not.be.undefined; - const treeItem = command?.toTreeItem(); - expect(treeItem?.command).to.not.be.undefined; - expect(treeItem?.command?.arguments).to.not.be.undefined; - if (treeItem && treeItem.command && treeItem.command.arguments) { - const command = treeItem.command.command; - const args = treeItem.command.arguments; - const result = await vscode.commands.executeCommand(command, ...args); - expect(result).to.be.true; - } - }); - }); - - suite("Dependencies", () => { - test("Includes remote dependency", async () => { - contextKeys.flatDependenciesList = false; - const items = await getHeaderChildren("Dependencies"); - const dep = items.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; - expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); - assertPathsEqual( - dep?.path, - path.join(testAssetPath("targets"), ".build/checkouts/swift-markdown") - ); - }); - - test("Includes local dependency", async () => { - const items = await getHeaderChildren("Dependencies"); - const dep = items.find(n => n.name === "defaultpackage") as PackageNode; - expect( - dep, - `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` - ).to.not.be.undefined; - assertPathsEqual(dep?.location, testAssetPath("defaultPackage")); - assertPathsEqual(dep?.path, testAssetPath("defaultPackage")); - }); - - test("Lists local dependency file structure", async () => { - contextKeys.flatDependenciesList = false; - const children = await getHeaderChildren("Dependencies"); - const dep = children.find(n => n.name === "defaultpackage") as PackageNode; - expect( - dep, - `Expected to find defaultPackage, but instead items were ${children.map(n => n.name)}` - ).to.not.be.undefined; - - const folders = await treeProvider.getChildren(dep); - const folder = folders.find(n => n.name === "Sources") as FileNode; - expect(folder).to.not.be.undefined; - - assertPathsEqual(folder?.path, path.join(testAssetPath("defaultPackage"), "Sources")); - - const childFolders = await treeProvider.getChildren(folder); - const childFolder = childFolders.find(n => n.name === "PackageExe") as FileNode; - expect(childFolder).to.not.be.undefined; - - assertPathsEqual( - childFolder?.path, - path.join(testAssetPath("defaultPackage"), "Sources/PackageExe") - ); - - const files = await treeProvider.getChildren(childFolder); - const file = files.find(n => n.name === "main.swift") as FileNode; - expect(file).to.not.be.undefined; - - assertPathsEqual( - file?.path, - path.join(testAssetPath("defaultPackage"), "Sources/PackageExe/main.swift") - ); - }); - - test("Lists remote dependency file structure", async () => { - contextKeys.flatDependenciesList = false; - const children = await getHeaderChildren("Dependencies"); - const dep = children.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep, `${JSON.stringify(children, null, 2)}`).to.not.be.undefined; - - const folders = await treeProvider.getChildren(dep); - const folder = folders.find(n => n.name === "Sources") as FileNode; - expect(folder).to.not.be.undefined; - - const depPath = path.join(testAssetPath("targets"), ".build/checkouts/swift-markdown"); - assertPathsEqual(folder?.path, path.join(depPath, "Sources")); - - const childFolders = await treeProvider.getChildren(folder); - const childFolder = childFolders.find(n => n.name === "CAtomic") as FileNode; - expect(childFolder).to.not.be.undefined; - - assertPathsEqual(childFolder?.path, path.join(depPath, "Sources/CAtomic")); - - const files = await treeProvider.getChildren(childFolder); - const file = files.find(n => n.name === "CAtomic.c") as FileNode; - expect(file).to.not.be.undefined; - - assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); - }); - - test("Shows a flat dependency list", async () => { - contextKeys.flatDependenciesList = true; - const items = await getHeaderChildren("Dependencies"); - expect(items.length).to.equal(3); - expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; - expect(items.find(n => n.name === "swift-cmark")).to.not.be.undefined; - expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; - }); - - test("Shows a nested dependency list", async () => { - contextKeys.flatDependenciesList = false; - const items = await getHeaderChildren("Dependencies"); - expect(items.length).to.equal(2); - expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; - expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; - }); - }); - - async function getHeaderChildren(headerName: string) { - const headers = await treeProvider.getChildren(); - const header = headers.find(n => n.name === headerName) as PackageNode; - expect(header).to.not.be.undefined; - return await header.getChildren(); - } - - function assertPathsEqual(path1: string | undefined, path2: string | undefined) { - expect(path1).to.not.be.undefined; - expect(path2).to.not.be.undefined; - // Convert to vscode.Uri to normalize paths, including drive letter capitalization on Windows. - expect(vscode.Uri.file(path1!).fsPath).to.equal(vscode.Uri.file(path2!).fsPath); - } -}); diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 23a7530cc..3414b1954 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -318,7 +318,7 @@ export type SettingsMap = { [key: string]: unknown }; export async function updateSettings(settings: SettingsMap): Promise<() => Promise> { const applySettings = async (settings: SettingsMap) => { const savedOriginalSettings: SettingsMap = {}; - for (const setting of Object.keys(settings)) { + Object.keys(settings).forEach(async setting => { const { section, name } = decomposeSettingName(setting); const config = vscode.workspace.getConfiguration(section, { languageId: "swift" }); savedOriginalSettings[setting] = config.get(name); @@ -327,7 +327,7 @@ export async function updateSettings(settings: SettingsMap): Promise<() => Promi settings[setting] === "" ? undefined : settings[setting], vscode.ConfigurationTarget.Workspace ); - } + }); // There is actually a delay between when the config.update promise resolves and when // the setting is actually written. If we exit this function right away the test might diff --git a/test/unit-tests/ui/PackageDependencyProvider.test.ts b/test/unit-tests/ui/PackageDependencyProvider.test.ts index b7cca7b3a..d7d14b994 100644 --- a/test/unit-tests/ui/PackageDependencyProvider.test.ts +++ b/test/unit-tests/ui/PackageDependencyProvider.test.ts @@ -15,7 +15,7 @@ import { expect } from "chai"; import * as vscode from "vscode"; import * as fs from "fs/promises"; -import { FileNode, PackageNode } from "../../../src/ui/ProjectPanelProvider"; +import { FileNode, PackageNode } from "../../../src/ui/PackageDependencyProvider"; import { mockGlobalModule } from "../../MockUtils"; suite("PackageDependencyProvider Unit Test Suite", function () {