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

Commit 32d2805

Browse files
authored
Merge pull request #1510 from github/refactor/convert-VSGitExt-to-service
Expose IVSGitExt as async VS service
2 parents ebbc6a6 + 0eadf70 commit 32d2805

File tree

8 files changed

+153
-114
lines changed

8 files changed

+153
-114
lines changed

src/GitHub.Exports/Services/IVSUIContextFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ public VSUIContextChangedEventArgs(bool activated)
2020
public interface IVSUIContext
2121
{
2222
bool IsActive { get; }
23-
event EventHandler<VSUIContextChangedEventArgs> UIContextChanged;
23+
void WhenActivated(Action action);
2424
}
2525
}

src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Windows;
32
using System.Threading;
43
using System.Runtime.InteropServices;
54
using GitHub.Services;
@@ -22,13 +21,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
2221
{
2322
var usageTracker = (IUsageTracker)await GetServiceAsync(typeof(IUsageTracker));
2423
var serviceProvider = (IGitHubServiceProvider)await GetServiceAsync(typeof(IGitHubServiceProvider));
24+
var gitExt = (IVSGitExt)await GetServiceAsync(typeof(IVSGitExt));
2525

26-
// NOTE: ThreadingHelper.SwitchToMainThreadAsync() doesn't return until a solution is loaded. Using Dispatcher.Invoke instead.
27-
Application.Current.Dispatcher.Invoke(() =>
28-
{
29-
var gitExt = serviceProvider.GetService<IVSGitExt>();
30-
new PullRequestStatusBarManager(gitExt, usageTracker, serviceProvider);
31-
});
26+
new PullRequestStatusBarManager(gitExt, usageTracker, serviceProvider);
3227
}
3328
}
3429
}
Lines changed: 30 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq;
3+
using System.Threading.Tasks;
34
using System.Collections.Generic;
45
using System.ComponentModel.Composition;
56
using GitHub.Models;
@@ -8,91 +9,67 @@
89
using GitHub.TeamFoundation.Services;
910
using Serilog;
1011
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
11-
using Task = System.Threading.Tasks.Task;
1212

1313
namespace GitHub.VisualStudio.Base
1414
{
1515
/// <summary>
1616
/// This service acts as an always available version of <see cref="IGitExt"/>.
1717
/// </summary>
18-
[Export(typeof(IVSGitExt))]
19-
[PartCreationPolicy(CreationPolicy.Shared)]
18+
/// <remarks>
19+
/// Initialization for this service will be done asynchronously and the <see cref="IGitExt" /> service will be
20+
/// retrieved using <see cref="GetServiceAsync" />. This means the service can be constructed and subscribed to from a background thread.
21+
/// </remarks>
2022
public class VSGitExt : IVSGitExt
2123
{
2224
static readonly ILogger log = LogManager.ForContext<VSGitExt>();
2325

24-
readonly IGitHubServiceProvider serviceProvider;
25-
readonly IVSUIContext context;
26+
readonly Func<Type, Task<object>> getServiceAsync;
2627
readonly ILocalRepositoryModelFactory repositoryFactory;
2728
readonly object refreshLock = new object();
2829

2930
IGitExt gitService;
3031
IReadOnlyList<ILocalRepositoryModel> activeRepositories;
3132

3233
[ImportingConstructor]
33-
public VSGitExt(IGitHubServiceProvider serviceProvider)
34-
: this(serviceProvider, new VSUIContextFactory(), new LocalRepositoryModelFactory())
34+
public VSGitExt(Func<Type, Task<object>> getServiceAsync)
35+
: this(getServiceAsync, new VSUIContextFactory(), new LocalRepositoryModelFactory())
3536
{
3637
}
3738

38-
public VSGitExt(IGitHubServiceProvider serviceProvider, IVSUIContextFactory factory, ILocalRepositoryModelFactory repositoryFactory)
39+
public VSGitExt(Func<Type, Task<object>> getServiceAsync, IVSUIContextFactory factory, ILocalRepositoryModelFactory repositoryFactory)
3940
{
40-
this.serviceProvider = serviceProvider;
41+
this.getServiceAsync = getServiceAsync;
4142
this.repositoryFactory = repositoryFactory;
4243

43-
// The IGitExt service isn't available when a TFS based solution is opened directly.
44-
// It will become available when moving to a Git based solution (cause a UIContext event to fire).
45-
context = factory.GetUIContext(new Guid(Guids.GitSccProviderId));
46-
47-
// Start with empty array until we have a change to initialize.
44+
// Start with empty array until we have a chance to initialize.
4845
ActiveRepositories = Array.Empty<ILocalRepositoryModel>();
4946

50-
if (context.IsActive && TryInitialize())
51-
{
52-
// Refresh ActiveRepositories on background thread so we don't delay startup.
53-
InitializeTask = Task.Run(() => RefreshActiveRepositories());
54-
}
55-
else
56-
{
57-
// If we're not in the UIContext or TryInitialize fails, have another go when the UIContext changes.
58-
context.UIContextChanged += ContextChanged;
59-
log.Debug("VSGitExt will be initialized later");
60-
InitializeTask = Task.CompletedTask;
61-
}
47+
// The IGitExt service isn't available when a TFS based solution is opened directly.
48+
// It will become available when moving to a Git based solution (and cause a UIContext event to fire).
49+
var context = factory.GetUIContext(new Guid(Guids.GitSccProviderId));
50+
context.WhenActivated(() => Initialize());
6251
}
6352

64-
void ContextChanged(object sender, VSUIContextChangedEventArgs e)
53+
void Initialize()
6554
{
66-
// If we're in the UIContext and TryInitialize succeeds, we can stop listening for events.
67-
// NOTE: this event can fire with UIContext=true in a TFS solution (not just Git).
68-
if (e.Activated && TryInitialize())
55+
PendingTasks = getServiceAsync(typeof(IGitExt)).ContinueWith(t =>
6956
{
70-
// Refresh ActiveRepositories on background thread so we don't delay UI context change.
71-
InitializeTask = Task.Run(() => RefreshActiveRepositories());
72-
context.UIContextChanged -= ContextChanged;
73-
log.Debug("Initialized VSGitExt on UIContextChanged");
74-
}
75-
}
57+
gitService = (IGitExt)t.Result;
58+
if (gitService == null)
59+
{
60+
log.Error("Couldn't find IGitExt service");
61+
return;
62+
}
7663

77-
bool TryInitialize()
78-
{
79-
gitService = serviceProvider.GetService<IGitExt>();
80-
if (gitService != null)
81-
{
64+
RefreshActiveRepositories();
8265
gitService.PropertyChanged += (s, e) =>
8366
{
8467
if (e.PropertyName == nameof(gitService.ActiveRepositories))
8568
{
8669
RefreshActiveRepositories();
8770
}
8871
};
89-
90-
log.Debug("Found IGitExt service and initialized VSGitExt");
91-
return true;
92-
}
93-
94-
log.Error("Couldn't find IGitExt service");
95-
return false;
72+
}, TaskScheduler.Default);
9673
}
9774

9875
void RefreshActiveRepositories()
@@ -104,7 +81,7 @@ void RefreshActiveRepositories()
10481
log.Debug(
10582
"IGitExt.ActiveRepositories (#{Id}) returned {Repositories}",
10683
gitService.GetHashCode(),
107-
gitService?.ActiveRepositories.Select(x => x.RepositoryPath));
84+
gitService.ActiveRepositories.Select(x => x.RepositoryPath));
10885

