Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions package.json
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for this, I was thinking the first "Install Swift" step could also have an Install Swiftly and Swift Toolchain button instead of being told to manually install from swift.org. Possibly can detect whether the user has an existing toolchain (or Xcode installed) and if they don't they can click a button and have the extension set everything up?

Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,46 @@
"description": "Learn how to use the Swift extension for VS Code.",
"steps": [
{
"id": "installing-mac",
"id": "installing-mac-not-installed",
"title": "Install Swift",
"description": "Swift is cross-platform! If you already have Xcode installed, you're ready to go. Otherwise, see the instructions to [install on macOS](https://www.swift.org/install).\n Swift is [open source](https://github.com/swiftlang) and community driven!\n Questions? Visit the [Swift forums](https://forums.swift.org/) or the [Swift extension GitHub page](https://github.com/swiftlang/vscode-swift) for help.",
"description": "Swift is not currently installed on your system.\n\n[Install Swift](command:swift.installSwiftly)\n\nOr install manually from [swift.org](https://www.swift.org/install).",
"media": {
"markdown": "./assets/walkthrough/welcome.md"
},
"when": "isMac"
"when": "isMac && !swiftInstalled"
},
{
"id": "installing-linux",
"title": "Installing Swift",
"description": "Swift is cross-platform! See the instructions to [install on Linux](https://www.swift.org/install).\n Swift is [open source](https://github.com/swiftlang) and community driven!\n Questions? Visit the [Swift forums](https://forums.swift.org/) or the [Swift extension GitHub page](https://github.com/swiftlang/vscode-swift) for help.",
"id": "installing-mac-installed",
"title": "Swift is Ready",
"description": "Swift is already installed on your system. You're ready to start developing!\n\n[Create New Swift Project](command:swift.createNewProject)",
"media": {
"markdown": "./assets/walkthrough/welcome.md"
},
"when": "isLinux"
"when": "isMac && swiftInstalled",
"completionEvents": [
"onContext:swiftInstalled"
]
},
{
"id": "installing-linux-not-installed",
"title": "Install Swift",
"description": "Swift is not currently installed on your system.\n\n[Install Swift](command:swift.installSwiftly)\n\nOr install manually from [swift.org](https://www.swift.org/install).",
"media": {
"markdown": "./assets/walkthrough/welcome.md"
},
"when": "isLinux && !swiftInstalled"
},
{
"id": "installing-linux-installed",
"title": "Swift is Ready",
"description": "Swift is already installed on your system. You're ready to start developing!\n\n[Create New Swift Project](command:swift.createNewProject)",
"media": {
"markdown": "./assets/walkthrough/welcome.md"
},
"when": "isLinux && swiftInstalled",
"completionEvents": [
"onContext:swiftInstalled"
]
},
{
"id": "installing-windows",
Expand Down Expand Up @@ -218,6 +242,11 @@
}
],
"commands": [
{
"command": "swift.checkInstallation",
"title": "Check Swift Installation",
"category": "Swift"
},
{
"command": "swift.showCommands",
"title": "Show Commands",
Expand Down
18 changes: 17 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useLocalDependency } from "./commands/dependencies/useLocal";
import { generateLaunchConfigurations } from "./commands/generateLaunchConfigurations";
import { generateSourcekitConfiguration } from "./commands/generateSourcekitConfiguration";
import { insertFunctionComment } from "./commands/insertFunctionComment";
import { handleMissingSwiftly } from "./commands/installSwiftly";
import { promptToInstallSwiftlyToolchain } from "./commands/installSwiftlyToolchain";
import { newSwiftFile } from "./commands/newFile";
import { openDocumentation } from "./commands/openDocumentation";
Expand Down Expand Up @@ -67,7 +68,8 @@ export type WorkspaceContextWithToolchain = WorkspaceContext & { toolchain: Swif

export function registerToolchainCommands(
ctx: WorkspaceContext | undefined,
logger: SwiftLogger
logger: SwiftLogger,
extensionPath: string
): vscode.Disposable[] {
return [
vscode.commands.registerCommand("swift.createNewProject", () =>
Expand All @@ -83,6 +85,9 @@ export function registerToolchainCommands(
vscode.commands.registerCommand("swift.pickProcess", configuration =>
pickProcess(configuration)
),
vscode.commands.registerCommand("swift.installSwiftly", () =>
handleMissingSwiftly(["latest"], extensionPath, logger, true)
),
];
}

Expand Down Expand Up @@ -117,6 +122,7 @@ export enum Commands {
OPEN_MANIFEST = "swift.openManifest",
RESTART_LSP = "swift.restartLSPServer",
SELECT_TOOLCHAIN = "swift.selectToolchain",
INSTALL_SWIFTLY = "swift.installSwiftly",
INSTALL_SWIFTLY_TOOLCHAIN = "swift.installSwiftlyToolchain",
INSTALL_SWIFTLY_SNAPSHOT_TOOLCHAIN = "swift.installSwiftlySnapshotToolchain",
GENERATE_SOURCEKIT_CONFIG = "swift.generateSourcekitConfiguration",
Expand Down Expand Up @@ -375,6 +381,16 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
Commands.INSTALL_SWIFTLY_SNAPSHOT_TOOLCHAIN,
async () => await promptToInstallSwiftlyToolchain(ctx, "snapshot")
),
vscode.commands.registerCommand(
Commands.INSTALL_SWIFTLY,
async () =>
await handleMissingSwiftly(
["latest"],
ctx.extensionContext.extensionPath,
ctx.logger,
true
)
),
];
}

Expand Down
11 changes: 7 additions & 4 deletions src/commands/installSwiftly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,20 @@ async function promptToRestartVSCode(): Promise<void> {
export async function handleMissingSwiftly(
swiftVersions: string[],
extensionRoot: string,
logger?: SwiftLogger
logger?: SwiftLogger,
skipPrompt: boolean = false
): Promise<boolean> {
// Check if the user wants to disable the prompt
if (vscode.workspace.getConfiguration("swift").get("disableSwiftlyInstallPrompt", false)) {
logger?.debug("Swiftly installation prompt is suppressed");
return false;
}

// Prompt user for installation
if (!(await promptForSwiftlyInstallation(logger))) {
return false;
if (!skipPrompt) {
// Prompt user for installation
if (!(await promptForSwiftlyInstallation(logger))) {
return false;
}
}

// Install Swiftly
Expand Down
13 changes: 13 additions & 0 deletions src/commands/swiftInstalled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as child_process from "child_process";
import { promisify } from "util";

const exec = promisify(child_process.exec);

export async function swiftInstalled(): Promise<boolean> {
try {
await exec("swift --version");
return true;
} catch (error) {
return false;
}
}
59 changes: 41 additions & 18 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as commands from "./commands";
import { resolveFolderDependencies } from "./commands/dependencies/resolve";
import { registerSourceKitSchemaWatcher } from "./commands/generateSourcekitConfiguration";
import { handleMissingSwiftly } from "./commands/installSwiftly";
import { swiftInstalled } from "./commands/swiftInstalled";
import configuration, {
ConfigurationValidationError,
handleConfigurationChangeEvent,
Expand Down Expand Up @@ -80,6 +81,10 @@ export async function activate(
checkForSwiftlyInstallation(context, contextKeys, logger);
const swiftlyCheckElapsed = Date.now() - swiftlyCheckStartTime;

const swiftLangCheckStartTime = Date.now();
await checkForSwiftLangInstallation();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that we can render a checkmark when swift --version returns something.

const swiftLangCheckElapsed = Date.now() - swiftLangCheckStartTime;

const toolchainStartTime = Date.now();
const toolchain = await createActiveToolchain(context, contextKeys, logger);
const toolchainElapsed = Date.now() - toolchainStartTime;
Expand All @@ -89,23 +94,28 @@ export async function activate(
// properly configured.
if (!toolchain) {
// In order to select a toolchain we need to register the command first.
const subscriptions = commands.registerToolchainCommands(undefined, logger);
const chosenRemediation = await showToolchainError();
subscriptions.forEach(sub => sub.dispose());
context.subscriptions.push(
...commands.registerToolchainCommands(undefined, logger, context.extensionPath)
);

// If they tried to fix the improperly configured toolchain, re-initialize the extension.
if (chosenRemediation) {
return activate(context);
} else {
return {
workspaceContext: undefined,
logger,
activate: () => activate(context),
deactivate: async () => {
await deactivate(context);
},
};
}
// Show the error dialog asynchronously without blocking activation.
// This prevents a UI deadlock where the walkthrough buttons can't work
// because activation is blocked waiting for the user to dismiss the dialog.
void showToolchainError().then(chosenRemediation => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found this was the only way to get the "Install Swift" button to work while there was no Swift toolchain installed (which is the most important time for that button to work!)

// If they tried to fix the improperly configured toolchain, re-initialize the extension.
if (chosenRemediation) {
void activate(context);
}
});

return {
workspaceContext: undefined,
logger,
activate: () => activate(context),
deactivate: async () => {
await deactivate(context);
},
};
}

const workspaceContextStartTime = Date.now();
Expand All @@ -117,7 +127,11 @@ export async function activate(
context.subscriptions.push(new SwiftEnvironmentVariablesManager(context));
context.subscriptions.push(SwiftTerminalProfileProvider.register());
context.subscriptions.push(
...commands.registerToolchainCommands(workspaceContext, workspaceContext.logger)
...commands.registerToolchainCommands(
workspaceContext,
workspaceContext.logger,
context.extensionPath
)
);

// Watch for configuration changes the trigger a reload of the extension if necessary.
Expand Down Expand Up @@ -184,7 +198,7 @@ export async function activate(

const totalActivationTime = Date.now() - activationStartTime;
logger.info(
`Extension activation completed in ${totalActivationTime}ms (log-setup: ${logSetupElapsed}ms, pre-toolchain: ${preToolchainElapsed}ms, toolchain: ${toolchainElapsed}ms, swiftly-check: ${swiftlyCheckElapsed}ms, workspace-context: ${workspaceContextElapsed}ms, subscriptions: ${subscriptionsElapsed}ms, workspace-folders: ${workspaceFoldersElapsed}ms, final-steps: ${finalStepsElapsed}ms)`
`Extension activation completed in ${totalActivationTime}ms (log-setup: ${logSetupElapsed}ms, pre-toolchain: ${preToolchainElapsed}ms, toolchain: ${toolchainElapsed}ms, swiftly-check: ${swiftlyCheckElapsed}ms, swift-lang-check: ${swiftLangCheckElapsed}, workspace-context: ${workspaceContextElapsed}ms, subscriptions: ${subscriptionsElapsed}ms, workspace-folders: ${workspaceFoldersElapsed}ms, final-steps: ${finalStepsElapsed}ms)`
);

return {
Expand Down Expand Up @@ -406,6 +420,15 @@ function findSwiftVersionFilesInWorkspace(): Promise<string[]> {
).then(results => results.reduceRight((prev, curr) => prev.concat(curr), []));
}

/**
* Checks if swift --version works, and updates the "swiftInstalled" context variable
*/
async function checkForSwiftLangInstallation() {
const isInstalled = await swiftInstalled();
// The welcome page checks the swiftInstalled context variable and renders a check mark if true
await vscode.commands.executeCommand("setContext", "swiftInstalled", isInstalled);
}

async function createActiveToolchain(
extension: vscode.ExtensionContext,
contextKeys: ContextKeys,
Expand Down
Loading