@@ -65,6 +65,7 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
6565 */
6666 private readonly _activeSelection = new Map < string , PythonEnvironment | undefined > ( ) ;
6767 private readonly _selectionRevisions = new Map < string , number > ( ) ;
68+ private readonly _selectionOperationCounters = new Map < string , number > ( ) ;
6869
6970 private _onDidChangeEnvironmentManager = new EventEmitter < DidChangeEnvironmentManagerEventArgs > ( ) ;
7071 private _onDidChangePackageManager = new EventEmitter < DidChangePackageManagerEventArgs > ( ) ;
@@ -117,7 +118,7 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
117118 ) ;
118119 } ) ,
119120 mgr . onDidChangeEnvironment ( ( e : DidChangeEnvironmentEventArgs ) => {
120- if ( e . old ?. envId . id === e . new ?. envId . id ) {
121+ if ( this . isSameEnvironment ( e . old , e . new ) ) {
121122 return ;
122123 }
123124
@@ -362,11 +363,12 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
362363 }
363364 const project = scope ? this . pm . get ( scope ) : undefined ;
364365 const key = this . getActiveSelectionKey ( scope , manager , project ) ;
366+ const operation = this . beginSelectionOperation ( key ) ;
367+ const inlineClearOperation =
368+ scope instanceof Uri && manager . id !== INLINE_SCRIPT_MANAGER_ID
369+ ? this . beginSelectionOperation ( this . getInlineScriptSelectionKey ( scope ) )
370+ : undefined ;
365371 await manager . set ( scope , environment ) ;
366- if ( scope instanceof Uri ) {
367- this . clearInlineActiveSelection ( scope , manager ) ;
368- }
369- this . bumpSelectionRevision ( key ) ;
370372
371373 // Only persist to settings when explicitly requested
372374 if ( shouldPersistSettings && scope ) {
@@ -393,8 +395,14 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
393395 ) ;
394396 }
395397
398+ if ( scope instanceof Uri ) {
399+ this . clearInlineActiveSelection ( scope , manager , inlineClearOperation ) ;
400+ }
401+ if ( ! this . commitSelectionOperation ( key , operation ) ) {
402+ return ;
403+ }
396404 const oldEnv = this . _activeSelection . get ( key ) ;
397- if ( oldEnv ?. envId . id !== environment ?. envId . id ) {
405+ if ( ! this . isSameEnvironment ( oldEnv , environment ) ) {
398406 this . _activeSelection . set ( key , environment ) ;
399407 await new Promise < void > ( ( resolve , reject ) => {
400408 setImmediate ( ( ) => {
@@ -443,42 +451,44 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
443451 const settings : EditAllManagerSettings [ ] = [ ] ;
444452 const events : DidChangeEnvironmentEventArgs [ ] = [ ] ;
445453 if ( Array . isArray ( scope ) && scope . every ( ( s ) => s instanceof Uri ) ) {
454+ const selections = scope . map ( ( uri ) => this . beginPendingSelection ( uri , manager ) ) ;
446455 await manager . set ( scope , environment ) ;
447- scope . forEach ( ( uri ) => {
448- this . clearInlineActiveSelection ( uri , manager ) ;
449- const project = this . pm . get ( uri ) ;
450- this . bumpSelectionRevision ( this . getActiveSelectionKey ( uri , manager , project ) ) ;
451- } ) ;
452- scope . forEach ( ( uri ) => {
453- const m = this . getEnvironmentManager ( uri ) ;
454- const project = this . pm . get ( uri ) ;
456+ selections . forEach ( ( selection ) => {
457+ const m = this . getEnvironmentManager ( selection . scope ) ;
455458 // Always add settings when persisting, OR when manager differs
456459 if (
457460 ( shouldPersistSettings || manager . id !== m ?. id ) &&
458- this . canPersistManagerSettingForScope ( uri , manager , project )
461+ this . canPersistManagerSettingForScope ( selection . scope , manager , selection . project )
459462 ) {
460463 settings . push ( {
461- project,
464+ project : selection . project ,
462465 envManager : manager . id ,
463466 packageManager : manager . preferredPackageManagerId ,
464467 } ) ;
465468 }
466-
467- const key = this . getActiveSelectionKey ( uri , manager , project ) ;
468- const oldEnv = this . _activeSelection . get ( key ) ;
469- if ( oldEnv ?. envId . id !== environment ?. envId . id ) {
470- this . _activeSelection . set ( key , environment ) ;
469+ } ) ;
470+ if ( shouldPersistSettings ) {
471+ await setAllManagerSettings ( settings ) ;
472+ }
473+ selections . forEach ( ( selection ) => {
474+ this . clearInlineActiveSelection ( selection . scope , manager , selection . inlineClearOperation ) ;
475+ if ( ! this . commitSelectionOperation ( selection . key , selection . operation ) ) {
476+ return ;
477+ }
478+ const oldEnv = this . _activeSelection . get ( selection . key ) ;
479+ if ( ! this . isSameEnvironment ( oldEnv , environment ) ) {
480+ this . _activeSelection . set ( selection . key , environment ) ;
471481 events . push ( {
472- uri : this . getActiveSelectionUri ( uri , manager , project ) ,
482+ uri : this . getActiveSelectionUri ( selection . scope , manager , selection . project ) ,
473483 new : environment ,
474484 old : oldEnv ,
475485 } ) ;
476486 }
477487 } ) ;
478488 } else if ( typeof scope === 'string' && scope === 'global' ) {
479489 const m = this . getEnvironmentManager ( undefined ) ;
490+ const operation = this . beginSelectionOperation ( 'global' ) ;
480491 await manager . set ( undefined , environment ) ;
481- this . bumpSelectionRevision ( 'global' ) ;
482492 // Always add settings when persisting, OR when manager differs
483493 if ( shouldPersistSettings || manager . id !== m ?. id ) {
484494 settings . push ( {
@@ -488,15 +498,16 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
488498 } ) ;
489499 }
490500
491- const oldEnv = this . _activeSelection . get ( 'global' ) ;
492- if ( oldEnv ?. envId . id !== environment ?. envId . id ) {
493- this . _activeSelection . set ( 'global' , environment ) ;
494- events . push ( { uri : undefined , new : environment , old : oldEnv } ) ;
501+ if ( shouldPersistSettings ) {
502+ await setAllManagerSettings ( settings ) ;
503+ }
504+ if ( this . commitSelectionOperation ( 'global' , operation ) ) {
505+ const oldEnv = this . _activeSelection . get ( 'global' ) ;
506+ if ( ! this . isSameEnvironment ( oldEnv , environment ) ) {
507+ this . _activeSelection . set ( 'global' , environment ) ;
508+ events . push ( { uri : undefined , new : environment , old : oldEnv } ) ;
509+ }
495510 }
496- }
497- // Only persist to settings when explicitly requested
498- if ( shouldPersistSettings ) {
499- await setAllManagerSettings ( settings ) ;
500511 }
501512 if ( events . length > 0 ) {
502513 await new Promise < void > ( ( resolve , reject ) => {
@@ -521,21 +532,19 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
521532 }
522533 } ) ;
523534 for ( const [ manager , uris ] of groupedScopes ) {
535+ const selections = uris . map ( ( uri ) => this . beginPendingSelection ( uri , manager ) ) ;
524536 await manager . set ( uris ) ;
525- uris . forEach ( ( uri ) => {
526- const project = this . pm . get ( uri ) ;
527- this . bumpSelectionRevision ( this . getActiveSelectionKey ( uri , manager , project ) ) ;
528- } ) ;
529537 await Promise . all (
530- uris . map ( async ( uri ) => {
531- const project = this . pm . get ( uri ) ;
532- const newEnv = await manager . get ( uri ) ;
533- const key = this . getActiveSelectionKey ( uri , manager , project ) ;
534- const oldEnv = this . _activeSelection . get ( key ) ;
535- if ( oldEnv ?. envId . id !== newEnv ?. envId . id ) {
536- this . _activeSelection . set ( key , newEnv ) ;
538+ selections . map ( async ( selection ) => {
539+ const newEnv = await manager . get ( selection . scope ) ;
540+ if ( ! this . commitSelectionOperation ( selection . key , selection . operation ) ) {
541+ return ;
542+ }
543+ const oldEnv = this . _activeSelection . get ( selection . key ) ;
544+ if ( ! this . isSameEnvironment ( oldEnv , newEnv ) ) {
545+ this . _activeSelection . set ( selection . key , newEnv ) ;
537546 events . push ( {
538- uri : this . getActiveSelectionUri ( uri , manager , project ) ,
547+ uri : this . getActiveSelectionUri ( selection . scope , manager , selection . project ) ,
539548 new : newEnv ,
540549 old : oldEnv ,
541550 } ) ;
@@ -546,13 +555,15 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
546555 } else if ( typeof scope === 'string' && scope === 'global' ) {
547556 const manager = this . getEnvironmentManager ( undefined ) ;
548557 if ( manager ) {
558+ const operation = this . beginSelectionOperation ( 'global' ) ;
549559 await manager . set ( undefined ) ;
550- this . bumpSelectionRevision ( 'global' ) ;
551560 const newEnv = await manager . get ( undefined ) ;
552- const oldEnv = this . _activeSelection . get ( 'global' ) ;
553- if ( oldEnv ?. envId . id !== newEnv ?. envId . id ) {
554- this . _activeSelection . set ( 'global' , newEnv ) ;
555- events . push ( { uri : undefined , new : newEnv , old : oldEnv } ) ;
561+ if ( this . commitSelectionOperation ( 'global' , operation ) ) {
562+ const oldEnv = this . _activeSelection . get ( 'global' ) ;
563+ if ( ! this . isSameEnvironment ( oldEnv , newEnv ) ) {
564+ this . _activeSelection . set ( 'global' , newEnv ) ;
565+ events . push ( { uri : undefined , new : newEnv , old : oldEnv } ) ;
566+ }
556567 }
557568 }
558569 }
@@ -640,27 +651,24 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
640651
641652 const project = scope ? this . pm . get ( scope ) : undefined ;
642653 const key = this . getActiveSelectionKey ( scope , manager , project ) ;
643- const revision = ( this . _selectionRevisions . get ( key ) ?? 0 ) + 1 ;
654+ const operation = this . beginSelectionOperation ( key ) ;
644655 const newEnv = await manager . get ( scope ) ;
645- if (
646- this . getEnvironmentManager ( scope ) !== manager ||
647- ( this . _selectionRevisions . get ( key ) ?? 0 ) >= revision
648- ) {
656+ if ( this . getEnvironmentManager ( scope ) !== manager ) {
649657 return ;
650658 }
651- this . _selectionRevisions . set ( key , revision ) ;
652659
653660 const oldEnv = this . _activeSelection . get ( key ) ;
654- if ( oldEnv ?. envId . id !== newEnv ?. envId . id ) {
655- this . _activeSelection . set ( key , newEnv ) ;
656- setImmediate ( ( ) =>
657- this . _onDidChangeActiveEnvironment . fire ( {
658- uri : this . getActiveSelectionUri ( scope , manager , project ) ,
659- new : newEnv ,
660- old : oldEnv ,
661- } ) ,
662- ) ;
661+ if ( this . isSameEnvironment ( oldEnv , newEnv ) || ! this . commitSelectionOperation ( key , operation ) ) {
662+ return ;
663663 }
664+ this . _activeSelection . set ( key , newEnv ) ;
665+ setImmediate ( ( ) =>
666+ this . _onDidChangeActiveEnvironment . fire ( {
667+ uri : this . getActiveSelectionUri ( scope , manager , project ) ,
668+ new : newEnv ,
669+ old : oldEnv ,
670+ } ) ,
671+ ) ;
664672 }
665673
666674 getLastKnownEnvironment ( scope : GetEnvironmentScope ) : PythonEnvironment | undefined {
@@ -694,13 +702,32 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
694702 return `inline-script:${ normalizePath ( scope . fsPath ) } ` ;
695703 }
696704
697- private clearInlineActiveSelection ( scope : Uri , manager : InternalEnvironmentManager ) : void {
698- if ( manager . id === INLINE_SCRIPT_MANAGER_ID ) {
705+ private beginPendingSelection ( scope : Uri , manager : InternalEnvironmentManager ) : PendingEnvironmentSelection {
706+ const project = this . pm . get ( scope ) ;
707+ const key = this . getActiveSelectionKey ( scope , manager , project ) ;
708+ return {
709+ scope,
710+ project,
711+ key,
712+ operation : this . beginSelectionOperation ( key ) ,
713+ inlineClearOperation :
714+ manager . id === INLINE_SCRIPT_MANAGER_ID
715+ ? undefined
716+ : this . beginSelectionOperation ( this . getInlineScriptSelectionKey ( scope ) ) ,
717+ } ;
718+ }
719+
720+ private clearInlineActiveSelection (
721+ scope : Uri ,
722+ manager : InternalEnvironmentManager ,
723+ operation : number | undefined ,
724+ ) : void {
725+ if ( manager . id === INLINE_SCRIPT_MANAGER_ID || operation === undefined ) {
699726 return ;
700727 }
701728 const key = this . getInlineScriptSelectionKey ( scope ) ;
702- if ( this . _activeSelection . delete ( key ) ) {
703- this . bumpSelectionRevision ( key ) ;
729+ if ( this . commitSelectionOperation ( key , operation ) ) {
730+ this . _activeSelection . delete ( key ) ;
704731 }
705732 }
706733
@@ -716,10 +743,33 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
716743 ) ;
717744 }
718745
719- private bumpSelectionRevision ( key : string ) : number {
720- const revision = ( this . _selectionRevisions . get ( key ) ?? 0 ) + 1 ;
721- this . _selectionRevisions . set ( key , revision ) ;
722- return revision ;
746+ private beginSelectionOperation ( key : string ) : number {
747+ const operation = ( this . _selectionOperationCounters . get ( key ) ?? 0 ) + 1 ;
748+ this . _selectionOperationCounters . set ( key , operation ) ;
749+ return operation ;
750+ }
751+
752+ private commitSelectionOperation ( key : string , operation : number ) : boolean {
753+ if ( ( this . _selectionRevisions . get ( key ) ?? 0 ) > operation ) {
754+ return false ;
755+ }
756+ this . _selectionRevisions . set ( key , operation ) ;
757+ return true ;
758+ }
759+
760+ private isSameEnvironment (
761+ first : PythonEnvironment | undefined ,
762+ second : PythonEnvironment | undefined ,
763+ ) : boolean {
764+ if ( first === second ) {
765+ return true ;
766+ }
767+ if ( ! first || ! second || first . envId . managerId !== second . envId . managerId ) {
768+ return false ;
769+ }
770+ return first . envId . managerId === INLINE_SCRIPT_MANAGER_ID
771+ ? normalizePath ( first . environmentPath . fsPath ) === normalizePath ( second . environmentPath . fsPath )
772+ : first . envId . id === second . envId . id ;
723773 }
724774
725775 getProjectEnvManagers ( uris : Uri [ ] ) : InternalEnvironmentManager [ ] {
@@ -733,3 +783,11 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
733783 return projectEnvManagers ;
734784 }
735785}
786+
787+ interface PendingEnvironmentSelection {
788+ readonly scope : Uri ;
789+ readonly project : PythonProject | undefined ;
790+ readonly key : string ;
791+ readonly operation : number ;
792+ readonly inlineClearOperation : number | undefined ;
793+ }
0 commit comments