10986
ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
11087
}
@@ -136,6 +113,9 @@ private set
136113

137114
public event Action ActiveRepositoriesChanged;
138115

139-
public Task InitializeTask { get; private set; }
116+
/// <summary>
117+
/// Tasks that are pending execution on the thread pool.
118+
/// </summary>
119+
public Task PendingTasks { get; private set; } = Task.CompletedTask;
140120
}
141121
}
Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.ComponentModel.Composition;
42
using Microsoft.VisualStudio.Shell;
53
using GitHub.Services;
64

@@ -17,36 +15,14 @@ public IVSUIContext GetUIContext(Guid contextGuid)
1715
class VSUIContext : IVSUIContext
1816
{
1917
readonly UIContext context;
20-
readonly Dictionary<EventHandler<VSUIContextChangedEventArgs>, EventHandler<UIContextChangedEventArgs>> handlers =
21-
new Dictionary<EventHandler<VSUIContextChangedEventArgs>, EventHandler<UIContextChangedEventArgs>>();
18+
2219
public VSUIContext(UIContext context)
2320
{
2421
this.context = context;
2522
}
2623

2724
public bool IsActive { get { return context.IsActive; } }
2825

29-
public event EventHandler<VSUIContextChangedEventArgs> UIContextChanged
30-
{
31-
add
32-
{
33-
EventHandler<UIContextChangedEventArgs> handler = null;
34-
if (!handlers.TryGetValue(value, out handler))
35-
{
36-
handler = (s, e) => value.Invoke(s, new VSUIContextChangedEventArgs(e.Activated));
37-
handlers.Add(value, handler);
38-
}
39-
context.UIContextChanged += handler;
40-
}
41-
remove
42-
{
43-
EventHandler<UIContextChangedEventArgs> handler = null;
44-
if (handlers.TryGetValue(value, out handler))
45-
{
46-
handlers.Remove(value);
47-
context.UIContextChanged -= handler;
48-
}
49-
}
50-
}
26+
public void WhenActivated(Action action) => context.WhenActivated(action);
5127
}
5228
}

