From c20e618b61efc1ae5569408e7dd797ddda0e1220 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 20 Mar 2018 21:58:04 +0000 Subject: [PATCH 1/6] Create MEF commands on background thread There is no need to do this on the Main thread. --- src/GitHub.InlineReviews/InlineReviewsPackage.cs | 9 ++++++--- src/GitHub.VisualStudio/GitHubPackage.cs | 12 +++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs index 24f9c105a4..7d06563566 100644 --- a/src/GitHub.InlineReviews/InlineReviewsPackage.cs +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -38,11 +38,14 @@ async Task InitializeMenus() var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; + var commands = new IVsCommandBase[] + { + exports.GetExportedValue(), + exports.GetExportedValue() + }; await JoinableTaskFactory.SwitchToMainThreadAsync(); - menuService.AddCommands( - exports.GetExportedValue(), - exports.GetExportedValue()); + menuService.AddCommands(commands); } } } diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 233ce50840..72f80b8040 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using GitHub.Api; using GitHub.Commands; -using GitHub.Helpers; using GitHub.Info; using GitHub.Exports; using GitHub.Logging; @@ -59,9 +58,8 @@ async Task InitializeMenus() var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - menuService.AddCommands( + var commands = new IVsCommandBase[] + { exports.GetExportedValue(), exports.GetExportedValue(), exports.GetExportedValue(), @@ -69,7 +67,11 @@ async Task InitializeMenus() exports.GetExportedValue(), exports.GetExportedValue(), exports.GetExportedValue(), - exports.GetExportedValue()); + exports.GetExportedValue() + }; + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + menuService.AddCommands(commands); } async Task EnsurePackageLoaded(Guid packageGuid) From 05274979782263b92a887b8ec41b7f148ec174e5 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 21 Mar 2018 10:20:54 +0000 Subject: [PATCH 2/6] Allow menuService.AddCommands to be called from a b/g thread --- .../InlineReviewsPackage.cs | 26 ++++++------------- src/GitHub.VisualStudio/GitHubPackage.cs | 21 +++++++-------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs index 7d06563566..d87584a44d 100644 --- a/src/GitHub.InlineReviews/InlineReviewsPackage.cs +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -8,7 +8,6 @@ using GitHub.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; using Task = System.Threading.Tasks.Task; namespace GitHub.InlineReviews @@ -28,24 +27,15 @@ protected override async Task InitializeAsync( var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; - // Avoid delays when there is ongoing UI activity. - // See: https://github.com/github/VisualStudio/issues/1537 - await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus); - } - - async Task InitializeMenus() - { - var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); - var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); - var exports = componentModel.DefaultExportProvider; - var commands = new IVsCommandBase[] - { + menuService.AddCommands( exports.GetExportedValue(), - exports.GetExportedValue() - }; - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - menuService.AddCommands(commands); + exports.GetExportedValue()); } + + // The IDesignerHost and ISelectionService services are requested by MenuCommandService.EnsureVerbs(). + // When called from a non-Main thread this would throw despite the fact these services don't exist. + // This override allows IMenuCommandService.AddCommands to be called form a background thread. + protected override object GetService(Type serviceType) + => (serviceType == typeof(ISelectionService) || serviceType == typeof(IDesignerHost)) ? null : base.GetService(serviceType); } } diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 72f80b8040..d28133ad8b 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -39,12 +39,15 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke LogVersionInformation(); await base.InitializeAsync(cancellationToken, progress); await GetServiceAsync(typeof(IUsageTracker)); - - // Avoid delays when there is ongoing UI activity. - // See: https://github.com/github/VisualStudio/issues/1537 - await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus); + await InitializeMenus(); } + // The IDesignerHost and ISelectionService services are requested by MenuCommandService.EnsureVerbs(). + // When called from a non-Main thread this would throw despite the fact these services don't exist. + // This override allows IMenuCommandService.AddCommands to be called form a background thread. + protected override object GetService(Type serviceType) + => (serviceType == typeof(ISelectionService) || serviceType == typeof(IDesignerHost)) ? null : base.GetService(serviceType); + void LogVersionInformation() { var packageVersion = ApplicationInfo.GetPackageVersion(this); @@ -58,8 +61,8 @@ async Task InitializeMenus() var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; - var commands = new IVsCommandBase[] - { + + menuService.AddCommands( exports.GetExportedValue(), exports.GetExportedValue(), exports.GetExportedValue(), @@ -67,11 +70,7 @@ async Task InitializeMenus() exports.GetExportedValue(), exports.GetExportedValue(), exports.GetExportedValue(), - exports.GetExportedValue() - }; - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - menuService.AddCommands(commands); + exports.GetExportedValue()); } async Task EnsurePackageLoaded(Guid packageGuid) From 266ec58ffd1aae3621524786f917d9ca8ae05fb9 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 21 Mar 2018 12:35:18 +0000 Subject: [PATCH 3/6] Construct PullRequestStatusBarManager using MEF on b/g thread Call the IShowCurrentPullRequestCommand directly rather than using DTE. --- .../PullRequestStatusBarPackage.cs | 8 +-- .../Services/PullRequestStatusBarManager.cs | 67 +++++-------------- 2 files changed, 19 insertions(+), 56 deletions(-) diff --git a/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs index 30cdf34150..55eeb756e6 100644 --- a/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs +++ b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs @@ -1,12 +1,12 @@ using System; using System.Threading; using System.Runtime.InteropServices; -using GitHub.Services; using GitHub.VisualStudio; using GitHub.InlineReviews.Services; using Microsoft.VisualStudio.Shell; using Task = System.Threading.Tasks.Task; using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.ComponentModelHost; namespace GitHub.InlineReviews { @@ -27,9 +27,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke async Task InitializeStatusBar() { - var usageTracker = (IUsageTracker)await GetServiceAsync(typeof(IUsageTracker)); - var serviceProvider = (IGitHubServiceProvider)await GetServiceAsync(typeof(IGitHubServiceProvider)); - var barManager = new PullRequestStatusBarManager(usageTracker, serviceProvider); + var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); + var exports = componentModel.DefaultExportProvider; + var barManager = exports.GetExportedValue(); await JoinableTaskFactory.SwitchToMainThreadAsync(); barManager.StartShowingStatus(); diff --git a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs index c013f1ce5b..84fd72346c 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -1,16 +1,14 @@ using System; using System.Windows; -using System.Windows.Input; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.ComponentModel.Composition; +using GitHub.Commands; using GitHub.InlineReviews.Views; using GitHub.InlineReviews.ViewModels; using GitHub.Services; -using GitHub.VisualStudio; using GitHub.Models; using GitHub.Logging; -using GitHub.Extensions; using Serilog; using ReactiveUI; @@ -19,21 +17,28 @@ namespace GitHub.InlineReviews.Services /// /// Manage the UI that shows the PR for the current branch. /// + [Export(typeof(PullRequestStatusBarManager))] public class PullRequestStatusBarManager { static readonly ILogger log = LogManager.ForContext(); const string StatusBarPartName = "PART_SccStatusBarHost"; readonly IUsageTracker usageTracker; - readonly IGitHubServiceProvider serviceProvider; + readonly IShowCurrentPullRequestCommand showCurrentPullRequestCommand; - IPullRequestSessionManager pullRequestSessionManager; + // More the moment this must be constructed on the main thread. + // TeamExplorerContext needs to retrieve DTE using GetService. + readonly Lazy pullRequestSessionManager; [ImportingConstructor] - public PullRequestStatusBarManager(IUsageTracker usageTracker, IGitHubServiceProvider serviceProvider) + public PullRequestStatusBarManager( + IUsageTracker usageTracker, + IShowCurrentPullRequestCommand showCurrentPullRequestCommand, + Lazy pullRequestSessionManager) { this.usageTracker = usageTracker; - this.serviceProvider = serviceProvider; + this.showCurrentPullRequestCommand = showCurrentPullRequestCommand; + this.pullRequestSessionManager = pullRequestSessionManager; } /// @@ -46,8 +51,7 @@ public void StartShowingStatus() { try { - pullRequestSessionManager = serviceProvider.GetService(); - pullRequestSessionManager.WhenAnyValue(x => x.CurrentSession) + pullRequestSessionManager.Value.WhenAnyValue(x => x.CurrentSession) .Subscribe(x => RefreshCurrentSession()); } catch (Exception e) @@ -58,16 +62,14 @@ public void StartShowingStatus() void RefreshCurrentSession() { - var pullRequest = pullRequestSessionManager.CurrentSession?.PullRequest; + var pullRequest = pullRequestSessionManager.Value.CurrentSession?.PullRequest; var viewModel = pullRequest != null ? CreatePullRequestStatusViewModel(pullRequest) : null; ShowStatus(viewModel); } PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestModel pullRequest) { - var dte = serviceProvider.TryGetService(); - var command = new RaisePullRequestCommand(dte, usageTracker); - var pullRequestStatusViewModel = new PullRequestStatusViewModel(command); + var pullRequestStatusViewModel = new PullRequestStatusViewModel(showCurrentPullRequestCommand); pullRequestStatusViewModel.Number = pullRequest.Number; pullRequestStatusViewModel.Title = pullRequest.Title; return pullRequestStatusViewModel; @@ -111,44 +113,5 @@ StatusBar FindSccStatusBar(Window mainWindow) var contentControl = mainWindow?.Template?.FindName(StatusBarPartName, mainWindow) as ContentControl; return contentControl?.Content as StatusBar; } - - class RaisePullRequestCommand : ICommand - { - readonly string guid = Guids.guidGitHubCmdSetString; - readonly int id = PkgCmdIDList.showCurrentPullRequestCommand; - - readonly EnvDTE.DTE dte; - readonly IUsageTracker usageTracker; - - internal RaisePullRequestCommand(EnvDTE.DTE dte, IUsageTracker usageTracker) - { - this.dte = dte; - this.usageTracker = usageTracker; - } - - public bool CanExecute(object parameter) => true; - - public void Execute(object parameter) - { - try - { - object customIn = null; - object customOut = null; - dte?.Commands.Raise(guid, id, ref customIn, ref customOut); - } - catch (Exception e) - { - log.Error(e, "Couldn't raise {Guid}:{ID}", guid, id); - } - - usageTracker.IncrementCounter(x => x.NumberOfShowCurrentPullRequest).Forget(); - } - - public event EventHandler CanExecuteChanged - { - add { } - remove { } - } - } } } From 5730008aca33278b90f14e714380cc7c817bbb0b Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 21 Mar 2018 13:03:44 +0000 Subject: [PATCH 4/6] Factor b/g loading menu code into AsyncMenuPackage --- .../InlineReviewsPackage.cs | 16 ++------ src/GitHub.Services.Vssdk/AsyncMenuPackage.cs | 41 +++++++++++++++++++ .../GitHub.Services.Vssdk.csproj | 1 + src/GitHub.VisualStudio/GitHubPackage.cs | 34 +++++---------- 4 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 src/GitHub.Services.Vssdk/AsyncMenuPackage.cs diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs index d87584a44d..b4741af9d8 100644 --- a/src/GitHub.InlineReviews/InlineReviewsPackage.cs +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -1,9 +1,8 @@ using System; -using System.ComponentModel.Design; using System.Runtime.InteropServices; -using System.Threading; using GitHub.Commands; using GitHub.InlineReviews.Views; +using GitHub.Services.Vssdk; using GitHub.Services.Vssdk.Commands; using GitHub.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; @@ -17,13 +16,10 @@ namespace GitHub.InlineReviews [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideToolWindow(typeof(PullRequestCommentsPane), DocumentLikeTool = true)] - public class InlineReviewsPackage : AsyncPackage + public class InlineReviewsPackage : AsyncMenuPackage { - protected override async Task InitializeAsync( - CancellationToken cancellationToken, - IProgress progress) + protected override async Task InitializeMenusAsync(OleMenuCommandService menuService) { - var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; @@ -31,11 +27,5 @@ protected override async Task InitializeAsync( exports.GetExportedValue(), exports.GetExportedValue()); } - - // The IDesignerHost and ISelectionService services are requested by MenuCommandService.EnsureVerbs(). - // When called from a non-Main thread this would throw despite the fact these services don't exist. - // This override allows IMenuCommandService.AddCommands to be called form a background thread. - protected override object GetService(Type serviceType) - => (serviceType == typeof(ISelectionService) || serviceType == typeof(IDesignerHost)) ? null : base.GetService(serviceType); } } diff --git a/src/GitHub.Services.Vssdk/AsyncMenuPackage.cs b/src/GitHub.Services.Vssdk/AsyncMenuPackage.cs new file mode 100644 index 0000000000..1b3230e255 --- /dev/null +++ b/src/GitHub.Services.Vssdk/AsyncMenuPackage.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.ComponentModel.Design; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services.Vssdk +{ + public abstract class AsyncMenuPackage : AsyncPackage + { + IVsUIShell vsUIShell; + + sealed protected async override Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + vsUIShell = await GetServiceAsync(typeof(SVsUIShell)) as IVsUIShell; + + var menuCommandService = (OleMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); + await InitializeMenusAsync(menuCommandService); + } + + protected abstract Task InitializeMenusAsync(OleMenuCommandService menuService); + + // The IDesignerHost, ISelectionService and IVsUIShell are requested by the MenuCommandService. + // This override allows IMenuCommandService.AddCommands to be called form a background thread. + protected override object GetService(Type serviceType) + { + if (serviceType == typeof(SVsUIShell)) + { + return vsUIShell; + } + + if (serviceType == typeof(ISelectionService) || serviceType == typeof(IDesignerHost)) + { + return null; + } + + return base.GetService(serviceType); + } + } +} diff --git a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj index 1bd5ab4904..59f3944c38 100644 --- a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj +++ b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj @@ -139,6 +139,7 @@ Properties\SolutionInfo.cs + diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index d28133ad8b..6fee2b11cd 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -2,7 +2,6 @@ using System.Windows; using System.Threading; using System.Threading.Tasks; -using System.ComponentModel.Design; using System.ComponentModel.Composition; using System.Runtime.InteropServices; using GitHub.Api; @@ -14,6 +13,7 @@ using GitHub.Services.Vssdk.Commands; using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio.UI; +using GitHub.Services.Vssdk; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; @@ -30,35 +30,15 @@ namespace GitHub.VisualStudio [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] [ProvideToolWindow(typeof(GitHubPane), Orientation = ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindSolutionExplorer)] [ProvideOptionPage(typeof(OptionsPage), "GitHub for Visual Studio", "General", 0, 0, supportsAutomation: true)] - public class GitHubPackage : AsyncPackage + public class GitHubPackage : AsyncMenuPackage { static readonly ILogger log = LogManager.ForContext(); - protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + protected async override Task InitializeMenusAsync(OleMenuCommandService menuService) { LogVersionInformation(); - await base.InitializeAsync(cancellationToken, progress); await GetServiceAsync(typeof(IUsageTracker)); - await InitializeMenus(); - } - - // The IDesignerHost and ISelectionService services are requested by MenuCommandService.EnsureVerbs(). - // When called from a non-Main thread this would throw despite the fact these services don't exist. - // This override allows IMenuCommandService.AddCommands to be called form a background thread. - protected override object GetService(Type serviceType) - => (serviceType == typeof(ISelectionService) || serviceType == typeof(IDesignerHost)) ? null : base.GetService(serviceType); - - void LogVersionInformation() - { - var packageVersion = ApplicationInfo.GetPackageVersion(this); - var hostVersionInfo = ApplicationInfo.GetHostVersionInfo(); - log.Information("Initializing GitHub Extension v{PackageVersion} in {$FileDescription} ({$ProductVersion})", - packageVersion, hostVersionInfo.FileDescription, hostVersionInfo.ProductVersion); - } - async Task InitializeMenus() - { - var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); var exports = componentModel.DefaultExportProvider; @@ -73,6 +53,14 @@ async Task InitializeMenus() exports.GetExportedValue()); } + void LogVersionInformation() + { + var packageVersion = ApplicationInfo.GetPackageVersion(this); + var hostVersionInfo = ApplicationInfo.GetHostVersionInfo(); + log.Information("Initializing GitHub Extension v{PackageVersion} in {$FileDescription} ({$ProductVersion})", + packageVersion, hostVersionInfo.FileDescription, hostVersionInfo.ProductVersion); + } + async Task EnsurePackageLoaded(Guid packageGuid) { var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; From ec469547bdf9c18a95aa17754400b9694323e314 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Wed, 21 Mar 2018 14:44:12 +0000 Subject: [PATCH 5/6] Restore missing metrics --- .../Services/PullRequestStatusBarManager.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs index 84fd72346c..34188850b9 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -1,5 +1,6 @@ using System; using System.Windows; +using System.Windows.Input; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.ComponentModel.Composition; @@ -9,6 +10,7 @@ using GitHub.Services; using GitHub.Models; using GitHub.Logging; +using GitHub.Extensions; using Serilog; using ReactiveUI; @@ -69,12 +71,17 @@ void RefreshCurrentSession() PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestModel pullRequest) { - var pullRequestStatusViewModel = new PullRequestStatusViewModel(showCurrentPullRequestCommand); + var trackingCommand = new UsageTrackingCommand(showCurrentPullRequestCommand, usageTracker); + var pullRequestStatusViewModel = new PullRequestStatusViewModel(trackingCommand); pullRequestStatusViewModel.Number = pullRequest.Number; pullRequestStatusViewModel.Title = pullRequest.Title; return pullRequestStatusViewModel; } + void IncrementNumberOfShowCurrentPullRequest() + { + } + void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) { var statusBar = FindSccStatusBar(Application.Current.MainWindow); @@ -113,5 +120,41 @@ StatusBar FindSccStatusBar(Window mainWindow) var contentControl = mainWindow?.Template?.FindName(StatusBarPartName, mainWindow) as ContentControl; return contentControl?.Content as StatusBar; } + + class UsageTrackingCommand : ICommand + { + readonly ICommand command; + readonly IUsageTracker usageTracker; + + internal UsageTrackingCommand(ICommand command, IUsageTracker usageTracker) + { + this.command = command; + this.usageTracker = usageTracker; + } + + public event EventHandler CanExecuteChanged + { + add + { + command.CanExecuteChanged += value; + } + + remove + { + command.CanExecuteChanged -= value; + } + } + + public bool CanExecute(object parameter) + { + return command.CanExecute(parameter); + } + + public void Execute(object parameter) + { + command.Execute(parameter); + usageTracker.IncrementCounter(x => x.NumberOfShowCurrentPullRequest).Forget(); + } + } } } From 48cb0cccd4ee579e442f445707e3d9ff1b84d95f Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Thu, 22 Mar 2018 10:58:13 +0000 Subject: [PATCH 6/6] Allow MEF to refresh its cache on a background thread --- src/GitHub.VisualStudio/GitHubPackage.cs | 3 +- src/GitHub.VisualStudio/UI/GitHubPane.cs | 43 +++++++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 6fee2b11cd..69ceb25d1f 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -136,7 +136,8 @@ public async Task ShowGitHubPane() ErrorHandler.Failed(frame.Show()); } - var viewModel = (IGitHubPaneViewModel)((FrameworkElement)pane.Content).DataContext; + var gitHubPane = (GitHubPane)pane; + var viewModel = (IGitHubPaneViewModel)((FrameworkElement)gitHubPane.View).DataContext; await viewModel.InitializeAsync(pane); return viewModel; } diff --git a/src/GitHub.VisualStudio/UI/GitHubPane.cs b/src/GitHub.VisualStudio/UI/GitHubPane.cs index df89bc39b4..ae929e0db0 100644 --- a/src/GitHub.VisualStudio/UI/GitHubPane.cs +++ b/src/GitHub.VisualStudio/UI/GitHubPane.cs @@ -4,15 +4,19 @@ using System.Reactive.Linq; using System.Runtime.InteropServices; using System.Windows; +using System.Windows.Controls; +using GitHub.Helpers; using GitHub.Extensions; using GitHub.Factories; -using GitHub.Models; using GitHub.Services; using GitHub.ViewModels; using GitHub.ViewModels.GitHubPane; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.ComponentModelHost; using ReactiveUI; +using Task = System.Threading.Tasks.Task; +using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; namespace GitHub.VisualStudio.UI { @@ -34,16 +38,17 @@ public class GitHubPane : ToolWindowPane, IServiceProviderAware bool initialized = false; IDisposable viewSubscription; IGitHubPaneViewModel viewModel; + ContentControl contentControl; - FrameworkElement View + public FrameworkElement View { - get { return Content as FrameworkElement; } + get { return contentControl.Content as FrameworkElement; } set { viewSubscription?.Dispose(); viewSubscription = null; - Content = value; + contentControl.Content = value; viewSubscription = value.WhenAnyValue(x => x.DataContext) .SelectMany(x => @@ -61,6 +66,7 @@ FrameworkElement View public GitHubPane() : base(null) { Caption = "GitHub"; + Content = contentControl = new ContentControl(); BitmapImageMoniker = new Microsoft.VisualStudio.Imaging.Interop.ImageMoniker() { @@ -83,17 +89,28 @@ public void Initialize(IServiceProvider serviceProvider) { if (!initialized) { - var provider = VisualStudio.Services.GitHubServiceProvider; - var teServiceHolder = provider.GetService(); - teServiceHolder.ServiceProvider = serviceProvider; + InitializeAsync(serviceProvider).Forget(); + } + } - var factory = provider.GetService(); - viewModel = provider.ExportProvider.GetExportedValue(); - viewModel.InitializeAsync(this).Forget(); + async Task InitializeAsync(IServiceProvider serviceProvider) + { + // Allow MEF to refresh its cache on a background thread so it isn't counted against us. + var asyncServiceProvider = (IAsyncServiceProvider)GetService(typeof(SAsyncServiceProvider)); + await asyncServiceProvider.GetServiceAsync(typeof(SComponentModel)); - View = factory.CreateView(); - View.DataContext = viewModel; - } + await ThreadingHelper.SwitchToMainThreadAsync(); + + var provider = VisualStudio.Services.GitHubServiceProvider; + var teServiceHolder = provider.GetService(); + teServiceHolder.ServiceProvider = serviceProvider; + + var factory = provider.GetService(); + viewModel = provider.ExportProvider.GetExportedValue(); + viewModel.InitializeAsync(this).Forget(); + + View = factory.CreateView(); + View.DataContext = viewModel; } [SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Justification = "WTF CA, I'm overriding!")]