From 931ae1aaf4401617938e06686f3357c276a660e1 Mon Sep 17 00:00:00 2001 From: David Cummings Date: Fri, 17 May 2024 13:39:49 -0400 Subject: [PATCH 1/2] Prototype for SPI integration This change is a prototype for SPI integration. It provides two new commands that leverage the new add package workflows within swiftpm * Add package * Browse Package Index... Add package is fairly straight forward. It prompts the user for both the package URL and the version, and adds the package to their Package.swift (by calling swift package add...). An alternative here would be to have the LSP do the equivalent refactoring operation. Browse Package Index opens a webview to the SPI (note in this patch it's using localhost) in an iframe. The intent is that the SPI can detect that it is running through the vscode webview and provide alternative UI for adding a package. This is accomplished through: * A window event listener between the frame and the webview in which messages that can be sent [1] * Access to the vscode api from the webview that post a message to run the command. This invokes the add package command detailed above [1] - SPI usage - SPI would need to detect that it is running in the vscode context and present alternative (or augmented UI). In the example given it could use window.name to check and see if it is equal to "vscode_iframe" - When the user takes the action to add their package using this new UI, SPI needs to send a message to its parent. window.parent.postMessage( { action: 'addPackage', url: url, version: version }, '*') --- package.json | 18 ++++++ src/commands.ts | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/package.json b/package.json index 3ea35b3d3..9c93db423 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,16 @@ } ], "commands": [ + { + "command": "swift.browsePackageIndex", + "title": "Browse Package Index...", + "category": "Swift" + }, + { + "command": "swift.addPackageDependency", + "title": "Add Package Dependency...", + "category": "Swift" + }, { "command": "swift.createNewProject", "title": "Create New Project...", @@ -536,6 +546,14 @@ "command": "swift.createNewProject", "when": "!swift.isActivated || swift.createNewProjectAvailable" }, + { + "command": "swift.browsePackageIndex", + "when": "swift.hasPackage" + }, + { + "command": "swift.addPackageDependency", + "when": "swift.hasPackage" + }, { "command": "swift.updateDependencies", "when": "swift.hasPackage" diff --git a/src/commands.ts b/src/commands.ts index a32424282..ccdee4e09 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -248,6 +248,147 @@ export async function createNewProject(ctx: WorkspaceContext): Promise { } } +/** + * Prompts the user to input project details and then executes `swift package init` + * to create the project. + */ +export async function browsePackageIndex(ctx: WorkspaceContext): Promise { + // The context key `swift.browsePackageIndex` only works if the extension has been + // activated. As such, we also have to allow this command to run when no workspace is + // active. Show an error to the user if the command is unavailable. + if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { + vscode.window.showErrorMessage( + "Creating a new swift project is only available starting in swift version 6.0.0." + ); + return; + } + + const panel = vscode.window.createWebviewPanel( + "swiftPackageIndex", + "Swift Package Index", + vscode.ViewColumn.One, + { + retainContextWhenHidden: true, + enableScripts: true, + } + ); + + panel.webview.onDidReceiveMessage( + message => { + switch (message.command) { + case "addSwiftPackage": + addPackageDependency(ctx, message.url, message.version); + return; + default: + // Log message + vscode.window.showErrorMessage(`An error occurred when adding packages`); + return; + } + }, + undefined, + ctx.subscriptions + ); + + panel.webview.html = ` + + + + `; +} + +export async function addPackageDependency( + ctx: WorkspaceContext, + url?: string, + version?: string +): Promise { + // The context key `swift.addPackageDependency` only works if the extension has been + // activated. As such, we also have to allow this command to run when no workspace is + // active. Show an error to the user if the command is unavailable. + if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { + vscode.window.showErrorMessage( + "Adding a new swift package dependency is only available starting in swift version 6.0.0." + ); + return; + } + + if (!ctx.currentFolder) { + vscode.window.showErrorMessage("An error occurred when adding packages"); + return; + } + const folderContext = ctx.currentFolder; + + if (!url) { + url = await vscode.window.showInputBox({ + prompt: "Enter the URL for the package", + validateInput(value) { + if (value.trim() === "") { + return "URL cannot be empty."; + } + return undefined; + }, + }); + + if (!url) { + return; + } + } + + if (!version) { + version = await vscode.window.showInputBox({ + prompt: "Enter the version for the package", + validateInput(value) { + if (value.trim() === "") { + return "Version cannot be empty."; + } + return undefined; + }, + }); + + if (!version) { + return; + } + } + + const resolveTask = createSwiftTask( + ["package", "add-dependency", url, "--branch", version], + "Adding Package Dependency", + { + cwd: folderContext.folder, + scope: folderContext.workspaceFolder, + prefix: folderContext.name, + presentationOptions: { reveal: vscode.TaskRevealKind.Silent }, + }, + folderContext.workspaceContext.toolchain + ); + + await executeTaskWithUI(resolveTask, "Adding Package Dependency", folderContext); + + await openPackage(ctx); +} + /** * Run `swift package update` inside a folder * @param folderContext folder to run update inside @@ -840,6 +981,10 @@ function updateAfterError(result: boolean, folderContext: FolderContext) { export function register(ctx: WorkspaceContext) { ctx.subscriptions.push( vscode.commands.registerCommand("swift.createNewProject", () => createNewProject(ctx)), + vscode.commands.registerCommand("swift.browsePackageIndex", () => browsePackageIndex(ctx)), + vscode.commands.registerCommand("swift.addPackageDependency", (url, version) => + addPackageDependency(ctx, url, version) + ), vscode.commands.registerCommand("swift.resolveDependencies", () => resolveDependencies(ctx) ), From 5343896941a98ab699574df3be4b69741ac573ae Mon Sep 17 00:00:00 2001 From: David Cummings Date: Wed, 22 May 2024 16:45:38 -0400 Subject: [PATCH 2/2] Hide add package related commands when appropriate The add package related commands (add package, browse swift package index) would previously show an error if the user ran the command but had a swift toolchain that was older than 6.0. This adds a context key so that the command doesn't appear in this case. --- package.json | 6 +++--- src/WorkspaceContext.ts | 3 +++ src/commands.ts | 6 +++--- src/contextKeys.ts | 7 +++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9c93db423..8f37b48fb 100644 --- a/package.json +++ b/package.json @@ -548,11 +548,11 @@ }, { "command": "swift.browsePackageIndex", - "when": "swift.hasPackage" + "when": "swift.hasPackage && swift.addPackageRefactoringAvailable" }, { "command": "swift.addPackageDependency", - "when": "swift.hasPackage" + "when": "swift.hasPackage && swift.addPackageRefactoringAvailable" }, { "command": "swift.updateDependencies", @@ -1181,4 +1181,4 @@ "vscode-languageclient": "^9.0.1", "xml2js": "^0.6.2" } -} \ No newline at end of file +} diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index 5b7da0946..a3053caf6 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -76,6 +76,9 @@ export class WorkspaceContext implements vscode.Disposable { contextKeys.createNewProjectAvailable = toolchain.swiftVersion.isGreaterThanOrEqual( new Version(5, 8, 0) ); + contextKeys.addPackageRefactoringAvailable = toolchain.swiftVersion.isGreaterThanOrEqual( + new Version(6, 0, 0) + ); const onChangeConfig = vscode.workspace.onDidChangeConfiguration(async event => { // on toolchain config change, reload window diff --git a/src/commands.ts b/src/commands.ts index ccdee4e09..8bb82dd01 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -253,12 +253,12 @@ export async function createNewProject(ctx: WorkspaceContext): Promise { * to create the project. */ export async function browsePackageIndex(ctx: WorkspaceContext): Promise { - // The context key `swift.browsePackageIndex` only works if the extension has been + // The context key `swift.addPackageRefactoringAvailable` only works if the extension has been // activated. As such, we also have to allow this command to run when no workspace is // active. Show an error to the user if the command is unavailable. if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { vscode.window.showErrorMessage( - "Creating a new swift project is only available starting in swift version 6.0.0." + "Browsing the package index is only available starting in swift version 6.0.0." ); return; } @@ -324,7 +324,7 @@ export async function addPackageDependency( url?: string, version?: string ): Promise { - // The context key `swift.addPackageDependency` only works if the extension has been + // The context key `swift.addPackageRefactoringAvailable` only works if the extension has been // activated. As such, we also have to allow this command to run when no workspace is // active. Show an error to the user if the command is unavailable. if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { diff --git a/src/contextKeys.ts b/src/contextKeys.ts index 6c221ba2c..736269cd8 100644 --- a/src/contextKeys.ts +++ b/src/contextKeys.ts @@ -80,6 +80,13 @@ const contextKeys = { set createNewProjectAvailable(value: boolean) { vscode.commands.executeCommand("setContext", "swift.createNewProjectAvailable", value); }, + + /** + * Whether the swift.addPackageRefactoringAvailable command is available. + */ + set addPackageRefactoringAvailable(value: boolean) { + vscode.commands.executeCommand("setContext", "swift.addPackageRefactoringAvailable", value); + }, }; export default contextKeys;