diff --git a/src/commands.ts b/src/commands.ts index 7b9c7bb83..7a2577399 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import * as path from "path"; import * as vscode from "vscode"; import { WorkspaceContext } from "./WorkspaceContext"; import { PackageNode } from "./ui/ProjectPanelProvider"; @@ -93,6 +94,7 @@ export enum Commands { RUN_ALL_TESTS_PARALLEL = "swift.runAllTestsParallel", DEBUG_ALL_TESTS = "swift.debugAllTests", COVER_ALL_TESTS = "swift.coverAllTests", + OPEN_MANIFEST = "swift.openManifest", } /** @@ -209,6 +211,10 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] { vscode.commands.registerCommand("swift.openEducationalNote", uri => openEducationalNote(uri) ), + vscode.commands.registerCommand(Commands.OPEN_MANIFEST, (uri: vscode.Uri) => { + const packagePath = path.join(uri.fsPath, "Package.swift"); + vscode.commands.executeCommand("vscode.open", vscode.Uri.file(packagePath)); + }), ]; } diff --git a/src/ui/ProjectPanelProvider.ts b/src/ui/ProjectPanelProvider.ts index 720077504..659a36c47 100644 --- a/src/ui/ProjectPanelProvider.ts +++ b/src/ui/ProjectPanelProvider.ts @@ -311,12 +311,44 @@ class HeaderNode { } } +class ErrorNode { + constructor( + public name: string, + private folder: vscode.Uri + ) {} + + get path(): string { + return ""; + } + + toTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.None); + item.id = `error-${this.folder.fsPath}`; + item.iconPath = new vscode.ThemeIcon("error", new vscode.ThemeColor("errorForeground")); + item.contextValue = "error"; + item.accessibilityInformation = { label: this.name }; + item.tooltip = + "Could not build the Package.swift, fix the error to refresh the project panel"; + + item.command = { + command: "swift.openManifest", + arguments: [this.folder], + title: "Open Manifest", + }; + return item; + } + + getChildren(): Promise { + return Promise.resolve([]); + } +} + /** * 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}. + * Can be either a {@link PackageNode}, {@link FileNode}, {@link TargetNode}, {@link TaskNode}, {@link ErrorNode} or {@link HeaderNode}. */ -type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode; +type TreeNode = PackageNode | FileNode | HeaderNode | TaskNode | TargetNode | ErrorNode; /** * A {@link vscode.TreeDataProvider TreeDataProvider} for project dependencies, tasks and commands {@link vscode.TreeView TreeView}. @@ -328,6 +360,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { private workspaceObserver?: vscode.Disposable; private disposables: vscode.Disposable[] = []; private activeTasks: Set = new Set(); + private lastComputedNodes: TreeNode[] = []; onDidChangeTreeData = this.didChangeTreeDataEmitter.event; @@ -426,6 +459,24 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { return []; } + if (!element && folderContext.hasResolveErrors) { + return [ + new ErrorNode("Error Parsing Package.swift", folderContext.folder), + ...this.lastComputedNodes, + ]; + } + + const nodes = await this.computeChildren(folderContext, element); + + // If we're fetching the root nodes then save them in case we have an error later, + // in which case we show the ErrorNode along with the last known good nodes. + if (!element) { + this.lastComputedNodes = nodes; + } + return nodes; + } + + async computeChildren(folderContext: FolderContext, element?: TreeNode): Promise { if (element) { return element.getChildren(); } @@ -515,7 +566,7 @@ export class ProjectPanelProvider implements vscode.TreeDataProvider { ); } - private async tasks(folderContext: FolderContext): Promise { + private async tasks(folderContext: FolderContext): Promise { const tasks = await vscode.tasks.fetchTasks(); return ( diff --git a/test/integration-tests/ui/ProjectPanelProvider.test.ts b/test/integration-tests/ui/ProjectPanelProvider.test.ts index e1cb5c433..d14a34259 100644 --- a/test/integration-tests/ui/ProjectPanelProvider.test.ts +++ b/test/integration-tests/ui/ProjectPanelProvider.test.ts @@ -282,6 +282,15 @@ suite("ProjectPanelProvider Test Suite", function () { expect(items.find(n => n.name === "swift-markdown")).to.not.be.undefined; expect(items.find(n => n.name === "defaultpackage")).to.not.be.undefined; }); + + test("Shows an error node when there is a problem compiling Package.swift", async () => { + workspaceContext.folders[0].hasResolveErrors = true; + workspaceContext.currentFolder = workspaceContext.folders[0]; + const treeProvider = new ProjectPanelProvider(workspaceContext); + const children = await treeProvider.getChildren(); + const errorNode = children.find(n => n.name === "Error Parsing Package.swift"); + expect(errorNode).to.not.be.undefined; + }); }); async function getHeaderChildren(headerName: string) {