1- import { EventEmitter , LogOutputChannel , MarkdownString } from 'vscode' ;
1+ import {
2+ CancellationError ,
3+ CancellationToken ,
4+ Event ,
5+ EventEmitter ,
6+ LogOutputChannel ,
7+ MarkdownString ,
8+ ProgressLocation ,
9+ ThemeIcon ,
10+ window ,
11+ } from 'vscode' ;
12+ import { Disposable } from 'vscode-jsonrpc' ;
213import {
314 DidChangePackagesEventArgs ,
415 IconPath ,
516 Package ,
6- PackageManager ,
17+ PackageChangeKind ,
718 PackageManagementOptions ,
19+ PackageManager ,
820 PythonEnvironment ,
921 PythonEnvironmentApi ,
1022} from '../../api' ;
1123import { traceInfo } from '../../common/logging' ;
24+ import { getPipenv , parsePipenvList , runPipenv } from './pipenvUtils' ;
1225
13- export class PipenvPackageManager implements PackageManager {
26+ function getChanges ( before : Package [ ] , after : Package [ ] ) : { kind : PackageChangeKind ; pkg : Package } [ ] {
27+ const changes : { kind : PackageChangeKind ; pkg : Package } [ ] = [ ] ;
28+ before . forEach ( ( pkg ) => {
29+ changes . push ( { kind : PackageChangeKind . remove , pkg } ) ;
30+ } ) ;
31+ after . forEach ( ( pkg ) => {
32+ changes . push ( { kind : PackageChangeKind . add , pkg } ) ;
33+ } ) ;
34+ return changes ;
35+ }
36+
37+ export class PipenvPackageManager implements PackageManager , Disposable {
1438 public readonly name : string ;
1539 public readonly displayName ?: string ;
1640 public readonly description ?: string ;
1741 public readonly tooltip ?: string | MarkdownString ;
1842 public readonly iconPath ?: IconPath ;
19- public readonly log ?: LogOutputChannel ;
2043
2144 private readonly _onDidChangePackages = new EventEmitter < DidChangePackagesEventArgs > ( ) ;
22- public readonly onDidChangePackages = this . _onDidChangePackages . event ;
45+ public readonly onDidChangePackages : Event < DidChangePackagesEventArgs > = this . _onDidChangePackages . event ;
46+
47+ private packages : Map < string , Package [ ] > = new Map ( ) ;
2348
24- constructor (
25- public readonly api : PythonEnvironmentApi ,
26- log ?: LogOutputChannel
27- ) {
49+ constructor ( public readonly api : PythonEnvironmentApi , public readonly log ?: LogOutputChannel ) {
2850 this . name = 'pipenv' ;
2951 this . displayName = 'Pipenv' ;
3052 this . description = 'Manages packages using Pipenv' ;
3153 this . tooltip = new MarkdownString ( 'Install and manage packages using Pipenv package manager' ) ;
32- this . log = log ;
54+ this . iconPath = new ThemeIcon ( 'package' ) ;
3355 }
3456
3557 async manage ( environment : PythonEnvironment , options : PackageManagementOptions ) : Promise < void > {
36- // TODO: Implement pipenv package management
37- // This would run commands like:
38- // - pipenv install <package> for installation
39- // - pipenv uninstall <package> for uninstallation
40- // - pipenv install for installing from Pipfile
41-
42- traceInfo ( `Pipenv package management not yet implemented for environment: ${ environment . name } ` ) ;
43- traceInfo ( `Options: ${ JSON . stringify ( options ) } ` ) ;
44-
45- // For now, just log the operation
46- if ( options . install && options . install . length > 0 ) {
47- traceInfo ( `Would install packages: ${ options . install . join ( ', ' ) } ` ) ;
58+ const pipenvPath = await getPipenv ( ) ;
59+ if ( ! pipenvPath ) {
60+ throw new Error ( 'Pipenv not found' ) ;
4861 }
62+
63+ let toInstall : string [ ] = [ ...( options . install ?? [ ] ) ] ;
64+ let toUninstall : string [ ] = [ ...( options . uninstall ?? [ ] ) ] ;
65+
66+ if ( toInstall . length === 0 && toUninstall . length === 0 ) {
67+ traceInfo ( 'No packages specified for installation or uninstallation' ) ;
68+ return ;
69+ }
70+
71+ const manageOptions = {
72+ ...options ,
73+ install : toInstall ,
74+ uninstall : toUninstall ,
75+ } ;
76+
77+ await window . withProgress (
78+ {
79+ location : ProgressLocation . Notification ,
80+ title : 'Managing packages with Pipenv' ,
81+ cancellable : true ,
82+ } ,
83+ async ( _progress , token ) => {
84+ try {
85+ const before = this . packages . get ( environment . envId . id ) ?? [ ] ;
86+ const after = await this . managePipenvPackages ( environment , manageOptions , pipenvPath , token ) ;
87+ const changes = getChanges ( before , after ) ;
88+ this . packages . set ( environment . envId . id , after ) ;
89+ this . _onDidChangePackages . fire ( { environment, manager : this , changes } ) ;
90+ } catch ( e ) {
91+ if ( e instanceof CancellationError ) {
92+ throw e ;
93+ }
94+ this . log ?. error ( 'Error managing packages' , e ) ;
95+ setImmediate ( async ( ) => {
96+ const result = await window . showErrorMessage (
97+ 'Error managing packages with Pipenv' ,
98+ 'View Output' ,
99+ ) ;
100+ if ( result === 'View Output' ) {
101+ this . log ?. show ( ) ;
102+ }
103+ } ) ;
104+ throw e ;
105+ }
106+ } ,
107+ ) ;
108+ }
109+
110+ private async managePipenvPackages (
111+ environment : PythonEnvironment ,
112+ options : PackageManagementOptions ,
113+ pipenvPath : string ,
114+ token ?: CancellationToken ,
115+ ) : Promise < Package [ ] > {
116+ // Get the project directory from the environment's sysPrefix or a parent directory
117+ const projectDir = environment . sysPrefix ;
118+
119+ // Uninstall packages first
49120 if ( options . uninstall && options . uninstall . length > 0 ) {
50- traceInfo ( `Would uninstall packages: ${ options . uninstall . join ( ', ' ) } ` ) ;
121+ const uninstallArgs = [ 'uninstall' , ...options . uninstall , '--yes' ] ; // Skip confirmation
122+ await runPipenv ( pipenvPath , uninstallArgs , projectDir , this . log , token ) ;
123+ }
124+
125+ // Install packages
126+ if ( options . install && options . install . length > 0 ) {
127+ const installArgs = [ 'install' , ...options . install ] ;
128+ if ( options . upgrade ) {
129+ installArgs . push ( '--upgrade' ) ;
130+ }
131+ await runPipenv ( pipenvPath , installArgs , projectDir , this . log , token ) ;
51132 }
52133
53- // Fire change event (though packages haven't actually changed)
54- // this._onDidChangePackages.fire({ changes: [] });
134+ return await this . refreshPipenvPackages ( environment , pipenvPath ) ;
55135 }
56136
57137 async refresh ( environment : PythonEnvironment ) : Promise < void > {
58- // TODO: Implement package list refresh
59- // This would run 'pipenv graph' or similar to get package list
60- traceInfo ( `Pipenv package refresh not yet implemented for environment: ${ environment . name } ` ) ;
138+ await window . withProgress (
139+ {
140+ location : ProgressLocation . Window ,
141+ title : 'Refreshing packages' ,
142+ } ,
143+ async ( ) => {
144+ const pipenvPath = await getPipenv ( ) ;
145+ if ( ! pipenvPath ) {
146+ throw new Error ( 'Pipenv not found' ) ;
147+ }
148+
149+ const before = this . packages . get ( environment . envId . id ) ?? [ ] ;
150+ const after = await this . refreshPipenvPackages ( environment , pipenvPath ) ;
151+ const changes = getChanges ( before , after ) ;
152+ this . packages . set ( environment . envId . id , after ) ;
153+ if ( changes . length > 0 ) {
154+ this . _onDidChangePackages . fire ( { environment, manager : this , changes } ) ;
155+ }
156+ } ,
157+ ) ;
158+ }
159+
160+ private async refreshPipenvPackages ( environment : PythonEnvironment , pipenvPath : string ) : Promise < Package [ ] > {
161+ try {
162+ // Use pipenv run pip list to get packages in the virtual environment
163+ const projectDir = environment . sysPrefix ;
164+ const data = await runPipenv ( pipenvPath , [ 'run' , 'pip' , 'list' ] , projectDir , this . log ) ;
165+ const pipenvPackages = parsePipenvList ( data ) ;
166+
167+ return pipenvPackages . map ( ( pkg ) => this . api . createPackageItem ( pkg , environment , this ) ) ;
168+ } catch ( error ) {
169+ this . log ?. error ( 'Error refreshing pipenv packages' , error ) ;
170+ return [ ] ;
171+ }
61172 }
62173
63174 async getPackages ( environment : PythonEnvironment ) : Promise < Package [ ] | undefined > {
64- // TODO: Implement package listing
65- // This would parse output from 'pipenv graph' or 'pip list' in the pipenv environment
66- traceInfo ( `Pipenv package listing not yet implemented for environment: ${ environment . name } ` ) ;
67- return [ ] ;
175+ if ( ! this . packages . has ( environment . envId . id ) ) {
176+ await this . refresh ( environment ) ;
177+ }
178+ return this . packages . get ( environment . envId . id ) ;
68179 }
69180
70181 public dispose ( ) {
71182 this . _onDidChangePackages . dispose ( ) ;
183+ this . packages . clear ( ) ;
72184 }
73- }
185+ }
0 commit comments