Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Create and add MEF commands on background thread #1548

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions src/GitHub.InlineReviews/InlineReviewsPackage.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<ServiceProgressData> 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<INextInlineCommentCommand>(),
exports.GetExportedValue<IPreviousInlineCommentCommand>());
Expand Down
8 changes: 4 additions & 4 deletions src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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<PullRequestStatusBarManager>();

await JoinableTaskFactory.SwitchToMainThreadAsync();
barManager.StartShowingStatus();
Expand Down
68 changes: 37 additions & 31 deletions src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,21 +19,28 @@ namespace GitHub.InlineReviews.Services
/// <summary>
/// Manage the UI that shows the PR for the current branch.
/// </summary>
[Export(typeof(PullRequestStatusBarManager))]
public class PullRequestStatusBarManager
{
static readonly ILogger log = LogManager.ForContext<PullRequestStatusBarManager>();
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<IPullRequestSessionManager> pullRequestSessionManager;

[ImportingConstructor]
public PullRequestStatusBarManager(IUsageTracker usageTracker, IGitHubServiceProvider serviceProvider)
public PullRequestStatusBarManager(
IUsageTracker usageTracker,
IShowCurrentPullRequestCommand showCurrentPullRequestCommand,
Lazy<IPullRequestSessionManager> pullRequestSessionManager)
{
this.usageTracker = usageTracker;
this.serviceProvider = serviceProvider;
this.showCurrentPullRequestCommand = showCurrentPullRequestCommand;
this.pullRequestSessionManager = pullRequestSessionManager;
}

/// <summary>
Expand All @@ -46,8 +53,7 @@ public void StartShowingStatus()
{
try
{
pullRequestSessionManager = serviceProvider.GetService<IPullRequestSessionManager>();
pullRequestSessionManager.WhenAnyValue(x => x.CurrentSession)
pullRequestSessionManager.Value.WhenAnyValue(x => x.CurrentSession)
.Subscribe(x => RefreshCurrentSession());
}
catch (Exception e)
Expand All @@ -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<EnvDTE.DTE>();
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);
Expand Down Expand Up @@ -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();
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions src/GitHub.Services.Vssdk/AsyncMenuPackage.cs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs some (preferably gramatical) xmldoc comments. 😉

{
IVsUIShell vsUIShell;

sealed protected async override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> 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);
}
}
}
1 change: 1 addition & 0 deletions src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<Compile Include="..\common\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="AsyncMenuPackage.cs" />
<Compile Include="Commands\MenuCommandServiceExtensions.cs" />
<Compile Include="Commands\VsCommand.cs" />
<Compile Include="Commands\VsCommandBase.cs" />
Expand Down
33 changes: 11 additions & 22 deletions src/GitHub.VisualStudio/GitHubPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
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;
using GitHub.Services;
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;
Expand All @@ -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<GitHubPackage>();

protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> 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<IAddConnectionCommand>(),
exports.GetExportedValue<IBlameLinkCommand>(),
Expand All @@ -72,6 +53,14 @@ async Task InitializeMenus()
exports.GetExportedValue<IShowGitHubPaneCommand>());
}

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;
Expand Down