diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs index 24f9c105a4..b4741af9d8 100644 --- a/src/GitHub.InlineReviews/InlineReviewsPackage.cs +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -1,14 +1,12 @@ 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; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; using Task = System.Threading.Tasks.Task; namespace GitHub.InlineReviews @@ -18,28 +16,13 @@ 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; - // 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; - - await JoinableTaskFactory.SwitchToMainThreadAsync(); menuService.AddCommands( exports.GetExportedValue(), exports.GetExportedValue()); 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..34188850b9 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -4,10 +4,10 @@ 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; @@ -19,21 +19,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 +53,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,21 +64,24 @@ 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 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); @@ -112,42 +121,39 @@ StatusBar FindSccStatusBar(Window mainWindow) return contentControl?.Content as StatusBar; } - class RaisePullRequestCommand : ICommand + class UsageTrackingCommand : ICommand { - readonly string guid = Guids.guidGitHubCmdSetString; - readonly int id = PkgCmdIDList.showCurrentPullRequestCommand; - - readonly EnvDTE.DTE dte; + readonly ICommand command; readonly IUsageTracker usageTracker; - internal RaisePullRequestCommand(EnvDTE.DTE dte, IUsageTracker usageTracker) + internal UsageTrackingCommand(ICommand command, IUsageTracker usageTracker) { - this.dte = dte; + this.command = command; this.usageTracker = usageTracker; } - public bool CanExecute(object parameter) => true; - - public void Execute(object parameter) + public event EventHandler CanExecuteChanged { - try + add { - object customIn = null; - object customOut = null; - dte?.Commands.Raise(guid, id, ref customIn, ref customOut); + command.CanExecuteChanged += value; } - catch (Exception e) + + remove { - log.Error(e, "Couldn't raise {Guid}:{ID}", guid, id); + command.CanExecuteChanged -= value; } + } - usageTracker.IncrementCounter(x => x.NumberOfShowCurrentPullRequest).Forget(); + public bool CanExecute(object parameter) + { + return command.CanExecute(parameter); } - public event EventHandler CanExecuteChanged + public void Execute(object parameter) { - add { } - remove { } + command.Execute(parameter); + usageTracker.IncrementCounter(x => x.NumberOfShowCurrentPullRequest).Forget(); } } } 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 233ce50840..69ceb25d1f 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -2,12 +2,10 @@ 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; using GitHub.Commands; -using GitHub.Helpers; using GitHub.Info; using GitHub.Exports; using GitHub.Logging; @@ -15,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; @@ -31,36 +30,18 @@ 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)); - // Avoid delays when there is ongoing UI activity. - // See: https://github.com/github/VisualStudio/issues/1537 - await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus); - } - - 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; - await JoinableTaskFactory.SwitchToMainThreadAsync(); menuService.AddCommands( exports.GetExportedValue(), exports.GetExportedValue(), @@ -72,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; @@ -147,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!")]