File: SignatureHelp\Presentation\Signature.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures_l5l0octc_wpftmp.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.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp.Presentation;
 
internal sealed class Signature : ISignature
{
    private const int MaxParamColumnCount = 100;
 
    private readonly SignatureHelpItem _signatureHelpItem;
 
    public Signature(ITrackingSpan applicableToSpan, SignatureHelpItem signatureHelpItem, int selectedParameterIndex)
    {
        if (selectedParameterIndex < -1 || selectedParameterIndex >= signatureHelpItem.Parameters.Length)
        {
            throw new ArgumentOutOfRangeException(nameof(selectedParameterIndex));
        }
 
        this.ApplicableToSpan = applicableToSpan;
        _signatureHelpItem = signatureHelpItem;
        _parameterIndex = selectedParameterIndex;
    }
 
    private bool _isInitialized;
    private void EnsureInitialized()
    {
        if (!_isInitialized)
        {
            _isInitialized = true;
            Initialize();
        }
    }
 
    private Signature InitializedThis
    {
        get
        {
            EnsureInitialized();
            return this;
        }
    }
 
    private IList<TaggedText> _displayParts;
    internal IList<TaggedText> DisplayParts => InitializedThis._displayParts;
 
    public ITrackingSpan ApplicableToSpan { get; }
 
    private string _content;
    public string Content => InitializedThis._content;
 
    private readonly int _parameterIndex = -1;
    public IParameter CurrentParameter
    {
        get
        {
            EnsureInitialized();
            return _parameterIndex >= 0 && _parameters != null ? _parameters[_parameterIndex] : null;
        }
    }
 
    /// <remarks>
    /// The documentation is included in <see cref="Content"/> so that it will be classified.
    /// </remarks>
    public string Documentation => null;
 
    private ReadOnlyCollection<IParameter> _parameters;
    public ReadOnlyCollection<IParameter> Parameters => InitializedThis._parameters;
 
    private string _prettyPrintedContent;
    public string PrettyPrintedContent => InitializedThis._prettyPrintedContent;
 
    // This event is required by the ISignature interface but it's not actually used
    // (once created the CurrentParameter property cannot change)
    public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged
    {
        add
        {
        }
        remove
        {
        }
    }
 
    private IList<TaggedText> _prettyPrintedDisplayParts;
    internal IList<TaggedText> PrettyPrintedDisplayParts => InitializedThis._prettyPrintedDisplayParts;
 
