@@ -160,12 +160,14 @@ export function parseSwiftlyMissingToolchainError(
160160 * @param version The toolchain version to install
161161 * @param logger Optional logger for error reporting
162162 * @param folder Optional folder context
163+ * @param token Optional cancellation token to abort the installation
163164 * @returns Promise<boolean> true if toolchain was successfully installed, false otherwise
164165 */
165166export async function handleMissingSwiftlyToolchain (
166167 version : string ,
167168 logger ?: SwiftLogger ,
168- folder ?: vscode . Uri
169+ folder ?: vscode . Uri ,
170+ token ?: vscode . CancellationToken
169171) : Promise < boolean > {
170172 logger ?. info ( `Attempting to handle missing toolchain: ${ version } ` ) ;
171173
@@ -178,10 +180,12 @@ export async function handleMissingSwiftlyToolchain(
178180
179181 // Use the existing installation function without showing reload notification
180182 // (since we want to continue the current operation)
181- return await installSwiftlyToolchainVersion ( version , logger , false ) ;
183+ return await installSwiftlyToolchainVersion ( version , logger , false , token ) ;
182184}
183185
184186export class Swiftly {
187+ public static cancellationMessage = "Installation cancelled by user" ;
188+
185189 /**
186190 * Finds the version of Swiftly installed on the system.
187191 *
@@ -452,11 +456,13 @@ export class Swiftly {
452456 * @param version The toolchain version to install.
453457 * @param progressCallback Optional callback that receives progress data as JSON objects.
454458 * @param logger Optional logger for error reporting.
459+ * @param token Optional cancellation token to abort the installation.
455460 */
456461 public static async installToolchain (
457462 version : string ,
458463 progressCallback ?: ( progressData : SwiftlyProgressData ) => void ,
459- logger ?: SwiftLogger
464+ logger ?: SwiftLogger ,
465+ token ?: vscode . CancellationToken
460466 ) : Promise < void > {
461467 if ( ! this . isSupported ( ) ) {
462468 throw new Error ( "Swiftly is not supported on this platform" ) ;
@@ -481,7 +487,18 @@ export class Swiftly {
481487 crlfDelay : Infinity ,
482488 } ) ;
483489
490+ // Handle cancellation during progress tracking
491+ const cancellationHandler = token ?. onCancellationRequested ( ( ) => {
492+ rl . close ( ) ;
493+ reject ( new Error ( Swiftly . cancellationMessage ) ) ;
494+ } ) ;
495+
484496 rl . on ( "line" , ( line : string ) => {
497+ if ( token ?. isCancellationRequested ) {
498+ rl . close ( ) ;
499+ return ;
500+ }
501+
485502 try {
486503 const progressData = JSON . parse ( line . trim ( ) ) as SwiftlyProgressData ;
487504 progressCallback ( progressData ) ;
@@ -491,10 +508,12 @@ export class Swiftly {
491508 } ) ;
492509
493510 rl . on ( "close" , ( ) => {
511+ cancellationHandler ?. dispose ( ) ;
494512 resolve ( ) ;
495513 } ) ;
496514
497515 rl . on ( "error" , err => {
516+ cancellationHandler ?. dispose ( ) ;
498517 reject ( err ) ;
499518 } ) ;
500519 } ) ;
@@ -514,29 +533,70 @@ export class Swiftly {
514533 }
515534
516535 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+ ) ;
518549
519550 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+ ] ) ;
521561 } else {
522562 await installPromise ;
523563 }
524564
565+ // Check for cancellation before post-install
566+ if ( token ?. isCancellationRequested ) {
567+ throw new Error ( Swiftly . cancellationMessage ) ;
568+ }
569+
525570 if ( process . platform === "linux" ) {
526571 await this . handlePostInstallFile ( postInstallFilePath , version , logger ) ;
527572 }
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 ;
528582 } finally {
529583 if ( progressPipePath ) {
530584 try {
531585 await fs . unlink ( progressPipePath ) ;
532586 } catch {
533- // Ignore errors if the pipe file doesn't exist
587+ // Ignore errors - file may not exist
534588 }
535589 }
590+
591+ // Clean up post-install file
536592 try {
537593 await fs . unlink ( postInstallFilePath ) ;
538594 } 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 } ` ) ;
540600 }
541601 }
542602 }
0 commit comments