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

Commit 6fe4bf1

Browse files
authored
Merge pull request #1396 from github/feature/show-current-pr
[Feature] Show current PR on status bar
2 parents 9df7918 + 05e74b0 commit 6fe4bf1

File tree

24 files changed

+556
-145
lines changed

24 files changed

+556
-145
lines changed

src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
4949
readonly ReactiveCommand<Unit> refresh;
5050
readonly ReactiveCommand<Unit> showPullRequests;
5151
readonly ReactiveCommand<object> openInBrowser;
52-
readonly SemaphoreSlim initializing = new SemaphoreSlim(1);
53-
bool initialized;
52+
Task initializeTask;
5453
IViewModel content;
5554
ILocalRepositoryModel localRepository;
5655
string searchQuery;
@@ -201,39 +200,9 @@ public void Dispose()
201200
}
202201

203202
/// <inheritdoc/>
204-
public async Task InitializeAsync(IServiceProvider paneServiceProvider)
203+
public Task InitializeAsync(IServiceProvider paneServiceProvider)
205204
{
206-
await initializing.WaitAsync();
207-
if (initialized) return;
208-
209-
try
210-
{
211-
await UpdateContent(teamExplorerContext.ActiveRepository);
212-
teamExplorerContext.WhenAnyValue(x => x.ActiveRepository)
213-
.Skip(1)
214-
.ObserveOn(RxApp.MainThreadScheduler)
215-
.Subscribe(x => UpdateContent(x).Forget());
216-
217-
connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget();
218-
219-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests);
220-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.backCommand, navigator.NavigateBack);
221-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.forwardCommand, navigator.NavigateForward);
222-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.refreshCommand, refresh);
223-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.githubCommand, openInBrowser);
224-
225-
paneServiceProvider.AddCommandHandler(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.helpCommand,
226-
(_, __) =>
227-
{
228-
browser.OpenUrl(new Uri(GitHubUrls.Documentation));
229-
usageTracker.IncrementCounter(x => x.NumberOfGitHubPaneHelpClicks).Forget();
230-
});
231-
}
232-
finally
233-
{
234-
initialized = true;
235-
initializing.Release();
236-
}
205+
return initializeTask = initializeTask ?? CreateInitializeTask(paneServiceProvider);
237206
}
238207

239208
/// <inheritdoc/>
@@ -307,6 +276,30 @@ public Task ShowPullRequest(string owner, string repo, int number)
307276
x => x.RemoteRepositoryOwner == owner && x.LocalRepository.Name == repo && x.Number == number);
308277
}
309278