    private void Initialize()
    {
        var content = new StringBuilder();
        var prettyPrintedContent = new StringBuilder();
 
        var parts = new List<TaggedText>();
        var prettyPrintedParts = new List<TaggedText>();
 
        var parameters = new List<IParameter>();
 
        var signaturePrefixParts = _signatureHelpItem.PrefixDisplayParts;
        var signaturePrefixContent = _signatureHelpItem.PrefixDisplayParts.GetFullText();
 
        AddRange(signaturePrefixParts, parts, prettyPrintedParts);
        Append(signaturePrefixContent, content, prettyPrintedContent);
 
        var separatorParts = _signatureHelpItem.SeparatorDisplayParts;
        var separatorContent = separatorParts.GetFullText();
 
        var newLinePart = new TaggedText(TextTags.LineBreak, "\r\n");
        var newLineContent = newLinePart.ToString();
        var spacerPart = new TaggedText(TextTags.Space, new string(' ', signaturePrefixContent.Length));
        var spacerContent = spacerPart.ToString();
 
        var paramColumnCount = 0;
 
        for (var i = 0; i < _signatureHelpItem.Parameters.Length; i++)
        {
            var sigHelpParameter = _signatureHelpItem.Parameters[i];
 
            var parameterPrefixParts = sigHelpParameter.PrefixDisplayParts;
            var parameterPrefixContext = sigHelpParameter.PrefixDisplayParts.GetFullText();
 
            var parameterParts = AddOptionalBrackets(
                sigHelpParameter.IsOptional, sigHelpParameter.DisplayParts);
            var parameterContent = parameterParts.GetFullText();
 
            var parameterSuffixParts = sigHelpParameter.SuffixDisplayParts;
            var parameterSuffixContext = sigHelpParameter.SuffixDisplayParts.GetFullText();
 
            paramColumnCount += separatorContent.Length + parameterPrefixContext.Length + parameterContent.Length + parameterSuffixContext.Length;
 
            if (i > 0)
            {
                AddRange(separatorParts, parts, prettyPrintedParts);
                Append(separatorContent, content, prettyPrintedContent);
 
                if (paramColumnCount > MaxParamColumnCount)
                {
                    prettyPrintedParts.Add(newLinePart);
                    prettyPrintedParts.Add(spacerPart);
                    prettyPrintedContent.Append(newLineContent);
                    prettyPrintedContent.Append(spacerContent);
 
                    paramColumnCount = 0;
                }
            }
 
            AddRange(parameterPrefixParts, parts, prettyPrintedParts);
            Append(parameterPrefixContext, content, prettyPrintedContent);
 
            parameters.Add(new Parameter(this, sigHelpParameter, parameterContent, content.Length, prettyPrintedContent.Length));
 
            AddRange(parameterParts, parts, prettyPrintedParts);
            Append(parameterContent, content, prettyPrintedContent);
 
            AddRange(parameterSuffixParts, parts, prettyPrintedParts);
            Append(parameterSuffixContext, content, prettyPrintedContent);
        }
 
        AddRange(_signatureHelpItem.SuffixDisplayParts, parts, prettyPrintedParts);
        Append(_signatureHelpItem.SuffixDisplayParts.GetFullText(), content, prettyPrintedContent);
 
        if (_parameterIndex >= 0)
        {
            var sigHelpParameter = _signatureHelpItem.Parameters[_parameterIndex];
 
            AddRange(sigHelpParameter.SelectedDisplayParts, parts, prettyPrintedParts);
            Append(sigHelpParameter.SelectedDisplayParts.GetFullText(), content, prettyPrintedContent);
        }
 
        AddRange(_signatureHelpItem.DescriptionParts, parts, prettyPrintedParts);
        Append(_signatureHelpItem.DescriptionParts.GetFullText(), content, prettyPrintedContent);
 
        var documentation = _signatureHelpItem.DocumentationFactory(CancellationToken.None).ToList();
        if (documentation.Count > 0)
        {
            AddRange(new[] { newLinePart }, parts, prettyPrintedParts);
            Append(newLineContent, content, prettyPrintedContent);
 
            AddRange(documentation, parts, prettyPrintedParts);
            Append(documentation.GetFullText(), content, prettyPrintedContent);
        }
 
        _content = content.ToString();
        _prettyPrintedContent = prettyPrintedContent.ToString();
        _displayParts = parts.ToImmutableArrayOrEmpty();
        _prettyPrintedDisplayParts = prettyPrintedParts.ToImmutableArrayOrEmpty();
        _parameters = parameters.ToReadOnlyCollection();
    }
 
    private static void AddRange(IList<TaggedText> values, List<TaggedText> parts, List<TaggedText> prettyPrintedParts)
    {
        parts.AddRange(values);
        prettyPrintedParts.AddRange(values);
    }
 
    private static void Append(string text, StringBuilder content, StringBuilder prettyPrintedContent)
    {
        content.Append(text);
        prettyPrintedContent.Append(text);
    }
 
    private static IList<TaggedText> AddOptionalBrackets(bool isOptional, IList<TaggedText> list)
    {
        if (isOptional)
        {
            var result = new List<TaggedText>
            {
                new TaggedText(TextTags.Punctuation, "[")
            };
            result.AddRange(list);
            result.Add(new TaggedText(TextTags.Punctuation, "]"));
            return result;
        }
 
        return list;
    }
}