File: RenameTracking\RenameTrackingTaggerProvider.cs
Web Access
Project: ..\..\..\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking
{
    /// <summary>
    /// Also known as "rename smart tag," this watches text changes in open buffers, determines
    /// whether they can be interpreted as an identifier rename, and if so displays a smart tag 
    /// that can perform a rename on that symbol. Each text buffer is tracked independently.
    /// </summary>
    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(RenameTrackingTag))]
    [TagType(typeof(IErrorTag))]
    [ContentType(ContentTypeNames.RoslynContentType)]
    [ContentType(ContentTypeNames.XamlContentType)]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal sealed partial class RenameTrackingTaggerProvider : ITaggerProvider
    {
        private readonly IThreadingContext _threadingContext;
        private readonly IAsynchronousOperationListener _asyncListener;
        private readonly IInlineRenameService _inlineRenameService;
        private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService;
        private readonly IGlobalOptionService _globalOptions;
 
        [ImportingConstructor]
        [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
        public RenameTrackingTaggerProvider(
            IThreadingContext threadingContext,
            IInlineRenameService inlineRenameService,
            IDiagnosticAnalyzerService diagnosticAnalyzerService,
            IGlobalOptionService globalOptions,
            IAsynchronousOperationListenerProvider listenerProvider)
        {
            _threadingContext = threadingContext;
            _inlineRenameService = inlineRenameService;
            _diagnosticAnalyzerService = diagnosticAnalyzerService;
            _globalOptions = globalOptions;
            _asyncListener = listenerProvider.GetListener(FeatureAttribute.RenameTracking);
        }
 
        public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
        {
            var stateMachine = buffer.Properties.GetOrCreateSingletonProperty(() => new StateMachine(_threadingContext, buffer, _inlineRenameService, _diagnosticAnalyzerService, _globalOptions, _asyncListener));
            return new Tagger(stateMachine) as ITagger<T>;
        }
 
        internal static void ResetRenameTrackingState(Workspace workspace, DocumentId documentId)
            => ResetRenameTrackingStateWorker(workspace, documentId, visible: false);
 
        internal static bool ResetVisibleRenameTrackingState(Workspace workspace, DocumentId documentId)
            => ResetRenameTrackingStateWorker(workspace, documentId, visible: true);
 
        internal static bool ResetRenameTrackingStateWorker(Workspace workspace, DocumentId documentId, bool visible)
        {
            if (workspace.IsDocumentOpen(documentId))
            {
                var document = workspace.CurrentSolution.GetDocument(documentId);
                ITextBuffer textBuffer;
                if (document != null &&
                    document.TryGetText(out var text))
                {
                    textBuffer = text.Container.TryGetTextBuffer();
                    if (textBuffer == null)
                    {
                        var ex = new InvalidOperationException(string.Format(
                            "document with name {0} is open but textBuffer is null. Textcontainer is of type {1}. SourceText is: {2}",
                            document.Name,
                            text.Container.GetType().FullName,
                            text.ToString()));
                        FatalError.ReportAndCatch(ex);
                        return false;
                    }
 
                    if (textBuffer.Properties.TryGetProperty(typeof(StateMachine), out StateMachine stateMachine))
                    {
                        if (visible)
                        {
                            return stateMachine.ClearVisibleTrackingSession();
                        }
                        else
                        {
                            return stateMachine.ClearTrackingSession();
                        }
                    }
                }
            }
 
            return false;
        }
 
        public static (CodeAction action, TextSpan renameSpan) TryGetCodeAction(
            Document document, TextSpan textSpan,
                IEnumerable<IRefactorNotifyService> refactorNotifyServices,
                ITextUndoHistoryRegistry undoHistoryRegistry,
                CancellationToken cancellationToken)
        {
            try
            {
                if (document != null && document.TryGetText(out var text))
                {
                    var textBuffer = text.Container.TryGetTextBuffer();
                    if (textBuffer != null &&
                        textBuffer.Properties.TryGetProperty(typeof(StateMachine), out StateMachine stateMachine) &&
                        stateMachine.CanInvokeRename(out _, cancellationToken: cancellationToken))
                    {
                        return stateMachine.TryGetCodeAction(
                            document, text, textSpan, refactorNotifyServices, undoHistoryRegistry, cancellationToken);
                    }
                }
 
                return default;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.General))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        internal static bool IsRenamableIdentifier(Task<TriggerIdentifierKind> isRenamableIdentifierTask, bool waitForResult, CancellationToken cancellationToken)
        {
            if (isRenamableIdentifierTask.Status == TaskStatus.RanToCompletion && isRenamableIdentifierTask.Result != TriggerIdentifierKind.NotRenamable)
            {
                return true;
            }
            else if (isRenamableIdentifierTask.Status == TaskStatus.Canceled)
            {
                return false;
            }
            else if (waitForResult)
            {
                return WaitForIsRenamableIdentifier(isRenamableIdentifierTask, cancellationToken);
            }
            else
            {
                return false;
            }
        }
 
        internal static bool WaitForIsRenamableIdentifier(Task<TriggerIdentifierKind> isRenamableIdentifierTask, CancellationToken cancellationToken)
        {
            try
            {
                return isRenamableIdentifierTask.WaitAndGetResult_CanCallOnBackground(cancellationToken) != TriggerIdentifierKind.NotRenamable;
            }
            catch (OperationCanceledException e) when (e.CancellationToken != cancellationToken || cancellationToken == CancellationToken.None)
            {
                // We passed in a different cancellationToken, so if there's a race and 
                // isRenamableIdentifierTask was cancelled, we'll get a OperationCanceledException
                return false;
            }
        }
    }
}