@@ -64,6 +64,8 @@ public partial class PackageManagerControl : UserControl, IVsWindowSearch, IDisp
6464 // This tells the operation execution part that it needs to trigger a refresh when done.
6565 private bool _isRefreshRequired ;
6666 private bool _isExecutingAction ; // Signifies where an action is being executed. Should be updated in a coordinated fashion with IsEnabled
67+ private bool _projectUpdateOccurredDuringRestore ;
68+ private IVsNuGetProjectUpdateEvents _projectUpdateEvents ;
6769 private RestartRequestBar _restartBar ;
6870 private bool _missingPackageStatus ;
6971 private bool _loadedAndInitialized = false ;
@@ -207,7 +209,9 @@ private async ValueTask InitializeAsync(PackageManagerModel model, INuGetUILogge
207209 solutionManager . ProjectRemoved += OnProjectChanged ;
208210 solutionManager . ProjectUpdated += OnProjectUpdated ;
209211 solutionManager . ProjectRenamed += OnProjectRenamed ;
210- solutionManager . AfterNuGetCacheUpdated += OnNuGetCacheUpdated ;
212+ _projectUpdateEvents = await ServiceLocator . GetComponentModelServiceAsync < IVsNuGetProjectUpdateEvents > ( ) ;
213+ _projectUpdateEvents . ProjectUpdateFinished += OnProjectUpdateFinished ;
214+ _projectUpdateEvents . SolutionRestoreFinished += OnSolutionRestoreFinished ;
211215
212216 Model . Context . ProjectActionsExecuted += OnProjectActionsExecuted ;
213217
@@ -406,47 +410,77 @@ private async ValueTask RefreshProjectAfterActionAsync(TimeSpan timeSpan, IReadO
406410 }
407411 }
408412
409- private void OnNuGetCacheUpdated ( object sender , string e )
413+ private void OnProjectUpdateFinished ( string projectUniqueName , IReadOnlyList < string > updatedFiles )
410414 {
411415 var timeSpan = GetTimeSinceLastRefreshAndRestart ( ) ;
412- // Do not refresh if the UI is not visible. It will be refreshed later when the loaded event is called.
413- if ( IsVisible )
416+
417+ if ( Model . IsSolution )
414418 {
415- NuGetUIThreadHelper . JoinableTaskFactory
416- . RunAsync ( ( ) => SolutionManager_CacheUpdatedAsync ( timeSpan , e ) )
417- . PostOnFailure ( nameof ( PackageManagerControl ) , nameof ( OnNuGetCacheUpdated ) ) ;
419+ // Solution-level PM UI: record that a non-no-op project update occurred.
420+ // The actual refresh will happen in OnSolutionRestoreFinished.
421+ _projectUpdateOccurredDuringRestore = true ;
422+ return ;
418423 }
419- else
424+
425+ if ( ! IsVisible )
420426 {
421- EmitRefreshEvent ( timeSpan , RefreshOperationSource . CacheUpdated , RefreshOperationStatus . NoOp ) ;
427+ _isRefreshRequired = true ;
428+ EmitRefreshEvent ( timeSpan , RefreshOperationSource . RestoreCompleted , RefreshOperationStatus . NoOp ) ;
429+ return ;
422430 }
431+
432+ // Project-level PM UI: only refresh when the updated project matches the viewed project.
433+ NuGetUIThreadHelper . JoinableTaskFactory
434+ . RunAsync ( ( ) => ProjectUpdateFinishedAsync ( timeSpan , projectUniqueName ) )
435+ . PostOnFailure ( nameof ( PackageManagerControl ) , nameof ( OnProjectUpdateFinished ) ) ;
423436 }
424437
425- private async Task SolutionManager_CacheUpdatedAsync ( TimeSpan timeSpan , string eventProjectFullName )
438+ private async Task ProjectUpdateFinishedAsync ( TimeSpan timeSpan , string projectUniqueName )
426439 {
427- if ( Model . IsSolution )
440+ IProjectContextInfo project = Model . Context . Projects . First ( ) ;
441+ IProjectMetadataContextInfo projectMetadata = await project . GetMetadataAsync (
442+ Model . Context . ServiceBroker ,
443+ CancellationToken . None ) ;
444+
445+ if ( string . Equals ( projectMetadata . FullPath , projectUniqueName , StringComparison . OrdinalIgnoreCase ) )
428446 {
429- await RefreshWhenNotExecutingActionAsync ( RefreshOperationSource . CacheUpdated , timeSpan ) ;
447+ await RefreshWhenNotExecutingActionAsync ( RefreshOperationSource . RestoreCompleted , timeSpan ) ;
430448 }
431449 else
432450 {
433- // This is a project package manager, so there is one and only one project.
434- IProjectContextInfo project = Model . Context . Projects . First ( ) ;
435- IProjectMetadataContextInfo projectMetadata = await project . GetMetadataAsync (
436- Model . Context . ServiceBroker ,
437- CancellationToken . None ) ;
451+ EmitRefreshEvent ( timeSpan , RefreshOperationSource . RestoreCompleted , RefreshOperationStatus . NotApplicable ) ;
452+ }
453+ }
438454
439- // This ensures that we refresh the UI only if the event.project.FullName matches the NuGetProject.FullName.
440- // We also refresh the UI if projectFullPath is not present.
441- if ( projectMetadata . FullPath == eventProjectFullName )
442- {
443- await RefreshWhenNotExecutingActionAsync ( RefreshOperationSource . CacheUpdated , timeSpan ) ;
444- }
445- else
446- {
447- EmitRefreshEvent ( timeSpan , RefreshOperationSource . CacheUpdated , RefreshOperationStatus . NotApplicable ) ;
448- }
455+ private void OnSolutionRestoreFinished ( IReadOnlyList < string > projects )
456+ {
457+ var timeSpan = GetTimeSinceLastRefreshAndRestart ( ) ;
458+
459+ if ( ! Model . IsSolution )
460+ {
461+ // Project-level PM UI handles refresh via OnProjectUpdateFinished.
462+ return ;
463+ }
464+
465+ // Only refresh if at least one project had a non-no-op restore.
466+ if ( ! _projectUpdateOccurredDuringRestore )
467+ {
468+ EmitRefreshEvent ( timeSpan , RefreshOperationSource . RestoreCompleted , RefreshOperationStatus . NoOp ) ;
469+ return ;
470+ }
471+
472+ _projectUpdateOccurredDuringRestore = false ;
473+
474+ if ( ! IsVisible )
475+ {
476+ _isRefreshRequired = true ;
477+ EmitRefreshEvent ( timeSpan , RefreshOperationSource . RestoreCompleted , RefreshOperationStatus . NoOp ) ;
478+ return ;
449479 }
480+
481+ NuGetUIThreadHelper . JoinableTaskFactory
482+ . RunAsync ( async ( ) => await RefreshWhenNotExecutingActionAsync ( RefreshOperationSource . RestoreCompleted , timeSpan ) )
483+ . PostOnFailure ( nameof ( PackageManagerControl ) , nameof ( OnSolutionRestoreFinished ) ) ;
450484 }
451485
452486 private async ValueTask RefreshWhenNotExecutingActionAsync ( RefreshOperationSource source , TimeSpan timeSpanSinceLastRefresh )
@@ -542,6 +576,11 @@ await RunAndEmitRefreshAsync(async () =>
542576 } ,
543577 RefreshOperationSource . PackageManagerLoaded , timeSpan , sw ) ;
544578 }
579+ else if ( _isRefreshRequired )
580+ {
581+ _isRefreshRequired = false ;
582+ await RunAndEmitRefreshAsync ( async ( ) => await RefreshAsync ( ) , RefreshOperationSource . PackageManagerLoaded , timeSpan , sw ) ;
583+ }
545584 else
546585 {
547586 EmitRefreshEvent ( timeSpan , RefreshOperationSource . PackageManagerLoaded , RefreshOperationStatus . NoOp , isUIFiltering : false , 0 ) ;
@@ -1603,7 +1642,12 @@ private void CleanUp()
16031642 solutionManager . ProjectRemoved -= OnProjectChanged ;
16041643 solutionManager . ProjectUpdated -= OnProjectUpdated ;
16051644 solutionManager . ProjectRenamed -= OnProjectRenamed ;
1606- solutionManager . AfterNuGetCacheUpdated -= OnNuGetCacheUpdated ;
1645+
1646+ if ( _projectUpdateEvents != null )
1647+ {
1648+ _projectUpdateEvents . ProjectUpdateFinished -= OnProjectUpdateFinished ;
1649+ _projectUpdateEvents . SolutionRestoreFinished -= OnSolutionRestoreFinished ;
1650+ }
16071651
16081652 Model . Context . ProjectActionsExecuted -= OnProjectActionsExecuted ;
16091653
0 commit comments