File: Completion\MatchResult.cs
Web Access
Project: src\src\roslyn\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.Collections.Generic;
using Microsoft.CodeAnalysis.PatternMatching;

namespace Microsoft.CodeAnalysis.Completion;

internal readonly struct MatchResult(
    CompletionItem completionItem,
    bool shouldBeConsideredMatchingFilterText,
    PatternMatch? patternMatch,
    int index,
    string? matchedAdditionalFilterText,
    int recentItemIndex = -1)
{
    /// <summary>
    /// The CompletionItem used to create this MatchResult.
    /// </summary>
    public readonly CompletionItem CompletionItem = completionItem;

    public readonly PatternMatch? PatternMatch = patternMatch;

    // The value of `ShouldBeConsideredMatchingFilterText` doesn't 100% reflect the actual PatternMatch result.
    // In certain cases, there'd be no match but we'd still want to consider it a match (e.g. when the item is in MRU list,)
    // and this is why PatternMatch can be null. There's also cases it's a match but we want to consider it a non-match
    // (e.g. when not a prefix match in deletion scenario).
    public readonly bool ShouldBeConsideredMatchingFilterText = shouldBeConsideredMatchingFilterText;

    public string FilterTextUsed => MatchedAdditionalFilterText ?? CompletionItem.FilterText;

    // We want to preserve the original alphabetical order for items with same pattern match score,
    // but `ArrayBuilder.Sort` we currently use isn't stable. So we have to add a monotonically increasing 
    // integer to achieve this.
    public readonly int IndexInOriginalSortedOrder = index;
    public readonly int RecentItemIndex = recentItemIndex;

    /// <summary>
    /// If `CompletionItem.AdditionalFilterTexts` was used to create this MatchResult, then this is set to the one that was used.
    /// Otherwise this is set to null.
    /// </summary>
    public readonly string? MatchedAdditionalFilterText = matchedAdditionalFilterText;

    public bool MatchedWithAdditionalFilterTexts => MatchedAdditionalFilterText is not null;

    public static IComparer<MatchResult> SortingComparer { get; } = new Comparer();

    private sealed class Comparer : IComparer<MatchResult>
    {
        // This comparison is used for sorting items in the completion list for the original sorting.

        public int Compare(MatchResult x, MatchResult y)
        {
            var matchX = x.PatternMatch;
            var matchY = y.PatternMatch;

            if (matchX.HasValue)
            {
                if (matchY.HasValue)
                {
                    var ret = matchX.Value.CompareTo(matchY.Value);

                    // We'd rank match of FilterText over match of any of AdditionalFilterTexts if they has same pattern match score
                    if (ret == 0)
                        ret = x.MatchedWithAdditionalFilterTexts.CompareTo(y.MatchedWithAdditionalFilterTexts);

                    // We want to preserve the original order for items with same pattern match score.
                    return ret == 0 ? x.IndexInOriginalSortedOrder - y.IndexInOriginalSortedOrder : ret;
                }

                return -1;
            }

            if (matchY.HasValue)
            {
                return 1;
            }

            return x.IndexInOriginalSortedOrder - y.IndexInOriginalSortedOrder;
        }
    }

    public override string ToString()
        => this.CompletionItem.ToString();
}