279+
async Task CreateInitializeTask(IServiceProvider paneServiceProvider)
280+
{
281+
await UpdateContent(teamExplorerContext.ActiveRepository);
282+
teamExplorerContext.WhenAnyValue(x => x.ActiveRepository)
283+
.Skip(1)
284+
.ObserveOn(RxApp.MainThreadScheduler)
285+
.Subscribe(x => UpdateContent(x).Forget());
286+
287+
connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget();
288+
289+
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests);
290+
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.backCommand, navigator.NavigateBack);
291+
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.forwardCommand, navigator.NavigateForward);
292+
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.refreshCommand, refresh);
293+
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.githubCommand, openInBrowser);
294+
295+
paneServiceProvider.AddCommandHandler(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.helpCommand,
296+
(_, __) =>
297+
{
298+
browser.OpenUrl(new Uri(GitHubUrls.Documentation));
299+
usageTracker.IncrementCounter(x => x.NumberOfGitHubPaneHelpClicks).Forget();
300+
});
301+
}
302+
310303
OleMenuCommand BindNavigatorCommand<T>(IServiceProvider paneServiceProvider, int commandId, ReactiveCommand<T> command)
311304
{
312305
Guard.ArgumentNotNull(paneServiceProvider, nameof(paneServiceProvider));

src/GitHub.Exports/Models/UsageModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public struct UsageModel
4242
public int NumberOfPRDetailsNavigateToEditor { get; set; }
4343
public int NumberOfPRReviewDiffViewInlineCommentOpen { get; set; }
4444
public int NumberOfPRReviewDiffViewInlineCommentPost { get; set; }
45+
public int NumberOfShowCurrentPullRequest { get; set; }
4546

4647
public UsageModel Clone(bool includeWeekly, bool includeMonthly)
4748
{

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.Exports/Settings/Guids.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public static class Guids
1313
public const string StartPagePackageId = "3b764d23-faf7-486f-94c7-b3accc44a70e";
1414
public const string CodeContainerProviderId = "6CE146CB-EF57-4F2C-A93F-5BA685317660";
1515
public const string InlineReviewsPackageId = "248325BE-4A2D-4111-B122-E7D59BF73A35";
16+
public const string PullRequestStatusPackageId = "5121BEC6-1088-4553-8453-0DDC7C8E2238";
1617
public const string TeamExplorerWelcomeMessage = "C529627F-8AA6-4FDB-82EB-4BFB7DB753C3";
1718
public const string LoginManagerId = "7BA2071A-790A-4F95-BE4A-0EEAA5928AAF";
1819

src/GitHub.InlineReviews/GitHub.InlineReviews.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@
8888
<Compile Include="Glyph\GlyphMargin.cs" />
8989
<Compile Include="Glyph\GlyphMarginVisualManager.cs" />
9090
<Compile Include="Glyph\IGlyphFactory.cs" />
91+
<Compile Include="PullRequestStatusBarPackage.cs" />
9192
<Compile Include="InlineReviewsPackage.cs" />
9293
<Compile Include="Models\InlineCommentThreadModel.cs" />
9394
<Compile Include="Models\PullRequestSessionLiveFile.cs" />
9495
<Compile Include="Models\PullRequestSessionFile.cs" />
96+
<Compile Include="Services\PullRequestStatusBarManager.cs" />
9597
<Compile Include="Tags\MouseEnterAndLeaveEventRouter.cs" />
9698
<Compile Include="Peek\InlineCommentPeekableItem.cs" />
9799
<Compile Include="Peek\InlineCommentPeekableItemSource.cs" />
@@ -124,6 +126,7 @@
124126
<Compile Include="ViewModels\IPullRequestCommentsViewModel.cs" />
125127
<Compile Include="ViewModels\IssueCommentThreadViewModel.cs" />
126128
<Compile Include="ViewModels\PullRequestCommentsViewModel.cs" />
129+
<Compile Include="ViewModels\PullRequestStatusViewModel.cs" />
127130
<Compile Include="Views\DiffCommentThreadView.xaml.cs">
128131
<DependentUpon>DiffCommentThreadView.xaml</DependentUpon>
129132
</Compile>
@@ -161,6 +164,9 @@
161164
<Compile Include="Views\CommentView.xaml.cs">
162165
<DependentUpon>CommentView.xaml</DependentUpon>
163166
</Compile>
167+
<Compile Include="Views\PullRequestStatusView.xaml.cs">
168+
<DependentUpon>PullRequestStatusView.xaml</DependentUpon>
169+
</Compile>
164170
<Compile Include="VisualStudioExtensions.cs" />
165171
</ItemGroup>
166172
<ItemGroup>
@@ -443,6 +449,10 @@
443449
<Generator>MSBuild:Compile</Generator>
444450
<SubType>Designer</SubType>
445451
</Page>
452+
<Page Include="Views\PullRequestStatusView.xaml">
453+
<SubType>Designer</SubType>
454+
<Generator>MSBuild:Compile</Generator>
455+
</Page>
446456
</ItemGroup>
447457
<ItemGroup>
448458
<Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" />

src/GitHub.InlineReviews/InlineReviewsPackage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace GitHub.InlineReviews
1212
[Guid(Guids.InlineReviewsPackageId)]
1313
[ProvideAutoLoad(UIContextGuids80.SolutionExists)]
1414
[ProvideMenuResource("Menus.ctmenu", 1)]
15-
[ProvideToolWindow(typeof(PullRequestCommentsPane), DocumentLikeTool=true)]
15+
[ProvideToolWindow(typeof(PullRequestCommentsPane), DocumentLikeTool = true)]
1616
public class InlineReviewsPackage : Package
1717
{
1818
protected override void Initialize()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Threading;
3+
using System.Runtime.InteropServices;
4+
using GitHub.Services;
5+
using GitHub.VisualStudio;
6+
using GitHub.InlineReviews.Services;
7+
using Microsoft.VisualStudio.Shell;
8+
using Task = System.Threading.Tasks.Task;
9+
10+
namespace GitHub.InlineReviews
11+
{
12+
[Guid(Guids.PullRequestStatusPackageId)]
13+
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
14+
[ProvideAutoLoad(Guids.GitSccProviderId, PackageAutoLoadFlags.BackgroundLoad)]
15+
public class PullRequestStatusBarPackage : AsyncPackage
16+
{
17+
/// <summary>
18+
/// Initialize the PR status UI on Visual Studio's status bar.
19+
/// </summary>
20+
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
21+
{
22+
var usageTracker = (IUsageTracker)await GetServiceAsync(typeof(IUsageTracker));
23+
var serviceProvider = (IGitHubServiceProvider)await GetServiceAsync(typeof(IGitHubServiceProvider));
24+
var gitExt = (IVSGitExt)await GetServiceAsync(typeof(IVSGitExt));
25+
26+
new PullRequestStatusBarManager(gitExt, usageTracker, serviceProvider);
27+
}
28+
}
29+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Input;
4+
using System.Windows.Controls;
5+
using System.Windows.Controls.Primitives;
6+
using System.ComponentModel;
7+
using System.ComponentModel.Composition;
8+
using GitHub.InlineReviews.Views;
9+
using GitHub.InlineReviews.ViewModels;
10+
using GitHub.Services;
11+
using GitHub.VisualStudio;
12+
using GitHub.Models;
13+
using GitHub.Logging;
14+
using GitHub.Extensions;
15+
using Serilog;
16+
17+
namespace GitHub.InlineReviews.Services
18+
{
19+
public class PullRequestStatusBarManager
20+
{
21+
static readonly ILogger log = LogManager.ForContext<PullRequestStatusBarManager>();
22+
const string StatusBarPartName = "PART_SccStatusBarHost";
23+
24+
readonly IVSGitExt gitExt;
25+
readonly IUsageTracker usageTracker;
26+
readonly IGitHubServiceProvider serviceProvider;
27+
28+
IPullRequestSessionManager pullRequestSessionManager;
29+
30+
[ImportingConstructor]
31+
public PullRequestStatusBarManager(IVSGitExt gitExt, IUsageTracker usageTracker, IGitHubServiceProvider serviceProvider)
32+
{
33+
this.gitExt = gitExt;
34+
this.usageTracker = usageTracker;
35+
this.serviceProvider = serviceProvider;
36+
37+
OnActiveRepositoriesChanged();
38+
gitExt.ActiveRepositoriesChanged += OnActiveRepositoriesChanged;
39+
}
40+
41+
void OnActiveRepositoriesChanged()
42+
{
43+
if (gitExt.ActiveRepositories.Count > 0)
44+
{
45+
gitExt.ActiveRepositoriesChanged -= OnActiveRepositoriesChanged;
46+
Application.Current.Dispatcher.Invoke(() => StartShowingStatus());
47+
}
48+
}
49+
50+
void StartShowingStatus()
51+
{
52+
try
53+
{
54+
// Create just in time on Main thread.
55+
pullRequestSessionManager = serviceProvider.GetService<IPullRequestSessionManager>();
56+
57+
RefreshCurrentSession();
58+
pullRequestSessionManager.PropertyChanged += PullRequestSessionManager_PropertyChanged;
59+
}
60+
catch (Exception e)
61+
{
62+
log.Error(e, "Error initializing");
63+
}
64+
}
65+
66+
void PullRequestSessionManager_PropertyChanged(object sender, PropertyChangedEventArgs e)
67+
{
68+
if (e.PropertyName == nameof(PullRequestSessionManager.CurrentSession))
69+
{
70+
RefreshCurrentSession();
71+
}
72+
}
73+
74+
void RefreshCurrentSession()
75+
{
76+
var pullRequest = pullRequestSessionManager.CurrentSession?.PullRequest;
77+
var viewModel = pullRequest != null ? CreatePullRequestStatusViewModel(pullRequest) : null;
78+
ShowStatus(viewModel);
79+
}
80+
81+
PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestModel pullRequest)
82+
{
83+
var dte = serviceProvider.TryGetService<EnvDTE.DTE>();
84+
var command = new RaisePullRequestCommand(dte, usageTracker);
85+
var pullRequestStatusViewModel = new PullRequestStatusViewModel(command);
86+
pullRequestStatusViewModel.Number = pullRequest.Number;
87+
pullRequestStatusViewModel.Title = pullRequest.Title;
88+
return pullRequestStatusViewModel;
89+
}
90+
91+
void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null)
92+
{
93+
var statusBar = FindSccStatusBar(Application.Current.MainWindow);
94+
if (statusBar != null)
95+
{
96+
var githubStatusBar = Find<PullRequestStatusView>(statusBar);
97+
if (githubStatusBar != null)
98+
{
99+
// Replace to ensure status shows up.
100+
statusBar.Items.Remove(githubStatusBar);
101+
}
102+
103+
if (pullRequestStatusViewModel != null)
104+
{
105+
githubStatusBar = new PullRequestStatusView { DataContext = pullRequestStatusViewModel };
106+
statusBar.Items.Insert(0, githubStatusBar);
107+
}
108+
}
109+
}
110+
111+
static T Find<T>(StatusBar statusBar)
112+
{
113+
foreach (var item in statusBar.Items)
114+
{
115+
if (item is T)
116+
{
117+
return (T)item;
118+
}
119+
}
120+
121+
return default(T);
122+
}
123+
124+
StatusBar FindSccStatusBar(Window mainWindow)
125+
{
126+
var contentControl = mainWindow?.Template?.FindName(StatusBarPartName, mainWindow) as ContentControl;
127+
return contentControl?.Content as StatusBar;
128+
}
129+
130+
class RaisePullRequestCommand : ICommand
131+
{
132+
readonly string guid = Guids.guidGitHubCmdSetString;
133+
readonly int id = PkgCmdIDList.showCurrentPullRequestCommand;
134+
135+
readonly EnvDTE.DTE dte;
136+
readonly IUsageTracker usageTracker;
137+
138+
internal RaisePullRequestCommand(EnvDTE.DTE dte, IUsageTracker usageTracker)
139+
{
140+
this.dte = dte;
141+
this.usageTracker = usageTracker;
142+
}
143+
144+
public bool CanExecute(object parameter) => true;
145+
146+
public void Execute(object parameter)
147+
{
148+
try
149+
{
150+
object customIn = null;
151+
object customOut = null;
152+
dte?.Commands.Raise(guid, id, ref customIn, ref customOut);
153+
}
154+
catch (Exception e)
155+
{
156+
log.Error(e, "Couldn't raise {Guid}:{ID}", guid, id);
157+
}
158+
159+
usageTracker.IncrementCounter(x => x.NumberOfShowCurrentPullRequest).Forget();
160+
}
161+
162+
public event EventHandler CanExecuteChanged
163+
{
164+
add { }
165+
remove { }
166+
}
167+
}
168+
}
169+
}

0 commit comments

Comments
 (0)