File: SignatureHelp\Controller.Session_UpdateModel.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_rnmfhr24_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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SignatureHelp;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp
{
    internal partial class Controller
    {
        internal partial class Session
        {
            internal readonly struct SignatureHelpSelection
            {
                private readonly int? _selectedParameter;
 
                public SignatureHelpSelection(SignatureHelpItem selectedItem, bool userSelected, int? selectedParameter) : this()
                {
                    SelectedItem = selectedItem;
                    UserSelected = userSelected;
                    _selectedParameter = selectedParameter;
                }
 
                public int? SelectedParameter => _selectedParameter;
                public SignatureHelpItem SelectedItem { get; }
                public bool UserSelected { get; }
            }
 
            internal static class DefaultSignatureHelpSelector
            {
                public static SignatureHelpSelection GetSelection(
                    IList<SignatureHelpItem> items,
                    SignatureHelpItem selectedItem,
                    bool userSelected,
                    int semanticParameterIndex,
                    int syntacticArgumentCount,
                    string argumentName,
                    bool isCaseSensitive)
                {
                    SelectBestItem(ref selectedItem, ref userSelected, items, semanticParameterIndex, syntacticArgumentCount, argumentName, isCaseSensitive);
                    var selectedParameter = GetSelectedParameter(selectedItem, semanticParameterIndex, argumentName, isCaseSensitive);
                    return new SignatureHelpSelection(selectedItem, userSelected, selectedParameter);
                }
 
                private static int GetSelectedParameter(SignatureHelpItem bestItem, int parameterIndex, string parameterName, bool isCaseSensitive)
                {
                    if (!string.IsNullOrEmpty(parameterName))
                    {
                        var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
                        var index = bestItem.Parameters.IndexOf(p => comparer.Equals(p.Name, parameterName));
                        if (index >= 0)
                        {
                            return index;
                        }
                    }
 
                    return parameterIndex;
                }
 
                private static void SelectBestItem(
                    ref SignatureHelpItem currentItem,
                    ref bool userSelected,
                    IList<SignatureHelpItem> filteredItems,
                    int semanticParameterIndex,
                    int syntacticArgumentCount,
                    string name,
                    bool isCaseSensitive)
                {
                    // If the current item is still applicable, then just keep it.
                    if (filteredItems.Contains(currentItem) &&
                        IsApplicable(currentItem, syntacticArgumentCount, name, isCaseSensitive))
                    {
                        // If the current item was user-selected, we keep it as such.
                        return;
                    }
 
                    // If the current item is no longer applicable, we'll be choosing a new one,
                    // which was definitely not previously user-selected.
                    userSelected = false;
 
                    // Try to find the first applicable item.  If there is none, then that means the
                    // selected parameter was outside the bounds of all methods.  i.e. all methods only
                    // went up to 3 parameters, and selected parameter is 3 or higher.  In that case,
                    // just pick the very last item as it is closest in parameter count.
                    var result = filteredItems.FirstOrDefault(i => IsApplicable(i, syntacticArgumentCount, name, isCaseSensitive));
                    if (result != null)
                    {
                        currentItem = result;
                        return;
                    }
 
                    // if we couldn't find a best item, and they provided a name, then try again without
                    // a name.
                    if (name != null)
                    {
                        SelectBestItem(ref currentItem, ref userSelected, filteredItems, semanticParameterIndex, syntacticArgumentCount, null, isCaseSensitive);
                        return;
                    }
 
                    // If we don't have an item that can take that number of parameters, then just pick
                    // the last item.  Or stick with the current item if the last item isn't any better.
                    var lastItem = filteredItems.Last();
                    if (currentItem.IsVariadic || currentItem.Parameters.Length == lastItem.Parameters.Length)
                    {
                        return;
                    }
 
                    currentItem = lastItem;
                }
 
                private static bool IsApplicable(SignatureHelpItem item, int syntacticArgumentCount, string name, bool isCaseSensitive)
                {
                    // If they provided a name, then the item is only valid if it has a parameter that
                    // matches that name.
                    if (name != null)
                    {
                        var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
                        return item.Parameters.Any(static (p, arg) => arg.comparer.Equals(p.Name, arg.name), (comparer, name));
                    }
 
                    // An item is applicable if it has at least as many parameters as the selected
                    // parameter index.  i.e. if it has 2 parameters and we're at index 0 or 1 then it's
                    // applicable.  However, if it has 2 parameters and we're at index 2, then it's not
                    // applicable.  
                    if (item.Parameters.Length >= syntacticArgumentCount)
                    {
                        return true;
                    }
 
                    // However, if it is variadic then it is applicable as it can take any number of
                    // items.
                    if (item.IsVariadic)
                    {
                        return true;
                    }
 
                    // Also, we special case 0.  that's because if the user has "Goo(" and goo takes no
                    // arguments, then we'll see that it's arg count is 0.  We still want to consider
                    // any item applicable here though.
                    return syntacticArgumentCount == 0;
                }
            }
        }
    }
}