File: InlineRename\UI\InlineRenameAdornmentManager.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_5itf1ngo_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures.Wpf)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.InlineRename;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.EditorFeatures.Lightup;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Telemetry;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename
{
    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;
        private readonly IThreadingContext _threadingContext;
#pragma warning disable CS0618 // Editor team use Obsolete attribute to mark potential changing API
        private readonly Lazy<ISmartRenameSessionFactoryWrapper>? _smartRenameSessionFactory;
#pragma warning restore CS0618
 
        private readonly IAdornmentLayer _adornmentLayer;
 
        private static readonly ConditionalWeakTable<InlineRenameSession, object> s_createdViewModels =
            new ConditionalWeakTable<InlineRenameSession, object>();
 
        public InlineRenameAdornmentManager(
            InlineRenameService renameService,
            IEditorFormatMapService editorFormatMapService,
            IInlineRenameColorUpdater? dashboardColorUpdater,
            IWpfTextView textView,
            IGlobalOptionService globalOptionService,
            IWpfThemeService? themeService,
            IAsyncQuickInfoBroker asyncQuickInfoBroker,
            IAsynchronousOperationListenerProvider listenerProvider,
            IThreadingContext threadingContext,
#pragma warning disable CS0618  // Editor team use Obsolete attribute to mark potential changing API
            Lazy<ISmartRenameSessionFactoryWrapper>? smartRenameSessionFactory)
#pragma warning restore CS0618
        {
            _renameService = renameService;
            _editorFormatMapService = editorFormatMapService;
            _dashboardColorUpdater = dashboardColorUpdater;
            _textView = textView;
            _globalOptionService = globalOptionService;
            _themeService = themeService;
            _asyncQuickInfoBroker = asyncQuickInfoBroker;
            _listenerProvider = listenerProvider;
            _adornmentLayer = textView.GetAdornmentLayer(InlineRenameAdornmentProvider.AdornmentLayerName);
            _threadingContext = threadingContext;
            _smartRenameSessionFactory = smartRenameSessionFactory;
 
            _renameService.ActiveSessionChanged += OnActiveSessionChanged;
            _textView.Closed += OnTextViewClosed;
 
            UpdateAdornments();
        }
 
        public void Dispose()
        {
            _renameService.ActiveSessionChanged -= OnActiveSessionChanged;
            _textView.Closed -= OnTextViewClosed;
        }
 
        private void OnTextViewClosed(object sender, EventArgs e)
            => Dispose();
 
        private void OnActiveSessionChanged(object sender, EventArgs e)
            => UpdateAdornments();
 
        private void UpdateAdornments()
        {
            _adornmentLayer.RemoveAllAdornments();
 
            if (_renameService.ActiveSession != null &&
                ViewIncludesBufferFromWorkspace(_textView, _renameService.ActiveSession.Workspace))
            {
                _dashboardColorUpdater?.UpdateColors();
 
                var adornment = GetAdornment();
 
                if (adornment is null)
                {
                    return;
                }
 
                _themeService?.ApplyThemeToElement(adornment);
 
                _adornmentLayer.AddAdornment(
                    AdornmentPositioningBehavior.ViewportRelative,
                    null, // Set no visual span because we don't want the editor to automatically remove the adornment if the overlapping span changes
                    tag: null,
                    adornment,
                    (tag, adornment) => ((InlineRenameAdornment)adornment).Dispose());
            }
        }
 
        private InlineRenameAdornment? GetAdornment()
        {
            if (_renameService.ActiveSession is null)
            {
                return null;
            }
 
            var useInlineAdornment = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.UseInlineAdornment);
            LogAdornmentChoice(useInlineAdornment);
            if (useInlineAdornment)
            {
                if (!_textView.HasAggregateFocus)
                {
                    // For the rename flyout, the adornment is dismissed on focus lost. There's
                    // no need to keep an adornment on every textview for show/hide behaviors
                    return null;
                }
 
                // Get the active selection to make sure the rename text is selected in the same way
                var originalSpan = _renameService.ActiveSession.TriggerSpan;
                var selectionSpan = _textView.Selection.SelectedSpans.First();
 
                var start = selectionSpan.IsEmpty
                    ? 0
                    : selectionSpan.Start - originalSpan.Start; // The length from the identifier to the start of selection
 
                var length = selectionSpan.IsEmpty
                    ? originalSpan.Length
                    : selectionSpan.Length;
 
                var identifierSelection = new TextSpan(start, length);
 
                var adornment = new RenameFlyout(
                    (RenameFlyoutViewModel)s_createdViewModels.GetValue(
                        _renameService.ActiveSession,
                        session => new RenameFlyoutViewModel(session,
                            identifierSelection,
                            registerOleComponent: true,
                            _globalOptionService,
                            _threadingContext,
                            _listenerProvider,
                            _smartRenameSessionFactory)),
                    _textView,
                    _themeService,
                    _asyncQuickInfoBroker,
                    _editorFormatMapService,
                    _threadingContext,
                    _listenerProvider);
 
                return adornment;
            }
            else
            {
                var newAdornment = new RenameDashboard(
                    (RenameDashboardViewModel)s_createdViewModels.GetValue(_renameService.ActiveSession, session => new RenameDashboardViewModel(session, _threadingContext, _textView)),
                    _editorFormatMapService,
                    _textView);
 
                return newAdornment;
            }
        }
 
        private static bool ViewIncludesBufferFromWorkspace(IWpfTextView textView, Workspace workspace)
        {
            return textView.BufferGraph.GetTextBuffers(b => GetWorkspace(b.AsTextContainer()) == workspace)
                                       .Any();
        }
 
        private static Workspace? GetWorkspace(SourceTextContainer textContainer)
        {
            Workspace.TryGetWorkspace(textContainer, out var workspace);
            return workspace;
        }
 
        private static void LogAdornmentChoice(bool useInlineAdornment)
        {
            TelemetryLogging.Log(FunctionId.InlineRenameAdornmentChoice, KeyValueLogMessage.Create(m =>
            {
                m[nameof(useInlineAdornment)] = useInlineAdornment;
            }));
        }
    }
}