Skip to content

Commit 523bcd1

Browse files
Toolchain selection (#817)
Add toolchain selection command and associated UI
1 parent 90f7641 commit 523bcd1

File tree

12 files changed

+552
-133
lines changed

12 files changed

+552
-133
lines changed

package.json

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@
136136
"category": "Swift"
137137
},
138138
{
139-
"command": "swift.selectXcodeDeveloperDir",
140-
"title": "Select Xcode Developer Dir...",
139+
"command": "swift.selectToolchain",
140+
"title": "Select Toolchain...",
141141
"category": "Swift"
142142
},
143143
{
@@ -523,12 +523,28 @@
523523
},
524524
{
525525
"command": "swift.switchPlatform",
526-
"when": "isMac"
526+
"when": "swift.isActivated && isMac"
527+
},
528+
{
529+
"command": "swift.insertFunctionComment",
530+
"when": "swift.isActivated"
527531
},
528532
{
529533
"command": "swift.resetPackage",
530534
"when": "swift.hasPackage"
531535
},
536+
{
537+
"command": "swift.restartLSPServer",
538+
"when": "swift.isActivated"
539+
},
540+
{
541+
"command": "swift.showTestCoverageReport",
542+
"when": "swift.isActivated"
543+
},
544+
{
545+
"command": "swift.toggleTestCoverage",
546+
"when": "swift.isActivated"
547+
},
532548
{
533549
"command": "swift.openPackage",
534550
"when": "swift.hasPackage"
@@ -553,10 +569,6 @@
553569
"command": "swift.openExternal",
554570
"when": "false"
555571
},
556-
{
557-
"command": "swift.selectXcodeDeveloperDir",
558-
"when": "isMac"
559-
},
560572
{
561573
"command": "swift.run",
562574
"when": "editorLangId == swift && swift.currentTargetType == 'executable'"
@@ -1148,4 +1160,4 @@
11481160
"vscode-languageclient": "^9.0.1",
11491161
"xml2js": "^0.6.2"
11501162
}
1151-
}
1163+
}

src/WorkspaceContext.ts

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
import { getLLDBLibPath } from "./debugger/lldb";
2727
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
2828
import { TemporaryFolder } from "./utilities/tempFolder";
29-
import { SwiftToolchain } from "./toolchain/toolchain";
3029
import { TaskManager } from "./tasks/TaskManager";
3130
import { BackgroundCompilation } from "./BackgroundCompilation";
3231
import { makeDebugConfigurations } from "./debugger/launch";
@@ -35,8 +34,8 @@ import contextKeys from "./contextKeys";
3534
import { setSnippetContextKey } from "./SwiftSnippets";
3635
import { CommentCompletionProviders } from "./editor/CommentCompletion";
3736
import { DebugAdapter } from "./debugger/debugAdapter";
38-
import { Version } from "./utilities/version";
3937
import { SwiftBuildStatus } from "./ui/SwiftBuildStatus";
38+
import { SwiftToolchain } from "./toolchain/toolchain";
4039