src/GitHub.VisualStudio/GitHub.VisualStudio.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@
328328
<Compile Include="Services\UsageTracker.cs" />
329329
<Compile Include="Services\LoginManagerDispatcher.cs" />
330330
<Compile Include="Services\UsageTrackerDispatcher.cs" />
331+
<Compile Include="Services\VSGitExtFactory.cs" />
331332
<Compile Include="Settings\Constants.cs" />
332333
<Compile Include="Services\ConnectionManager.cs" />
333334
<Compile Include="Services\Program.cs" />
@@ -679,13 +680,15 @@
679680
<Private>True</Private>
680681
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX>
681682
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly>
683+
<Aliases>TF14</Aliases>
682684
</ProjectReference>
683685
<ProjectReference Include="..\GitHub.TeamFoundation.15\GitHub.TeamFoundation.15.csproj">
684686
<Project>{161dbf01-1dbf-4b00-8551-c5c00f26720e}</Project>
685687
<Name>GitHub.TeamFoundation.15</Name>
686688
<Private>True</Private>
687689
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX>
688690
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly>
691+
<Aliases>TF15</Aliases>
689692
</ProjectReference>
690693
<ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj">
691694
<Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project>

src/GitHub.VisualStudio/GitHubPackage.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.ComponentModel.Composition;
33
using System.Diagnostics;
4-
using System.Reflection;
54
using System.Runtime.InteropServices;
65
using System.Threading;
76
using System.Threading.Tasks;
@@ -113,6 +112,7 @@ public GHClient(IProgram program)
113112
[ProvideService(typeof(IGitHubServiceProvider), IsAsyncQueryable = true)]
114113
[ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)]
115114
[ProvideService(typeof(IUsageService), IsAsyncQueryable = true)]
115+
[ProvideService(typeof(IVSGitExt), IsAsyncQueryable = true)]
116116
[ProvideService(typeof(IGitHubToolWindowManager))]
117117
[Guid(ServiceProviderPackageId)]
118118
public sealed class ServiceProviderPackage : AsyncPackage, IServiceProviderPackage, IGitHubToolWindowManager
@@ -150,6 +150,7 @@ Version VSVersion
150150
protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
151151
{
152152
AddService(typeof(IGitHubServiceProvider), CreateService, true);
153+
AddService(typeof(IVSGitExt), CreateService, true);
153154
AddService(typeof(IUsageTracker), CreateService, true);
154155
AddService(typeof(IUsageService), CreateService, true);
155156
AddService(typeof(ILoginManager), CreateService, true);
@@ -258,6 +259,11 @@ async Task<object> CreateService(IAsyncServiceContainer container, CancellationT
258259
var serviceProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider;
259260
return new UsageTracker(serviceProvider, usageService);
260261
}
262+
else if (serviceType == typeof(IVSGitExt))
263+
{
264+
var vsVersion = ApplicationInfo.GetHostVersionInfo().FileMajorPart;
265+
return VSGitExtFactory.Create(vsVersion, this);
266+
}
261267
else if (serviceType == typeof(IGitHubToolWindowManager))
262268
{
263269
return this;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
extern alias TF14;
2+
extern alias TF15;
3+
4+
using System;
5+
using System.ComponentModel.Composition;
6+
using GitHub.Info;
7+
using GitHub.Logging;
8+
using Serilog;
9+
using Microsoft.VisualStudio.Shell;
10+
using VSGitExt14 = TF14.GitHub.VisualStudio.Base.VSGitExt;
11+
using VSGitExt15 = TF15.GitHub.VisualStudio.Base.VSGitExt;
12+
13+
namespace GitHub.Services
14+
{
15+
[PartCreationPolicy(CreationPolicy.Shared)]
16+
public class VSGitExtFactory
17+
{
18+
static readonly ILogger log = LogManager.ForContext<VSGitExtFactory>();
19+
20+
[ImportingConstructor]
21+
public VSGitExtFactory(IGitHubServiceProvider serviceProvider)
22+
{
23+
VSGitExt = serviceProvider.GetService<IVSGitExt>();
24+
}
25+
26+
public static IVSGitExt Create(int vsVersion, IAsyncServiceProvider sp)
27+
{
28+
switch (vsVersion)
29+
{
30+
case 14:
31+
return Create(() => new VSGitExt14(sp.GetServiceAsync));
32+
case 15:
33+
return Create(() => new VSGitExt15(sp.GetServiceAsync));
34+
default:
35+
log.Error("There is no IVSGitExt implementation for DTE version {Version}", vsVersion);
36+
return null;
37+
}
38+
}
39+
40+
// NOTE: We're being careful to only reference VSGitExt14 and VSGitExt15 from inside a lambda expression.
41+
// This ensures that only the type that's compatible with the running DTE version is loaded.
42+
static IVSGitExt Create(Func<IVSGitExt> factory) => factory.Invoke();
43+
44+
[Export]
45+
public IVSGitExt VSGitExt { get; }
46+
}
47+
}

0 commit comments

Comments
 (0)