File: SignatureHelp\SignatureHelpItems.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.SignatureHelp;
 
internal sealed class SignatureHelpItems
{
    /// <summary>
    /// The list of items to present to the user.
    /// </summary>
    public IList<SignatureHelpItem> Items { get; }
 
    /// <summary>
    /// The span this session applies to.
    /// 
    /// Navigation outside this span will cause signature help to be dismissed.
    /// </summary>
    public TextSpan ApplicableSpan { get; }
 
    /// <summary>
    /// Returns the specified <em>parameter</em> index that the provided position is at in the current document.  This 
    /// index may be greater than the number of actual syntactic arguments in the selected <see cref="SignatureHelpItem"/>.
    /// </summary>
    /// <remarks>
    /// This relates to the original semantic symbol that was used to create a particular item.  In other words,
    /// it may be using
    /// </remarks>
    public int SemanticParameterIndex { get; }
 
    /// <summary>
    /// Returns the total number of arguments that have been typed in the current document.  This may be 
    /// greater than the ArgumentIndex if there are additional arguments after the provided position.
    /// </summary>
    public int SyntacticArgumentCount { get; }
 
    /// <summary>
    /// Returns the name of specified argument at the current position in the document.  
    /// This only applies to languages that allow the user to provide named arguments.
    /// If no named argument exists at the current position, then null should be returned. 
    /// 
    /// This value is used to determine which documentation comment should be provided for the current
    /// parameter.  Normally this is determined simply by determining the parameter by index.
    /// </summary>
    public string? ArgumentName { get; }
 
    /// <summary>
    /// The item to select by default.  If this is <see langword="null"/> then the controller will
    /// pick the first item that has enough arguments to be viable based on what argument 
    /// position the user is currently inside of.
    /// </summary>
    public int? SelectedItemIndex { get; }
 
    public SignatureHelpItems(
        IList<SignatureHelpItem> items,
        TextSpan applicableSpan,
        int semanticParameterIndex,
        int syntacticArgumentCount,
        string? argumentName,
        int? selectedItem = null)
    {
        Contract.ThrowIfNull(items);
        Contract.ThrowIfTrue(items.IsEmpty());
        Contract.ThrowIfTrue(selectedItem.HasValue && selectedItem.Value >= items.Count);
 
        if (semanticParameterIndex < 0)
            throw new ArgumentException($"{nameof(semanticParameterIndex)} < 0. {semanticParameterIndex} < 0", nameof(semanticParameterIndex));
 
        // Adjust the `selectedItem` index if duplicates are able to be removed.
        var distinctItems = items.Distinct().ToList();
        if (selectedItem.HasValue && items.Count != distinctItems.Count)
        {
            // `selectedItem` index has already been determined to be valid, it now needs to be adjusted to point
            // to the equivalent item in the reduced list to account for duplicates being removed
            // E.g.,
            //   items = {A, A, B, B, C, D}
            //   selectedItem = 4 (index for item C)
            // ergo
            //   distinctItems = {A, B, C, D}
            //   actualItem = C
            //   selectedItem = 2 (index for item C)
            var actualItem = items[selectedItem.Value];
            selectedItem = distinctItems.IndexOf(actualItem);
            Debug.Assert(selectedItem.Value >= 0, "actual item was not part of the final list");
        }
 
        Items = distinctItems;
        ApplicableSpan = applicableSpan;
        SemanticParameterIndex = semanticParameterIndex;
        SyntacticArgumentCount = syntacticArgumentCount;
        SelectedItemIndex = selectedItem;
        ArgumentName = argumentName;
    }
}