@@ -9,7 +9,7 @@ import { IProcessServiceFactory } from '../../common/process/types';
99import { ITerminalServiceFactory } from '../../common/terminal/types' ;
1010import { IComponentAdapter , ICondaService } from '../../interpreter/contracts' ;
1111import { IServiceContainer } from '../../ioc/types' ;
12- import { IPackageManager , MessageEmitter , PackageKernel } from './types' ;
12+ import { IPackageManager , MessageEmitter , PackageSession } from './types' ;
1313
1414/** Package info returned by `conda search --json` */
1515interface CondaPackageInfo {
@@ -64,11 +64,11 @@ export class CondaPackageManager implements IPackageManager {
6464 private readonly _pythonPath : string ,
6565 _messageEmitter : MessageEmitter ,
6666 private readonly _serviceContainer : IServiceContainer ,
67- private readonly _kernel : PackageKernel ,
67+ private readonly _session : PackageSession ,
6868 ) { }
6969
70- async getPackages ( ) : Promise < positron . LanguageRuntimePackage [ ] > {
71- return this . _kernel . callMethod ( 'getPackagesInstalled' ) ;
70+ async getPackages ( token ?: vscode . CancellationToken ) : Promise < positron . LanguageRuntimePackage [ ] > {
71+ return this . _callMethod < positron . LanguageRuntimePackage [ ] > ( 'getPackagesInstalled' , token ) ;
7272 }
7373
7474 /**
@@ -83,54 +83,70 @@ export class CondaPackageManager implements IPackageManager {
8383 }
8484 }
8585
86- async installPackages ( packages : positron . PackageSpec [ ] ) : Promise < void > {
86+ async installPackages ( packages : positron . PackageSpec [ ] , token ?: vscode . CancellationToken ) : Promise < void > {
8787 if ( packages . length === 0 ) {
8888 return ;
8989 }
9090
91+ if ( token ?. isCancellationRequested ) {
92+ throw new vscode . CancellationError ( ) ;
93+ }
94+
9195 await this . _ensureConda ( ) ;
9296
9397 const packageSpecs = this . _formatPackageSpecs ( packages ) ;
9498 const envPrefix = await this . _getEnvironmentPrefix ( ) ;
9599 const args = [ 'install' , '--prefix' , envPrefix , '-y' , ...packageSpecs ] ;
96100
97- await this . _executeCondaInTerminal ( args ) ;
101+ await this . _executeCondaInTerminal ( args , token ) ;
98102 }
99103
100- async uninstallPackages ( packages : string [ ] ) : Promise < void > {
104+ async uninstallPackages ( packages : string [ ] , token ?: vscode . CancellationToken ) : Promise < void > {
101105 if ( packages . length === 0 ) {
102106 return ;
103107 }
104108
109+ if ( token ?. isCancellationRequested ) {
110+ throw new vscode . CancellationError ( ) ;
111+ }
112+
105113 await this . _ensureConda ( ) ;
106114
107115 const envPrefix = await this . _getEnvironmentPrefix ( ) ;
108116 const args = [ 'remove' , '--prefix' , envPrefix , '-y' , ...packages ] ;
109117
110- await this . _executeCondaInTerminal ( args ) ;
118+ await this . _executeCondaInTerminal ( args , token ) ;
111119 }
112120
113- async updatePackages ( packages : positron . PackageSpec [ ] ) : Promise < void > {
121+ async updatePackages ( packages : positron . PackageSpec [ ] , token ?: vscode . CancellationToken ) : Promise < void > {
114122 // Use installPackages() because conda update doesn't support version specs.
115123 // conda install will update (or downgrade) to the specified version.
116- return this . installPackages ( packages ) ;
124+ return this . installPackages ( packages , token ) ;
117125 }
118126
119- async updateAllPackages ( ) : Promise < void > {
127+ async updateAllPackages ( token ?: vscode . CancellationToken ) : Promise < void > {
128+ if ( token ?. isCancellationRequested ) {
129+ throw new vscode . CancellationError ( ) ;
130+ }
131+
120132 await this . _ensureConda ( ) ;
121133
122134 const envPrefix = await this . _getEnvironmentPrefix ( ) ;
123135 const args = [ 'update' , '--prefix' , envPrefix , '--all' , '-y' ] ;
124136
125- await this . _executeCondaInTerminal ( args ) ;
137+ await this . _executeCondaInTerminal ( args , token ) ;
126138 }
127139
128- async searchPackages ( query : string ) : Promise < positron . LanguageRuntimePackage [ ] > {
140+ async searchPackages ( query : string , token ?: vscode . CancellationToken ) : Promise < positron . LanguageRuntimePackage [ ] > {
141+ if ( token ?. isCancellationRequested ) {
142+ throw new vscode . CancellationError ( ) ;
143+ }
144+
129145 await this . _ensureConda ( ) ;
130146
131147 try {
132148 // Use wildcard pattern for partial matching
133- const result = await this . _executeCondaWithOutput ( [ 'search' , `*${ query } *` , '--json' ] ) ;
149+ const result = await this . _executeCondaWithOutput ( [ 'search' , `*${ query } *` , '--json' ] , token ) ;
134150 const json = parseCondaSearchResult ( result ) ;
135151
136152 // Return unique package names with the latest version (sorted by timestamp)
@@ -144,17 +160,24 @@ export class CondaPackageManager implements IPackageManager {
144160 version : latest . version ,
145161 } ;
146162 } ) ;
147- } catch {
163+ } catch ( e ) {
164+ if ( e instanceof vscode . CancellationError ) {
165+ throw e ;
166+ }
148167 // Return empty array if search fails (e.g., no matches)
149168 return [ ] ;
150169 }
151170 }
152171
153- async searchPackageVersions ( name : string ) : Promise < string [ ] > {
172+ async searchPackageVersions ( name : string , token ?: vscode . CancellationToken ) : Promise < string [ ] > {
173+ if ( token ?. isCancellationRequested ) {
174+ throw new vscode . CancellationError ( ) ;
175+ }
176+
154177 await this . _ensureConda ( ) ;
155178
156179 try {
157- const result = await this . _executeCondaWithOutput ( [ 'search' , name , '--json' ] ) ;
180+ const result = await this . _executeCondaWithOutput ( [ 'search' , name , '--json' ] , token ) ;
158181 const json = parseCondaSearchResult ( result ) ;
159182
160183 // Get all unique versions for this package
@@ -167,7 +190,10 @@ export class CondaPackageManager implements IPackageManager {
167190 const sorted = [ ...packageInfo ] . sort ( ( a , b ) => b . timestamp - a . timestamp ) ;
168191 const versions = [ ...new Set ( sorted . map ( ( p ) => p . version ) ) ] ;
169192 return versions ;
170- } catch {
193+ } catch ( e ) {
194+ if ( e instanceof vscode . CancellationError ) {
195+ throw e ;
196+ }
171197 return [ ] ;
172198 }
173199 }
@@ -224,31 +250,74 @@ export class CondaPackageManager implements IPackageManager {
224250
225251 /**
226252 * Execute a conda command in the terminal (visible to user).
253+ * @param args The conda arguments to execute
254+ * @param token Optional cancellation token
227255 */
228- private async _executeCondaInTerminal ( args : string [ ] ) : Promise < void > {
256+ private async _executeCondaInTerminal ( args : string [ ] , token ?: vscode . CancellationToken ) : Promise < void > {
229257 const condaFile = await this . _getCondaFile ( ) ;
230258 const terminalService = this . _serviceContainer
231259 . get < ITerminalServiceFactory > ( ITerminalServiceFactory )
232260 . getTerminalService ( { } ) ;
233261 // Ensure terminal is created and ready before sending command
234262 await terminalService . show ( ) ;
235- const tokenSource = new vscode . CancellationTokenSource ( ) ;
263+
264+ const disposable = token ?. onCancellationRequested ( async ( ) => {
265+ // Send Ctrl+C to interrupt the running command
266+ await terminalService . sendText ( '\x03' ) ;
267+ } ) ;
268+
236269 try {
237- await terminalService . sendCommand ( condaFile , args , tokenSource . token ) ;
270+ await terminalService . sendCommand ( condaFile , args , token ) ;
238271 } finally {
239- tokenSource . dispose ( ) ;
272+ disposable ? .dispose ( ) ;
240273 }
241274 }
242275
243276 /**
244277 * Execute a conda command and capture stdout.
245278 */
246- private async _executeCondaWithOutput ( args : string [ ] ) : Promise < string > {
279+ private async _executeCondaWithOutput ( args : string [ ] , token ?: vscode . CancellationToken ) : Promise < string > {
247280 const condaFile = await this . _getCondaFile ( ) ;
248281 const processServiceFactory = this . _serviceContainer . get < IProcessServiceFactory > ( IProcessServiceFactory ) ;
249282 const processService = await processServiceFactory . create ( ) ;
250283
251- const result = await processService . exec ( condaFile , args ) ;
284+ const result = await processService . exec ( condaFile , args , { token } ) ;
252285 return result . stdout ;
253286 }
287+
288+ /**
289+ * Call a kernel method with cancellation support.
290+ * If the token is cancelled, interrupts the kernel (if supported).
291+ */
292+ private async _callMethod < T > ( method : string , token ?: vscode . CancellationToken , ...args : unknown [ ] ) : Promise < T > {
293+ if ( token ?. isCancellationRequested ) {
294+ throw new vscode . CancellationError ( ) ;
295+ }
296+
297+ const resultPromise = this . _session . callMethod ( method , ...args ) as Promise < T > ;
298+
299+ // If no token provided, just return the method result
300+ if ( ! token ) {
301+ return resultPromise ;
302+ }
303+
304+ // Wrap callMethod promise with cancellation handling
305+ return new Promise < T > ( ( resolve , reject ) => {
306+ const cancelDisp = token . onCancellationRequested ( async ( ) => {
307+ // Interrupt the session via the runtime service
308+ await positron . runtime . interruptSession ( this . _session . metadata . sessionId ) ;
309+ reject ( new vscode . CancellationError ( ) ) ;
310+ } ) ;
311+
312+ resultPromise
313+ . then ( ( result ) => {
314+ cancelDisp . dispose ( ) ;
315+ resolve ( result ) ;
316+ } )
317+ . catch ( ( err ) => {
318+ cancelDisp . dispose ( ) ;
319+ reject ( err ) ;
320+ } ) ;
321+ } ) ;
322+ }
254323}
0 commit comments