@@ -160,12 +160,14 @@ export function parseSwiftlyMissingToolchainError(
160
160
* @param version The toolchain version to install
161
161
* @param logger Optional logger for error reporting
162
162
* @param folder Optional folder context
163
+ * @param token Optional cancellation token to abort the installation
163
164
* @returns Promise<boolean> true if toolchain was successfully installed, false otherwise
164
165
*/
165
166
export async function handleMissingSwiftlyToolchain (
166
167
version : string ,
167
168
logger ?: SwiftLogger ,
168
- folder ?: vscode . Uri
169
+ folder ?: vscode . Uri ,
170
+ token ?: vscode . CancellationToken
169
171
) : Promise < boolean > {
170
172
logger ?. info ( `Attempting to handle missing toolchain: ${ version } ` ) ;
171
173
@@ -178,10 +180,12 @@ export async function handleMissingSwiftlyToolchain(
178
180
179
181
// Use the existing installation function without showing reload notification
180
182
// (since we want to continue the current operation)
181
- return await installSwiftlyToolchainVersion ( version , logger , false ) ;
183
+ return await installSwiftlyToolchainVersion ( version , logger , false , token ) ;
182
184
}
183
185
184
186
export class Swiftly {
187
+ public static cancellationMessage = "Installation cancelled by user" ;
188
+
185
189
/**
186
190
* Finds the version of Swiftly installed on the system.
187
191
*
@@ -452,11 +456,13 @@ export class Swiftly {
452
456
* @param version The toolchain version to install.
453
457
* @param progressCallback Optional callback that receives progress data as JSON objects.
454
458
* @param logger Optional logger for error reporting.
459
+ * @param token Optional cancellation token to abort the installation.
455
460
*/
456
461
public static async installToolchain (
457
462
version : string ,
458
463
progressCallback ?: ( progressData : SwiftlyProgressData ) => void ,
459
- logger ?: SwiftLogger
464
+ logger ?: SwiftLogger ,
465
+ token ?: vscode . CancellationToken
460
466
) : Promise < void > {
461
467
if ( ! this . isSupported ( ) ) {
462
468
throw new Error ( "Swiftly is not supported on this platform" ) ;
@@ -481,7 +487,18 @@ export class Swiftly {
481
487
crlfDelay : Infinity ,
482
488
} ) ;
483
489
490
+ // Handle cancellation during progress tracking
491
+ const cancellationHandler = token ?. onCancellationRequested ( ( ) => {
492
+ rl . close ( ) ;
493
+ reject ( new Error ( Swiftly . cancellationMessage ) ) ;
494
+ } ) ;
495
+
484
496
rl . on ( "line" , ( line : string ) => {
497
+ if ( token ?. isCancellationRequested ) {
498
+ rl . close ( ) ;
499
+ return ;
500
+ }
501
+
485
502
try {
486
503
const progressData = JSON . parse ( line . trim ( ) ) as SwiftlyProgressData ;
487
504
progressCallback ( progressData ) ;
@@ -491,10 +508,12 @@ export class Swiftly {
491
508
} ) ;
492
509
493
510
rl . on ( "close" , ( ) => {
511
+ cancellationHandler ?. dispose ( ) ;
494
512
resolve ( ) ;
495
513
} ) ;
496
514
497
515
rl . on ( "error" , err => {
516
+ cancellationHandler ?. dispose ( ) ;
498
517
reject ( err ) ;
499
518
} ) ;
500
519
} ) ;
@@ -514,29 +533,70 @@ export class Swiftly {
514
533
}
515
534
516
535
try {
517
- const installPromise = execFile ( "swiftly" , installArgs ) ;
536
+ // Create output streams for process output
537
+ const stdoutStream = new Stream . PassThrough ( ) ;
538
+ const stderrStream = new Stream . PassThrough ( ) ;
539
+
540
+ // Use execFileStreamOutput with cancellation token
541
+ const installPromise = execFileStreamOutput (
542
+ "swiftly" ,
543
+ installArgs ,
544
+ stdoutStream ,
545
+ stderrStream ,
546
+ token || null ,
547
+ { }
548
+ ) ;
518
549
519
550
if ( progressPromise ) {
520
- await Promise . all ( [ installPromise , progressPromise ] ) ;
551
+ await Promise . race ( [
552
+ Promise . all ( [ installPromise , progressPromise ] ) ,
553
+ new Promise < never > ( ( _ , reject ) => {
554
+ if ( token ) {
555
+ token . onCancellationRequested ( ( ) =>
556
+ reject ( new Error ( Swiftly . cancellationMessage ) )
557
+ ) ;
558
+ }
559
+ } ) ,
560
+ ] ) ;
521
561
} else {
522
562
await installPromise ;
523
563
}
524
564
565
+ // Check for cancellation before post-install
566
+ if ( token ?. isCancellationRequested ) {
567
+ throw new Error ( Swiftly . cancellationMessage ) ;
568
+ }
569
+
525
570
if ( process . platform === "linux" ) {
526
571
await this . handlePostInstallFile ( postInstallFilePath , version , logger ) ;
527
572
}
573
+ } catch ( error ) {
574
+ if (
575
+ token ?. isCancellationRequested ||
576
+ ( error as Error ) . message . includes ( Swiftly . cancellationMessage )
577
+ ) {
578
+ logger ?. info ( `Installation of ${ version } was cancelled by user` ) ;
579
+ throw new Error ( Swiftly . cancellationMessage ) ;
580
+ }
581
+ throw error ;
528
582
} finally {
529
583
if ( progressPipePath ) {
530
584
try {
531
585
await fs . unlink ( progressPipePath ) ;
532
586
} catch {
533
- // Ignore errors if the pipe file doesn't exist
587
+ // Ignore errors - file may not exist
534
588
}
535
589
}
590
+
591
+ // Clean up post-install file
536
592
try {
537
593
await fs . unlink ( postInstallFilePath ) ;
538
594
} catch {
539
- // Ignore errors if the post-install file doesn't exist
595
+ // Ignore errors - file may not exist
596
+ }
597
+
598
+ if ( token ?. isCancellationRequested ) {
599
+ logger ?. info ( `Cleaned up temporary files for cancelled installation of ${ version } ` ) ;
540
600
}
541
601
}
542
602
}
0 commit comments