From 0765701a5be33c8011aa5915f3eb0815ee4d2930 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Thu, 18 Oct 2018 15:52:41 +0100 Subject: [PATCH 1/2] Add VsTippingService for callout notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a façade for the internal SVsTippingService on Visual Studio 2015 and 2017. --- .../Services/IVsTippingService.cs | 11 +++ .../GitHub.Services.Vssdk.csproj | 6 ++ .../Services/VsTippingService.cs | 70 +++++++++++++++++++ src/GitHub.VisualStudio/GitHubPackage.cs | 10 +++ 4 files changed, 97 insertions(+) create mode 100644 src/GitHub.Exports/Services/IVsTippingService.cs create mode 100644 src/GitHub.Services.Vssdk/Services/VsTippingService.cs diff --git a/src/GitHub.Exports/Services/IVsTippingService.cs b/src/GitHub.Exports/Services/IVsTippingService.cs new file mode 100644 index 0000000000..e7d04c41f2 --- /dev/null +++ b/src/GitHub.Exports/Services/IVsTippingService.cs @@ -0,0 +1,11 @@ +using System; +using System.Windows; + +namespace GitHub.Services +{ + public interface IVsTippingService + { + void RequestCalloutDisplay(Guid calloutId, string title, string message, + bool isPermanentlyDismissible, FrameworkElement targetElement, Guid vsCommandGroupId, uint vsCommandId); + } +} diff --git a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj index 47e9b32ae8..475bb8248d 100644 --- a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj +++ b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj @@ -15,4 +15,10 @@ + + + + + + diff --git a/src/GitHub.Services.Vssdk/Services/VsTippingService.cs b/src/GitHub.Services.Vssdk/Services/VsTippingService.cs new file mode 100644 index 0000000000..4b332cfa22 --- /dev/null +++ b/src/GitHub.Services.Vssdk/Services/VsTippingService.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; +using Microsoft; +using IServiceProvider = System.IServiceProvider; + +namespace GitHub.Services.Vssdk.Services +{ + public class VsTippingService : IVsTippingService + { + // This is the only supported ClientId + readonly Guid ClientId = new Guid("D5D3B674-05BB-4942-B8EC-C3D13B5BD6EE"); + + readonly IServiceProvider serviceProvider; + + public VsTippingService(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public void RequestCalloutDisplay(Guid calloutId, string title, string message, + bool isPermanentlyDismissible, FrameworkElement targetElement, + Guid vsCommandGroupId, uint vsCommandId) + { + var screenPoint = targetElement.PointToScreen(new Point(targetElement.ActualWidth / 2, 0)); + var point = new Microsoft.VisualStudio.OLE.Interop.POINT { x = (int)screenPoint.X, y = (int)screenPoint.Y }; + RequestCalloutDisplay(ClientId, calloutId, title, message, isPermanentlyDismissible, + point, vsCommandGroupId, vsCommandId); + } + + // Available on Visual Studio 2017 + void RequestCalloutDisplay(Guid clientId, Guid calloutId, string title, string message, + bool isPermanentlyDismissible, FrameworkElement targetElement, + Guid vsCommandGroupId, uint vsCommandId, object commandOption = null) + { + var tippingService = serviceProvider.GetService(typeof(SVsTippingService)); + Assumes.Present(tippingService); + var currentMethod = MethodBase.GetCurrentMethod(); + var parameterTypes = currentMethod.GetParameters().Select(p => p.ParameterType).ToArray(); + var method = tippingService.GetType().GetMethod(currentMethod.Name, parameterTypes); + var arguments = new object[] { clientId, calloutId, title, message, isPermanentlyDismissible, targetElement, + vsCommandGroupId, vsCommandId, commandOption }; + method.Invoke(tippingService, arguments); + } + + // Available on Visual Studio 2015 + void RequestCalloutDisplay(Guid clientId, Guid calloutId, string title, string message, bool isPermanentlyDismissible, + Microsoft.VisualStudio.OLE.Interop.POINT anchor, Guid vsCommandGroupId, uint vsCommandId) + { + var tippingService = serviceProvider.GetService(typeof(SVsTippingService)); + Assumes.Present(tippingService); + var currentMethod = MethodBase.GetCurrentMethod(); + var parameterTypes = currentMethod.GetParameters().Select(p => p.ParameterType).ToArray(); + var method = tippingService.GetType().GetMethod(currentMethod.Name, parameterTypes); + var arguments = new object[] { clientId, calloutId, title, message, isPermanentlyDismissible, anchor, + vsCommandGroupId, vsCommandId }; + method.Invoke(tippingService, arguments); + } + } + + [Guid("DCCC6A2B-F300-4DA1-92E1-8BF4A5BCA795")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [TypeIdentifier] + [ComImport] + public interface SVsTippingService + { + } +} diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 2f368e96c0..ab9a2ef5f0 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -13,6 +13,7 @@ using GitHub.Settings; using GitHub.VisualStudio.Commands; using GitHub.Services.Vssdk.Commands; +using GitHub.Services.Vssdk.Services; using GitHub.ViewModels.GitHubPane; using GitHub.VisualStudio.Settings; using GitHub.VisualStudio.UI; @@ -149,6 +150,9 @@ public ServiceProviderExports([Import(typeof(SVsServiceProvider))] IServiceProvi [ExportForVisualStudioProcess] public IPackageSettings PackageSettings => GetService(); + [ExportForVisualStudioProcess] + public IVsTippingService TippingService => GetService(); + T GetService() => (T)serviceProvider.GetService(typeof(T)); } @@ -160,6 +164,7 @@ public ServiceProviderExports([Import(typeof(SVsServiceProvider))] IServiceProvi [ProvideService(typeof(IUsageService), IsAsyncQueryable = true)] [ProvideService(typeof(IVSGitExt), IsAsyncQueryable = true)] [ProvideService(typeof(IGitHubToolWindowManager))] + [ProvideService(typeof(IVsTippingService))] [Guid(ServiceProviderPackageId)] public sealed class ServiceProviderPackage : AsyncPackage, IServiceProviderPackage, IGitHubToolWindowManager { @@ -175,6 +180,7 @@ protected override Task InitializeAsync(CancellationToken cancellationToken, IPr AddService(typeof(ILoginManager), CreateService, true); AddService(typeof(IGitHubToolWindowManager), CreateService, true); AddService(typeof(IPackageSettings), CreateService, true); + AddService(typeof(IVsTippingService), CreateService, true); return Task.CompletedTask; } @@ -289,6 +295,10 @@ async Task CreateService(IAsyncServiceContainer container, CancellationT var sp = new ServiceProvider(Services.Dte as Microsoft.VisualStudio.OLE.Interop.IServiceProvider); return new PackageSettings(sp); } + else if (serviceType == typeof(IVsTippingService)) + { + return new VsTippingService(this); + } // go the mef route else { From 79b684fa552127a962f03aa0f7c771c7b6eed619 Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Thu, 18 Oct 2018 15:53:08 +0100 Subject: [PATCH 2/2] Example: show tip when user changes GitHub repo --- .../Services/PullRequestStatusBarManager.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs index c6672b2847..355c3a83c1 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -16,6 +16,7 @@ using GitHub.Logging; using Serilog; using ReactiveUI; +using GitHub.VisualStudio; namespace GitHub.InlineReviews.Services { @@ -36,6 +37,7 @@ public class PullRequestStatusBarManager readonly Lazy pullRequestSessionManager; readonly Lazy teamExplorerContext; readonly Lazy connectionManager; + readonly Lazy tippingService; IDisposable currentSessionSubscription; @@ -46,7 +48,8 @@ public PullRequestStatusBarManager( IShowCurrentPullRequestCommand showCurrentPullRequestCommand, Lazy pullRequestSessionManager, Lazy teamExplorerContext, - Lazy connectionManager) + Lazy connectionManager, + Lazy tippingService) { this.openPullRequestsCommand = new UsageTrackingCommand(usageTracker, x => x.NumberOfStatusBarOpenPullRequestList, openPullRequestsCommand); @@ -56,6 +59,7 @@ public PullRequestStatusBarManager( this.pullRequestSessionManager = pullRequestSessionManager; this.teamExplorerContext = teamExplorerContext; this.connectionManager = connectionManager; + this.tippingService = tippingService; } /// @@ -97,7 +101,11 @@ async Task RefreshCurrentSession(ILocalRepositoryModel repository, IPullRequestS } var viewModel = CreatePullRequestStatusViewModel(session); - ShowStatus(viewModel); + var view = ShowStatus(viewModel); + if (view != null) + { + view.Loaded += (s, e) => ShowCallout(view, repository); + } } catch (Exception e) { @@ -105,6 +113,18 @@ async Task RefreshCurrentSession(ILocalRepositoryModel repository, IPullRequestS } } + void ShowCallout(FrameworkElement view, ILocalRepositoryModel repository) + { + var calloutId = new Guid("63b813cd-9292-4c0f-aa49-ebd888b791f9"); + var title = "GitHub repository opened"; + var message = repository.CloneUrl; + var isDismissable = true; + var commandSet = new Guid("E234E66E-BA64-4D71-B304-16F0A4C793F5"); + var commandId = (uint)0x4010; // View.TfsTeamExplorer + tippingService.Value.RequestCalloutDisplay(calloutId, title, message, + isDismissable, view, commandSet, commandId); + } + async Task IsDotComOrEnterpriseRepository(ILocalRepositoryModel repository) { var cloneUrl = repository?.CloneUrl; @@ -140,7 +160,7 @@ PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestSession return pullRequestStatusViewModel; } - void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) + PullRequestStatusView ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) { var statusBar = FindSccStatusBar(Application.Current.MainWindow); if (statusBar != null) @@ -156,8 +176,11 @@ void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) { githubStatusBar = new PullRequestStatusView { DataContext = pullRequestStatusViewModel }; statusBar.Items.Insert(0, githubStatusBar); + return githubStatusBar; } } + + return null; } static T Find(StatusBar statusBar)