diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs index 57eba210cef61..cdb27dabe4e7b 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs @@ -3,14 +3,18 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename { @@ -21,15 +25,23 @@ internal partial class RenameFlyout : InlineRenameAdornment { private readonly RenameFlyoutViewModel _viewModel; private readonly IWpfTextView _textView; - - public RenameFlyout(RenameFlyoutViewModel viewModel, IWpfTextView textView, IWpfThemeService? themeService) + private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker; + private readonly IAsynchronousOperationListener _listener; + + public RenameFlyout( + RenameFlyoutViewModel viewModel, + IWpfTextView textView, + IWpfThemeService? themeService, + IAsyncQuickInfoBroker asyncQuickInfoBroker, + IAsynchronousOperationListenerProvider listenerProvider) { DataContext = _viewModel = viewModel; _textView = textView; - + _asyncQuickInfoBroker = asyncQuickInfoBroker; _textView.LayoutChanged += TextView_LayoutChanged; _textView.ViewportHeightChanged += TextView_ViewPortChanged; _textView.ViewportWidthChanged += TextView_ViewPortChanged; + _listener = listenerProvider.GetListener(FeatureAttribute.InlineRenameFlyout); // On load focus the first tab target Loaded += (s, e) => @@ -50,6 +62,24 @@ public RenameFlyout(RenameFlyoutViewModel viewModel, IWpfTextView textView, IWpf Outline.BorderBrush = new SolidColorBrush(themeService.GetThemeColor(EnvironmentColors.AccentBorderColorKey)); Background = new SolidColorBrush(themeService.GetThemeColor(EnvironmentColors.ToolWindowBackgroundColorKey)); } + + // Dismiss any current tooltips. Note that this does not disable tooltips + // from showing up again, so if a user has the mouse unmoved another + // tooltip will pop up. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1611398 + // tracks when we can handle this with IFeaturesService in VS + var token = _listener.BeginAsyncOperation(nameof(DismissToolTipsAsync)); + _ = DismissToolTipsAsync().CompletesAsyncOperation(token); + } + + private async Task DismissToolTipsAsync() + { + var infoSession = _asyncQuickInfoBroker.GetSession(_textView); + if (infoSession is null) + { + return; + } + + await infoSession.DismissAsync().ConfigureAwait(false); } #pragma warning disable CA1822 // Mark members as static - used in xaml diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentManager.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentManager.cs index b114ab21c1d6e..521ea12a83a9f 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentManager.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentManager.cs @@ -9,7 +9,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.InlineRename; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; @@ -20,6 +22,8 @@ internal class InlineRenameAdornmentManager : IDisposable private readonly IWpfTextView _textView; private readonly IGlobalOptionService _globalOptionService; private readonly IWpfThemeService? _themeService; + private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; private readonly InlineRenameService _renameService; private readonly IEditorFormatMapService _editorFormatMapService; private readonly IInlineRenameColorUpdater? _dashboardColorUpdater; @@ -35,7 +39,9 @@ public InlineRenameAdornmentManager( IInlineRenameColorUpdater? dashboardColorUpdater, IWpfTextView textView, IGlobalOptionService globalOptionService, - IWpfThemeService? themeService) + IWpfThemeService? themeService, + IAsyncQuickInfoBroker asyncQuickInfoBroker, + IAsynchronousOperationListenerProvider listenerProvider) { _renameService = renameService; _editorFormatMapService = editorFormatMapService; @@ -43,6 +49,8 @@ public InlineRenameAdornmentManager( _textView = textView; _globalOptionService = globalOptionService; _themeService = themeService; + _asyncQuickInfoBroker = asyncQuickInfoBroker; + _listenerProvider = listenerProvider; _adornmentLayer = textView.GetAdornmentLayer(InlineRenameAdornmentProvider.AdornmentLayerName); _renameService.ActiveSessionChanged += OnActiveSessionChanged; @@ -124,7 +132,9 @@ private void UpdateAdornments() var adornment = new RenameFlyout( (RenameFlyoutViewModel)s_createdViewModels.GetValue(_renameService.ActiveSession, session => new RenameFlyoutViewModel(session, identifierSelection, registerOleComponent: true, _globalOptionService)), _textView, - _themeService); + _themeService, + _asyncQuickInfoBroker, + _listenerProvider); return adornment; } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentProvider.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentProvider.cs index d5f87f0e10c6e..897e618ad1d32 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/InlineRenameAdornmentProvider.cs @@ -9,6 +9,8 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; @@ -27,6 +29,8 @@ internal class InlineRenameAdornmentProvider : IWpfTextViewConnectionListener private readonly IInlineRenameColorUpdater? _dashboardColorUpdater; private readonly IWpfThemeService? _themeingService; private readonly IGlobalOptionService _globalOptionService; + private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; public const string AdornmentLayerName = "RoslynRenameDashboard"; [Export] @@ -47,19 +51,23 @@ public InlineRenameAdornmentProvider( IEditorFormatMapService editorFormatMapService, [Import(AllowDefault = true)] IInlineRenameColorUpdater? dashboardColorUpdater, [Import(AllowDefault = true)] IWpfThemeService? themeingService, - IGlobalOptionService globalOptionService) + IGlobalOptionService globalOptionService, + IAsyncQuickInfoBroker asyncQuickInfoBroker, + IAsynchronousOperationListenerProvider listenerProvider) { _renameService = renameService; _editorFormatMapService = editorFormatMapService; _dashboardColorUpdater = dashboardColorUpdater; _themeingService = themeingService; _globalOptionService = globalOptionService; + _asyncQuickInfoBroker = asyncQuickInfoBroker; + _listenerProvider = listenerProvider; } public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) { // Create it for the view if we don't already have one - textView.GetOrCreateAutoClosingProperty(v => new InlineRenameAdornmentManager(_renameService, _editorFormatMapService, _dashboardColorUpdater, v, _globalOptionService, _themeingService)); + textView.GetOrCreateAutoClosingProperty(v => new InlineRenameAdornmentManager(_renameService, _editorFormatMapService, _dashboardColorUpdater, v, _globalOptionService, _themeingService, _asyncQuickInfoBroker, _listenerProvider)); } public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.QuickInfoSource.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.QuickInfoSource.cs index e45ee3288845a..b168c90b7ee0f 100644 --- a/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.QuickInfoSource.cs +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.QuickInfoSource.cs @@ -24,6 +24,7 @@ using Roslyn.Utilities; using IntellisenseQuickInfoItem = Microsoft.VisualStudio.Language.Intellisense.QuickInfoItem; +using Microsoft.CodeAnalysis.Editor.InlineRename; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo { @@ -37,6 +38,7 @@ private sealed class QuickInfoSource : IAsyncQuickInfoSource private readonly IAsynchronousOperationListener _asyncListener; private readonly Lazy _streamingPresenter; private readonly IGlobalOptionService _globalOptions; + private readonly IInlineRenameService _inlineRenameService; public QuickInfoSource( ITextBuffer subjectBuffer, @@ -44,7 +46,8 @@ public QuickInfoSource( IUIThreadOperationExecutor operationExecutor, IAsynchronousOperationListener asyncListener, Lazy streamingPresenter, - IGlobalOptionService globalOptions) + IGlobalOptionService globalOptions, + IInlineRenameService inlineRenameService) { _subjectBuffer = subjectBuffer; _threadingContext = threadingContext; @@ -52,10 +55,17 @@ public QuickInfoSource( _asyncListener = asyncListener; _streamingPresenter = streamingPresenter; _globalOptions = globalOptions; + _inlineRenameService = inlineRenameService; } public async Task GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) { + // Until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1611398 is resolved we can't disable + // quickinfo in InlineRename. Instead, we return no quickinfo information while the adornment + // is being shown. This can be removed after IFeaturesService supports disabling quickinfo + if (_globalOptions.GetOption(InlineRenameUIOptions.UseInlineAdornment) && _inlineRenameService.ActiveSession is not null) + return null; + var triggerPoint = session.GetTriggerPoint(_subjectBuffer.CurrentSnapshot); if (!triggerPoint.HasValue) return null; diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.cs index cd9681af7641f..4bebe52cf7c30 100644 --- a/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.cs +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/QuickInfoSourceProvider.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.Composition; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.InlineRename; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; @@ -28,6 +29,7 @@ internal partial class QuickInfoSourceProvider : IAsyncQuickInfoSourceProvider private readonly Lazy _streamingPresenter; private readonly IAsynchronousOperationListener _listener; private readonly IGlobalOptionService _globalOptions; + private readonly IInlineRenameService _inlineRenameService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -36,13 +38,15 @@ public QuickInfoSourceProvider( IUIThreadOperationExecutor operationExecutor, IAsynchronousOperationListenerProvider listenerProvider, Lazy streamingPresenter, - IGlobalOptionService globalOptions) + IGlobalOptionService globalOptions, + IInlineRenameService inlineRenameService) { _threadingContext = threadingContext; _operationExecutor = operationExecutor; _streamingPresenter = streamingPresenter; _listener = listenerProvider.GetListener(FeatureAttribute.QuickInfo); _globalOptions = globalOptions; + _inlineRenameService = inlineRenameService; } public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) @@ -51,7 +55,7 @@ public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) return null; return new QuickInfoSource( - textBuffer, _threadingContext, _operationExecutor, _listener, _streamingPresenter, _globalOptions); + textBuffer, _threadingContext, _operationExecutor, _listener, _streamingPresenter, _globalOptions, _inlineRenameService); } } } diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index 6f43dfe329d1a..c1b6c34549a3f 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -9,6 +9,9 @@ Imports Microsoft.CodeAnalysis.Editor.InlineRename Imports Microsoft.CodeAnalysis.InlineRename Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Rename +Imports Microsoft.CodeAnalysis.[Shared].TestHooks +Imports Microsoft.VisualStudio.Language.Intellisense +Imports Microsoft.VisualStudio.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename <[UseExportProvider]> @@ -614,10 +617,15 @@ class D : B Assert.Equal(severity, model.Severity) End Using + Dim TestQuickInfoBroker = New TestQuickInfoBroker() + Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider)().Value + Using flyout = New RenameFlyout( New RenameFlyoutViewModel(DirectCast(sessionInfo.Session, InlineRenameSession), selectionSpan:=Nothing, registerOleComponent:=False, globalOptions), ' Don't registerOleComponent in tests, it requires OleComponentManagers that don't exist in our host textView:=cursorDocument.GetTextView(), - themeService:=Nothing) + themeService:=Nothing, + TestQuickInfoBroker, + listenerProvider) Await WaitForRename(workspace) @@ -629,6 +637,12 @@ class D : B If Not isRenameOverloadsEditable Then Assert.True(model.RenameOverloadsFlag) End If + + Dim waiter = listenerProvider.GetWaiter(FeatureAttribute.InlineRenameFlyout) + Await waiter.ExpeditedWaitAsync() + + Dim QuickInfoSession = DirectCast(TestQuickInfoBroker.GetSession(cursorDocument.GetTextView()), TestQuickInfoBroker.TestSession) + Assert.True(QuickInfoSession.Dismissed) End Using sessionInfo.Session.Cancel() @@ -704,4 +718,90 @@ class D : B End Using End Sub End Class + + Friend Class TestQuickInfoBroker + Implements IAsyncQuickInfoBroker + + Private ReadOnly Session As TestSession = New TestSession() + + Public Function IsQuickInfoActive(textView As VisualStudio.Text.Editor.ITextView) As Boolean Implements IAsyncQuickInfoBroker.IsQuickInfoActive + Return False + End Function + + Public Function TriggerQuickInfoAsync(textView As VisualStudio.Text.Editor.ITextView, Optional triggerPoint As VisualStudio.Text.ITrackingPoint = Nothing, Optional options As QuickInfoSessionOptions = QuickInfoSessionOptions.None, Optional cancellationToken As CancellationToken = Nothing) As Task(Of IAsyncQuickInfoSession) Implements IAsyncQuickInfoBroker.TriggerQuickInfoAsync + Throw New NotImplementedException() + End Function + + Public Function GetQuickInfoItemsAsync(textView As VisualStudio.Text.Editor.ITextView, triggerPoint As VisualStudio.Text.ITrackingPoint, cancellationToken As CancellationToken) As Task(Of QuickInfoItemsCollection) Implements IAsyncQuickInfoBroker.GetQuickInfoItemsAsync + Throw New NotImplementedException() + End Function + + Public Function GetSession(textView As VisualStudio.Text.Editor.ITextView) As IAsyncQuickInfoSession Implements IAsyncQuickInfoBroker.GetSession + Return Session + End Function + + Public Class TestSession + Implements IAsyncQuickInfoSession + + Public Dismissed As Boolean = False + + Public ReadOnly Property ApplicableToSpan As VisualStudio.Text.ITrackingSpan Implements IAsyncQuickInfoSession.ApplicableToSpan + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Content As IEnumerable(Of Object) Implements IAsyncQuickInfoSession.Content + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property HasInteractiveContent As Boolean Implements IAsyncQuickInfoSession.HasInteractiveContent + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Options As QuickInfoSessionOptions Implements IAsyncQuickInfoSession.Options + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property State As QuickInfoSessionState Implements IAsyncQuickInfoSession.State + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property TextView As VisualStudio.Text.Editor.ITextView Implements IAsyncQuickInfoSession.TextView + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property Properties As PropertyCollection Implements IPropertyOwner.Properties + Get + Throw New NotImplementedException() + End Get + End Property + + Public Event StateChanged As EventHandler(Of QuickInfoSessionStateChangedEventArgs) Implements IAsyncQuickInfoSession.StateChanged + + Public Function GetTriggerPoint(textBuffer As VisualStudio.Text.ITextBuffer) As VisualStudio.Text.ITrackingPoint Implements IAsyncQuickInfoSession.GetTriggerPoint + Throw New NotImplementedException() + End Function + + Public Function GetTriggerPoint(snapshot As VisualStudio.Text.ITextSnapshot) As VisualStudio.Text.SnapshotPoint? Implements IAsyncQuickInfoSession.GetTriggerPoint + Throw New NotImplementedException() + End Function + + Public Function DismissAsync() As Task Implements IAsyncQuickInfoSession.DismissAsync + Dismissed = True + + Return Task.CompletedTask + End Function + End Class + End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 3ad3d5acac97c..a56212aff28fe 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -32,6 +32,7 @@ internal static class FeatureAttribute public const string InlineDiagnostics = nameof(InlineDiagnostics); public const string InheritanceMargin = nameof(InheritanceMargin); public const string InlineHints = nameof(InlineHints); + public const string InlineRenameFlyout = nameof(InlineRenameFlyout); public const string InteractiveEvaluator = nameof(InteractiveEvaluator); public const string KeywordHighlighting = nameof(KeywordHighlighting); public const string LibraryManager = nameof(LibraryManager);