4140
/**
4241
* Context for whole workspace. Holds array of contexts for each workspace folder
@@ -46,62 +45,28 @@ export class WorkspaceContext implements vscode.Disposable {
4645
public folders: FolderContext[] = [];
4746
public currentFolder: FolderContext | null | undefined;
4847
public currentDocument: vscode.Uri | null;
49-
public outputChannel: SwiftOutputChannel;
5048
public statusItem: StatusItem;
5149
public buildStatus: SwiftBuildStatus;
5250
public languageClientManager: LanguageClientManager;
5351
public tasks: TaskManager;
54-
public subscriptions: { dispose(): unknown }[];
52+
public subscriptions: vscode.Disposable[];
5553
public commentCompletionProvider: CommentCompletionProviders;
5654
private lastFocusUri: vscode.Uri | undefined;
5755
private initialisationFinished = false;
5856

5957
private constructor(
6058
public tempFolder: TemporaryFolder,
59+
public outputChannel: SwiftOutputChannel,
6160
public toolchain: SwiftToolchain
6261
) {
63-
this.outputChannel = new SwiftOutputChannel();
6462
this.statusItem = new StatusItem();
6563
this.buildStatus = new SwiftBuildStatus(this.statusItem);
6664
this.languageClientManager = new LanguageClientManager(this);
67-
this.outputChannel.log(this.toolchain.swiftVersionString);
68-
this.toolchain.logDiagnostics(this.outputChannel);
6965
this.tasks = new TaskManager(this);
7066
this.currentDocument = null;
7167
this.commentCompletionProvider = new CommentCompletionProviders();
72-
contextKeys.createNewProjectAvailable = toolchain.swiftVersion.isGreaterThanOrEqual(
73-
new Version(5, 8, 0)
74-
);
7568

7669
const onChangeConfig = vscode.workspace.onDidChangeConfiguration(async event => {
77-
// on toolchain config change, reload window
78-
if (event.affectsConfiguration("swift.path")) {
79-
vscode.window
80-
.showInformationMessage(
81-
"Changing the Swift path requires the project be reloaded.",
82-
"Ok"
83-
)
84-
.then(selected => {
85-
if (selected === "Ok") {
86-
vscode.commands.executeCommand("workbench.action.reloadWindow");
87-
}
88-
});
89-
}
90-
// on sdk config change, restart sourcekit-lsp
91-
if (event.affectsConfiguration("swift.SDK")) {
92-
// FIXME: There is a bug stopping us from restarting SourceKit-LSP directly.
93-
// As long as it's fixed we won't need to reload on newer versions.
94-
vscode.window
95-
.showInformationMessage(
96-
"Changing the Swift SDK path requires the project be reloaded.",
97-
"Ok"
98-
)
99-
.then(selected => {
100-
if (selected === "Ok") {
101-
vscode.commands.executeCommand("workbench.action.reloadWindow");
102-
}
103-
});
104-
}
10570
// on runtime path config change, regenerate launch.json
10671
if (event.affectsConfiguration("swift.runtimePath")) {
10772
if (!this.needToAutoGenerateLaunchConfig()) {
@@ -228,10 +193,12 @@ export class WorkspaceContext implements vscode.Disposable {
228193
}
229194

230195
/** Get swift version and create WorkspaceContext */
231-
static async create(): Promise<WorkspaceContext> {
196+
static async create(
197+
outputChannel: SwiftOutputChannel,
198+
toolchain: SwiftToolchain
199+
): Promise<WorkspaceContext> {
232200
const tempFolder = await TemporaryFolder.create();
233-
const toolchain = await SwiftToolchain.create();
234-
return new WorkspaceContext(tempFolder, toolchain);
201+
return new WorkspaceContext(tempFolder, outputChannel, toolchain);
235202
}
236203

237204
/**

src/commands.ts

Lines changed: 37 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { debugLaunchConfig, getLaunchConfiguration } from "./debugger/launch";
3030
import { execFile } from "./utilities/utilities";
3131
import { SwiftExecOperation, TaskOperation } from "./tasks/TaskQueue";
3232
import { SwiftProjectTemplate } from "./toolchain/toolchain";
33+
import { showToolchainSelectionQuickPick, showToolchainError } from "./ui/ToolchainSelection";
3334

3435
/**
3536
* References:
@@ -40,6 +41,8 @@ import { SwiftProjectTemplate } from "./toolchain/toolchain";
4041
* https://code.visualstudio.com/api/extension-guides/command
4142
*/
4243

44+
export type WorkspaceContextWithToolchain = WorkspaceContext & { toolchain: SwiftToolchain };
45+
4346
/**
4447
* Executes a {@link vscode.Task task} to resolve this package's dependencies.
4548
*/
@@ -97,19 +100,27 @@ export async function updateDependencies(ctx: WorkspaceContext) {
97100
* Prompts the user to input project details and then executes `swift package init`
98101
* to create the project.
99102
*/
100-
export async function createNewProject(ctx: WorkspaceContext): Promise<void> {
103+
export async function createNewProject(toolchain: SwiftToolchain | undefined): Promise<void> {
104+
// It is possible for this command to be run without a valid toolchain because it can be
105+
// run before the Swift extension is activated. Show the toolchain error notification in
106+
// this case.
107+
if (!toolchain) {
108+
showToolchainError();
109+
return;
110+
}
111+
101112
// The context key `swift.createNewProjectAvailable` only works if the extension has been
102113
// activated. As such, we also have to allow this command to run when no workspace is
103114
// active. Show an error to the user if the command is unavailable.
104-
if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(5, 8, 0))) {
115+
if (!toolchain.swiftVersion.isGreaterThanOrEqual(new Version(5, 8, 0))) {
105116
vscode.window.showErrorMessage(
106117
"Creating a new swift project is only available starting in swift version 5.8.0."
107118
);
108119
return;
109120
}
110121

111122
// Prompt the user for the type of project they would like to create
112-
const availableProjectTemplates = await ctx.toolchain.getProjectTemplates();
123+
const availableProjectTemplates = await toolchain.getProjectTemplates();
113124
const selectedProjectTemplate = await vscode.window.showQuickPick<
114125
vscode.QuickPickItem & { type: SwiftProjectTemplate }
115126
>(
@@ -186,7 +197,7 @@ export async function createNewProject(ctx: WorkspaceContext): Promise<void> {
186197
async () => {
187198
await execSwift(
188199
["package", "init", "--type", projectType, "--name", projectName],
189-
ctx.toolchain,
200+
toolchain,
190201
{
191202
cwd: projectUri.fsPath,
192203
}
@@ -630,7 +641,7 @@ async function openPackage(workspaceContext: WorkspaceContext) {
630641
}
631642
}
632643

633-
function insertFunctionComment(workspaceContext: WorkspaceContext) {
644+
async function insertFunctionComment(workspaceContext: WorkspaceContext): Promise<void> {
634645
const activeEditor = vscode.window.activeTextEditor;
635646
if (!activeEditor) {
636647
return;
@@ -640,8 +651,8 @@ function insertFunctionComment(workspaceContext: WorkspaceContext) {
640651
}
641652

642653
/** Restart the SourceKit-LSP server */
643-
function restartLSPServer(workspaceContext: WorkspaceContext) {
644-
workspaceContext.languageClientManager.restart();
654+
function restartLSPServer(workspaceContext: WorkspaceContext): Promise<void> {
655+
return workspaceContext.languageClientManager.restart();
645656
}
646657

647658
/** Execute task and show UI while running */
@@ -726,61 +737,9 @@ async function switchPlatform() {
726737
);
727738
}
728739

729-
/**
730-
* Choose DEVELOPER_DIR
731-
* @param workspaceContext
732-
*/
733-
async function selectXcodeDeveloperDir() {
734-
const defaultXcode = await SwiftToolchain.getXcodeDeveloperDir();
735-
const selectedXcode = configuration.swiftEnvironmentVariables.DEVELOPER_DIR;
736-
const xcodes = await SwiftToolchain.getXcodeInstalls();
737-
await withQuickPick(
738-
selectedXcode ?? defaultXcode,
739-
xcodes.map(xcode => {
740-
const developerDir = `${xcode}/Contents/Developer`;
741-
return {
742-
label: developerDir === defaultXcode ? `${xcode} (default)` : xcode,
743-
folder: developerDir === defaultXcode ? undefined : developerDir,
744-
};
745-
}),
746-
async selected => {
747-
let swiftEnv = configuration.swiftEnvironmentVariables;
748-
const previousDeveloperDir = swiftEnv.DEVELOPER_DIR ?? defaultXcode;
749-
if (selected.folder) {
750-
swiftEnv.DEVELOPER_DIR = selected.folder;
751-
} else if (swiftEnv.DEVELOPER_DIR) {
752-
// if DEVELOPER_DIR was set and the new folder is the default then
753-
// delete variable
754-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
755-
const { DEVELOPER_DIR, ...rest } = swiftEnv;
756-
swiftEnv = rest;
757-
}
758-
configuration.swiftEnvironmentVariables = swiftEnv;
759-
// if SDK is inside previous DEVELOPER_DIR then move to new DEVELOPER_DIR
760-
if (
761-
configuration.sdk.length > 0 &&
762-
configuration.sdk.startsWith(previousDeveloperDir)
763-
) {
764-
configuration.sdk = configuration.sdk.replace(
765-
previousDeveloperDir,
766-
selected.folder ?? defaultXcode
767-
);
768-
}
769-
vscode.window
770-
.showInformationMessage(
771-
"Changing the Xcode Developer Directory requires the project be reloaded.",
772-
"Ok"
773-
)
774-
.then(() => {
775-
vscode.commands.executeCommand("workbench.action.reloadWindow");
776-
});
777-
}
778-
);
779-
}
780-
781-
async function attachDebugger(workspaceContext: WorkspaceContext) {
740+
async function attachDebugger(ctx: WorkspaceContext) {
782741
// use LLDB to get list of processes
783-
const lldb = workspaceContext.toolchain.getLLDB();
742+
const lldb = await ctx.toolchain.getLLDB();
784743
try {
785744
const { stdout } = await execFile(lldb, [
786745
"--batch",
@@ -823,12 +782,24 @@ function updateAfterError(result: boolean, folderContext: FolderContext) {
823782
}
824783
}
825784

785+
export function registerToolchainCommands(
786+
toolchain: SwiftToolchain | undefined
787+
): vscode.Disposable[] {
788+
return [
789+
vscode.commands.registerCommand("swift.createNewProject", () =>
790+
createNewProject(toolchain)
791+
),
792+
vscode.commands.registerCommand("swift.selectToolchain", () =>
793+
showToolchainSelectionQuickPick(toolchain)
794+
),
795+
];
796+
}
797+
826798
/**
827799
* Registers this extension's commands in the given {@link vscode.ExtensionContext context}.
828800
*/
829-
export function register(ctx: WorkspaceContext) {
830-
ctx.subscriptions.push(
831-
vscode.commands.registerCommand("swift.createNewProject", () => createNewProject(ctx)),
801+
export function register(ctx: WorkspaceContext): vscode.Disposable[] {
802+
return [
832803
vscode.commands.registerCommand("swift.resolveDependencies", () =>
833804
resolveDependencies(ctx)
834805
),
@@ -873,9 +844,6 @@ export function register(ctx: WorkspaceContext) {
873844
openInExternalEditor(item);
874845
}
875846
}),
876-
vscode.commands.registerCommand("swift.selectXcodeDeveloperDir", () =>
877-
selectXcodeDeveloperDir()
878-
),
879-
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx))
880-
);
847+
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)),
848+
];
881849
}

src/debugger/lldb.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import { SwiftToolchain } from "../toolchain/toolchain";
2727
* @returns Library path for LLDB
2828
*/
2929
export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise<Result<string>> {
30-
const executable = toolchain.getLLDB();
30+
let executable: string;
31+
try {
32+
executable = await toolchain.getLLDB();
33+
} catch (error) {
34+
return Result.makeFailure(error);
35+
}
3136
let pathHint = path.dirname(toolchain.swiftFolderPath);
3237
try {
3338
const statement = `print('<!' + lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeLLDBShlibDir).fullpath + '!>')`;

0 commit comments

Comments
 (0)