File: InlineRename\AbstractEditorInlineRenameService.SymbolRenameInfo.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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename
{
    internal abstract partial class AbstractEditorInlineRenameService
    {
        /// <summary>
        /// Represents information about the ability to rename a particular location.
        /// </summary>
        private partial class SymbolInlineRenameInfo : IInlineRenameInfo
        {
            private const string AttributeSuffix = "Attribute";
 
            private readonly SymbolicRenameInfo _info;
 
            private Document Document => _info.Document!;
            private readonly CodeCleanupOptionsProvider _fallbackOptions;
            private readonly IEnumerable<IRefactorNotifyService> _refactorNotifyServices;
 
            /// <summary>
            /// Whether or not we shortened the trigger span (say because we were renaming an attribute,
            /// and we didn't select the 'Attribute' portion of the name).
            /// </summary>
            private bool IsRenamingAttributePrefix => _info.IsRenamingAttributePrefix;
 
            public bool CanRename { get; }
            public string? LocalizedErrorMessage => null;
            public TextSpan TriggerSpan { get; }
            public bool HasOverloads { get; }
            public bool MustRenameOverloads => _info.ForceRenameOverloads;
 
            /// <summary>
            /// The locations of the potential rename candidates for the symbol.
            /// </summary>
            public ImmutableArray<DocumentSpan> DefinitionLocations => _info.DocumentSpans;
 
            public ISymbol RenameSymbol => _info.Symbol!;
 
            public SymbolInlineRenameInfo(
                IEnumerable<IRefactorNotifyService> refactorNotifyServices,
                SymbolicRenameInfo info,
                CodeCleanupOptionsProvider fallbackOptions,
                CancellationToken cancellationToken)
            {
                Contract.ThrowIfTrue(info.IsError);
                this.CanRename = true;
 
                _info = info;
                _refactorNotifyServices = refactorNotifyServices;
                _fallbackOptions = fallbackOptions;
 
                this.HasOverloads = RenameUtilities.GetOverloadedSymbols(this.RenameSymbol).Any();
 
                this.TriggerSpan = GetReferenceEditSpan(new InlineRenameLocation(this.Document, info.TriggerToken.Span), info.TriggerText, cancellationToken);
            }
 
            /// <summary>
            /// Given a span of text, we need to return the subspan that is editable and
            /// contains the current replacementText.
            ///
            /// These cases are currently handled:
            ///     - Escaped identifiers                          [goo] => goo
            ///     - Type suffixes in VB                          goo$ => goo
            ///     - Qualified names from complexification        A.goo => goo
            ///     - Optional Attribute suffixes                  XAttribute => X
            ///         Careful here:                              XAttribute => XAttribute if renamesymbol is XAttributeAttribute
            ///     - Compiler-generated EventHandler suffix       XEventHandler => X
            ///     - Compiler-generated get_ and set_ prefixes    get_X => X
            /// </summary>
            public TextSpan GetReferenceEditSpan(InlineRenameLocation location, string triggerText, CancellationToken cancellationToken)
            {
                var searchName = this.RenameSymbol.Name;
                if (this.IsRenamingAttributePrefix)
                {
                    // We're only renaming the attribute prefix part.  We want to adjust the span of
                    // the reference we've found to only update the prefix portion.
                    searchName = _info.GetWithoutAttributeSuffix(this.RenameSymbol.Name);
                }
 
                var index = triggerText.LastIndexOf(searchName, StringComparison.Ordinal);
                if (index < 0)
                {
                    // Couldn't even find the search text at this reference location.  This might happen
                    // if the user used things like unicode escapes.  IN that case, we'll have to rename
                    // the entire identifier.
                    return location.TextSpan;
                }
 
                return new TextSpan(location.TextSpan.Start + index, searchName.Length);
            }
 
            public TextSpan? GetConflictEditSpan(InlineRenameLocation location, string triggerText, string replacementText, CancellationToken cancellationToken)
            {
                var position = triggerText.LastIndexOf(replacementText, StringComparison.Ordinal);
 
                if (this.IsRenamingAttributePrefix)
                {
                    // We're only renaming the attribute prefix part.  We want to adjust the span of
                    // the reference we've found to only update the prefix portion.
                    var index = triggerText.LastIndexOf(replacementText + AttributeSuffix, StringComparison.Ordinal);
                    position = index >= 0 ? index : position;
                }
 
                if (position < 0)
                {
                    return null;
                }
 
                return new TextSpan(location.TextSpan.Start + position, replacementText.Length);
            }
 
            public string DisplayName => RenameSymbol.Name;
            public string FullDisplayName => RenameSymbol.ToDisplayString();
            public Glyph Glyph => RenameSymbol.GetGlyph();
 
            public string GetFinalSymbolName(string replacementText)
                => _info.GetFinalSymbolName(replacementText);
 
            public async Task<IInlineRenameLocationSet> FindRenameLocationsAsync(SymbolRenameOptions options, CancellationToken cancellationToken)
            {
                var solution = this.Document.Project.Solution;
                var locations = await Renamer.FindRenameLocationsAsync(
                    solution, this.RenameSymbol, options, _fallbackOptions, cancellationToken).ConfigureAwait(false);
 
                return new InlineRenameLocationSet(this, locations);
            }
 
            public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable<DocumentId> changedDocumentIDs, string replacementText)
            {
                return _refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol,
                    this.GetFinalSymbolName(replacementText), throwOnFailure: false);
            }
 
            public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable<DocumentId> changedDocumentIDs, string replacementText)
            {
                return _refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol,
                    this.GetFinalSymbolName(replacementText), throwOnFailure: false);
            }
 
            public InlineRenameFileRenameInfo GetFileRenameInfo()
            {
                if (RenameSymbol.Kind == SymbolKind.NamedType &&
                    this.Document.Project.Solution.CanApplyChange(ApplyChangesKind.ChangeDocumentInfo))
                {
                    if (RenameSymbol.Locations.Length > 1)
                    {
                        return InlineRenameFileRenameInfo.TypeWithMultipleLocations;
                    }
 
                    // Get the document that the symbol is defined in to compare
                    // the name with the symbol name. If they match allow
                    // rename file rename as part of the symbol rename
                    var symbolSourceDocument = this.Document.Project.Solution.GetDocument(RenameSymbol.Locations.Single().SourceTree);
                    if (symbolSourceDocument != null && WorkspacePathUtilities.TypeNameMatchesDocumentName(symbolSourceDocument, RenameSymbol.Name))
                    {
                        return InlineRenameFileRenameInfo.Allowed;
                    }
 
                    return InlineRenameFileRenameInfo.TypeDoesNotMatchFileName;
                }
 
                return InlineRenameFileRenameInfo.NotAllowed;
            }
        }
    }
}