@@ -63,7 +63,7 @@ export async function selectToolchainFolder() {
63
63
if ( ! selected || selected . length !== 1 ) {
64
64
return ;
65
65
}
66
- await setToolchainPath ( selected [ 0 ] . fsPath , "prompt " ) ;
66
+ await setToolchainPath ( selected [ 0 ] . fsPath , "public " ) ;
67
67
}
68
68
69
69
/**
@@ -91,14 +91,28 @@ export async function showToolchainError(): Promise<void> {
91
91
}
92
92
}
93
93
94
- /** A {@link vscode.QuickPickItem} that contains the path to an installed swift toolchain */
95
- interface SwiftToolchainItem extends vscode . QuickPickItem {
94
+ /** A {@link vscode.QuickPickItem} that contains the path to an installed Swift toolchain */
95
+ type SwiftToolchainItem = PublicSwiftToolchainItem | XcodeToolchainItem ;
96
+
97
+ /** Common properties for a {@link vscode.QuickPickItem} that represents a Swift toolchain */
98
+ interface BaseSwiftToolchainItem extends vscode . QuickPickItem {
96
99
type : "toolchain" ;
97
100
toolchainPath : string ;
98
101
swiftFolderPath : string ;
99
102
onDidSelect ?( ) : Promise < void > ;
100
103
}
101
104
105
+ /** A {@link vscode.QuickPickItem} for a Swift toolchain that has been installed manually */
106
+ interface PublicSwiftToolchainItem extends BaseSwiftToolchainItem {
107
+ category : "public" | "swiftly" ;
108
+ }
109
+
110
+ /** A {@link vscode.QuickPickItem} for a Swift toolchain provided by an installed Xcode application */
111
+ interface XcodeToolchainItem extends BaseSwiftToolchainItem {
112
+ category : "xcode" ;
113
+ xcodePath : string ;
114
+ }
115
+
102
116
/** A {@link vscode.QuickPickItem} that performs an action for the user */
103
117
interface ActionItem extends vscode . QuickPickItem {
104
118
type : "action" ;
@@ -128,6 +142,7 @@ type SelectToolchainItem = SwiftToolchainItem | ActionItem | SeparatorItem;
128
142
async function getQuickPickItems (
129
143
activeToolchain : SwiftToolchain | undefined
130
144
) : Promise < SelectToolchainItem [ ] > {
145
+ // Find any Xcode installations on the system
131
146
const xcodes = ( await SwiftToolchain . getXcodeInstalls ( ) )
132
147
. reverse ( )
133
148
. map < SwiftToolchainItem > ( xcodePath => {
@@ -141,17 +156,21 @@ async function getQuickPickItems(
141
156
) ;
142
157
return {
143
158
type : "toolchain" ,
159
+ category : "xcode" ,
144
160
label : path . basename ( xcodePath , ".app" ) ,
145
161
detail : xcodePath ,
162
+ xcodePath,
146
163
toolchainPath,
147
164
swiftFolderPath : path . join ( toolchainPath , "bin" ) ,
148
165
} ;
149
166
} ) ;
167
+ // Find any public Swift toolchains on the system
150
168
const toolchains = ( await SwiftToolchain . getToolchainInstalls ( ) )
151
169
. reverse ( )
152
170
. map < SwiftToolchainItem > ( toolchainPath => {
153
171
const result : SwiftToolchainItem = {
154
172
type : "toolchain" ,
173
+ category : "public" ,
155
174
label : path . basename ( toolchainPath , ".xctoolchain" ) ,
156
175
detail : toolchainPath ,
157
176
toolchainPath : path . join ( toolchainPath , "usr" ) ,
@@ -167,15 +186,18 @@ async function getQuickPickItems(
167
186
}
168
187
return result ;
169
188
} ) ;
189
+ // Find any Swift toolchains installed via Swiftly
170
190
const swiftlyToolchains = ( await SwiftToolchain . getSwiftlyToolchainInstalls ( ) )
171
191
. reverse ( )
172
192
. map < SwiftToolchainItem > ( toolchainPath => ( {
173
193
type : "toolchain" ,
194
+ category : "swiftly" ,
174
195
label : path . basename ( toolchainPath ) ,
175
196
detail : toolchainPath ,
176
197
toolchainPath : path . join ( toolchainPath , "usr" ) ,
177
198
swiftFolderPath : path . join ( toolchainPath , "usr" , "bin" ) ,
178
199
} ) ) ;
200
+ // Mark which toolchain is being actively used
179
201
if ( activeToolchain ) {
180
202
const toolchainInUse = [ ...xcodes , ...toolchains , ...swiftlyToolchains ] . find ( toolchain => {
181
203
return toolchain . toolchainPath === activeToolchain . toolchainPath ;
@@ -185,6 +207,7 @@ async function getQuickPickItems(
185
207
} else {
186
208
toolchains . splice ( 0 , 0 , {
187
209
type : "toolchain" ,
210
+ category : "public" ,
188
211
label : `Swift ${ activeToolchain . swiftVersion . toString ( ) } ` ,
189
212
description : "$(check) in use" ,
190
213
detail : activeToolchain . toolchainPath ,
@@ -193,6 +216,7 @@ async function getQuickPickItems(
193
216
} ) ;
194
217
}
195
218
}
219
+ // Various actions that the user can perform (e.g. to install new toolchains)
196
220
const actionItems : ActionItem [ ] = [ ] ;
197
221
if ( process . platform === "linux" ) {
198
222
actionItems . push ( {
@@ -232,69 +256,171 @@ async function getQuickPickItems(
232
256
* @param activeToolchain the {@link WorkspaceContext}
233
257
*/
234
258
export async function showToolchainSelectionQuickPick ( activeToolchain : SwiftToolchain | undefined ) {
259
+ let xcodePaths : string [ ] = [ ] ;
235
260
const selected = await vscode . window . showQuickPick < SelectToolchainItem > (
236
- getQuickPickItems ( activeToolchain ) ,
261
+ getQuickPickItems ( activeToolchain ) . then ( result => {
262
+ xcodePaths = result
263
+ . filter ( ( i ) : i is XcodeToolchainItem => "category" in i && i . category === "xcode" )
264
+ . map ( xcode => xcode . xcodePath ) ;
265
+ return result ;
266
+ } ) ,
237
267
{
238
268
title : "Select the Swift toolchain" ,
239
269
placeHolder : "Pick a Swift toolchain that VS Code will use" ,
240
270
canPickMany : false ,
241
271
}
242
272
) ;
243
273
if ( selected ?. type === "action" ) {
244
- await selected . run ( ) ;
245
- } else if ( selected ?. type === "toolchain" ) {
246
- const isUpdated = await setToolchainPath ( selected . swiftFolderPath , "prompt" ) ;
274
+ return await selected . run ( ) ;
275
+ }
276
+ if ( selected ?. type === "toolchain" ) {
277
+ // Select an Xcode to build with
278
+ let developerDir : string | undefined ;
279
+ if ( selected . category === "xcode" ) {
280
+ developerDir = selected . xcodePath ;
281
+ } else if ( xcodePaths . length === 1 ) {
282
+ developerDir = xcodePaths [ 0 ] ;
283
+ } else if ( process . platform === "darwin" && xcodePaths . length > 1 ) {
284
+ developerDir = await showDeveloperDirQuickPick ( xcodePaths ) ;
285
+ if ( ! developerDir ) {
286
+ return ;
287
+ }
288
+ }
289
+ // Update the toolchain path
290
+ const isUpdated = await setToolchainPath ( selected . swiftFolderPath , developerDir ) ;
247
291
if ( isUpdated && selected . onDidSelect ) {
248
292
await selected . onDidSelect ( ) ;
249
293
}
294
+ return ;
250
295
}
251
296
}
252
297
298
+ /**
299
+ * Prompt the user to choose a value for the DEVELOPER_DIR environment variable.
300
+ *
301
+ * @param xcodePaths An array of paths to available Xcode installations on the system
302
+ * @returns The selected DEVELOPER_DIR or undefined if the user cancelled selection
303
+ */
304
+ async function showDeveloperDirQuickPick ( xcodePaths : string [ ] ) : Promise < string | undefined > {
305
+ const selected = await vscode . window . showQuickPick < vscode . QuickPickItem > (
306
+ SwiftToolchain . getXcodeDeveloperDir ( configuration . swiftEnvironmentVariables ) . then (
307
+ existingDeveloperDir => {
308
+ return xcodePaths
309
+ . map ( xcodePath => {
310
+ const result : vscode . QuickPickItem = {
311
+ label : path . basename ( xcodePath , ".app" ) ,
312
+ detail : xcodePath ,
313
+ } ;
314
+ if ( existingDeveloperDir . startsWith ( xcodePath ) ) {
315
+ result . description = "$(check) in use" ;
316
+ }
317
+ return result ;
318
+ } )
319
+ . sort ( ( a , b ) => {
320
+ // Bring the active Xcode to the top
321
+ if ( existingDeveloperDir . startsWith ( a . detail ?? "" ) ) {
322
+ return - 1 ;
323
+ } else if ( existingDeveloperDir . startsWith ( b . detail ?? "" ) ) {
324
+ return 1 ;
325
+ }
326
+ // Otherwise sort by name
327
+ return a . label . localeCompare ( b . label ) ;
328
+ } ) ;
329
+ }
330
+ ) ,
331
+ {
332
+ title : "Select a developer directory" ,
333
+ placeHolder :
334
+ "Pick an Xcode installation to use as the developer directory and for the macOS SDK" ,
335
+ canPickMany : false ,
336
+ }
337
+ ) ;
338
+ return selected ?. detail ;
339
+ }
340
+
253
341
/**
254
342
* Delete all set Swift path settings.
255
343
*/
256
344
async function removeToolchainPath ( ) {
257
345
const swiftSettings = vscode . workspace . getConfiguration ( "swift" ) ;
346
+ const swiftEnvironmentSettings = swiftSettings . inspect ( "swiftEnvironmentVariables" ) ;
347
+ if ( swiftEnvironmentSettings ?. globalValue ) {
348
+ await swiftSettings . update (
349
+ "swiftEnvironmentVariables" ,
350
+ {
351
+ ...swiftEnvironmentSettings ?. globalValue ,
352
+ DEVELOPER_DIR : undefined ,
353
+ } ,
354
+ vscode . ConfigurationTarget . Global
355
+ ) ;
356
+ }
258
357
await swiftSettings . update ( "path" , undefined , vscode . ConfigurationTarget . Global ) ;
358
+ if ( swiftEnvironmentSettings ?. workspaceValue ) {
359
+ await swiftSettings . update (
360
+ "swiftEnvironmentVariables" ,
361
+ {
362
+ ...swiftEnvironmentSettings ?. workspaceValue ,
363
+ DEVELOPER_DIR : undefined ,
364
+ } ,
365
+ vscode . ConfigurationTarget . Workspace
366
+ ) ;
367
+ }
259
368
await swiftSettings . update ( "path" , undefined , vscode . ConfigurationTarget . Workspace ) ;
260
369
}
261
370
371
+ /**
372
+ * Update the toolchain path
373
+ * @param swiftFolderPath
374
+ * @param developerDir
375
+ * @returns
376
+ */
262
377
async function setToolchainPath (
263
- value : string | undefined ,
264
- target ?: vscode . ConfigurationTarget | "prompt"
378
+ swiftFolderPath : string | undefined ,
379
+ developerDir : string | undefined
265
380
) : Promise < boolean > {
266
- if ( target === "prompt" ) {
267
- const items : ( vscode . QuickPickItem & {
268
- target ?: vscode . ConfigurationTarget ;
269
- } ) [ ] = [ ] ;
270
- if ( vscode . workspace . workspaceFolders ) {
271
- items . push ( {
272
- label : "Workspace Configuration" ,
273
- description : "(Recommended)" ,
274
- detail : "Add to VS Code workspace configuration" ,
275
- target : vscode . ConfigurationTarget . Workspace ,
276
- } ) ;
277
- }
381
+ let target : vscode . ConfigurationTarget | undefined ;
382
+ const items : ( vscode . QuickPickItem & {
383
+ target ?: vscode . ConfigurationTarget ;
384
+ } ) [ ] = [ ] ;
385
+ if ( vscode . workspace . workspaceFolders ) {
278
386
items . push ( {
279
- label : "User Configuration" ,
280
- detail : "Add to VS Code user configuration." ,
281
- target : vscode . ConfigurationTarget . Global ,
387
+ label : "Workspace Configuration" ,
388
+ description : "(Recommended)" ,
389
+ detail : "Add to VS Code workspace configuration" ,
390
+ target : vscode . ConfigurationTarget . Workspace ,
282
391
} ) ;
283
- if ( items . length > 1 ) {
284
- const selected = await vscode . window . showQuickPick ( items , {
285
- title : "Toolchain Configuration" ,
286
- placeHolder : "Select a location to update the toolchain selection" ,
287
- canPickMany : false ,
288
- } ) ;
289
- if ( ! selected ) {
290
- return false ;
291
- }
292
- target = selected . target ;
293
- } else {
294
- target = vscode . ConfigurationTarget . Global ; // Global scope by default
392
+ }
393
+ items . push ( {
394
+ label : "User Configuration" ,
395
+ detail : "Add to VS Code user configuration." ,
396
+ target : vscode . ConfigurationTarget . Global ,
397
+ } ) ;
398
+ if ( items . length > 1 ) {
399
+ const selected = await vscode . window . showQuickPick ( items , {
400
+ title : "Toolchain Configuration" ,
401
+ placeHolder : "Select a location to update the toolchain selection" ,
402
+ canPickMany : false ,
403
+ } ) ;
404
+ if ( ! selected ) {
405
+ return false ;
295
406
}
407
+ target = selected . target ;
408
+ } else {
409
+ target = vscode . ConfigurationTarget . Global ; // Global scope by default
410
+ }
411
+ const swiftConfiguration = vscode . workspace . getConfiguration ( "swift" ) ;
412
+ await swiftConfiguration . update ( "path" , swiftFolderPath , target ) ;
413
+ if ( developerDir ) {
414
+ const swiftEnv = configuration . swiftEnvironmentVariables ;
415
+ await swiftConfiguration . update (
416
+ "swiftEnvironmentVariables" ,
417
+ {
418
+ ...swiftEnv ,
419
+ DEVELOPER_DIR : developerDir ,
420
+ } ,
421
+ target
422
+ ) ;
296
423
}
297
- await vscode . workspace . getConfiguration ( "swift" ) . update ( "path" , value , target ) ;
298
424
await checkAndRemoveWorkspaceSetting ( target ) ;
299
425
return true ;
300
426
}
@@ -307,15 +433,25 @@ async function checkAndRemoveWorkspaceSetting(target: vscode.ConfigurationTarget
307
433
const inspect = vscode . workspace . getConfiguration ( "swift" ) . inspect < string > ( "path" ) ;
308
434
if ( inspect ?. workspaceValue ) {
309
435
const confirmation = await vscode . window . showWarningMessage (
310
- "You already have the Swift path configured in Workspace Settings which takes precedence over User Settings." +
436
+ "You already have a Swift path configured in Workspace Settings which takes precedence over User Settings." +
311
437
" Would you like to remove the setting from your workspace and use the User Settings instead?" ,
312
438
"Remove Workspace Setting"
313
439
) ;
314
440
if ( confirmation !== "Remove Workspace Setting" ) {
315
441
return ;
316
442
}
317
- await vscode . workspace
318
- . getConfiguration ( "swift" )
319
- . update ( "path" , undefined , vscode . ConfigurationTarget . Workspace ) ;
443
+ const swiftSettings = vscode . workspace . getConfiguration ( "swift" ) ;
444
+ const swiftEnvironmentSettings = swiftSettings . inspect ( "swiftEnvironmentVariables" ) ;
445
+ if ( swiftEnvironmentSettings ?. workspaceValue ) {
446
+ await swiftSettings . update (
447
+ "swiftEnvironmentVariables" ,
448
+ {
449
+ ...swiftEnvironmentSettings ?. workspaceValue ,
450
+ DEVELOPER_DIR : undefined ,
451
+ } ,
452
+ vscode . ConfigurationTarget . Workspace
453
+ ) ;
454
+ }
455
+ await swiftSettings . update ( "path" , undefined , vscode . ConfigurationTarget . Workspace ) ;
320
456
}
321
457
}
0 commit comments