2021-09-20 18:20:01 +02:00

156 lines
4.3 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.VisualStudio.Language.Intellisense;
namespace dnSpy.Contracts.Language.Intellisense {
struct BestMatchSelector {
enum MatchPriority {
/// <summary>
/// Full match, case sensitive
/// </summary>
Full,
/// <summary>
/// Full acronym match, eg. <c>TA</c> matches <c>TaskAwaiter</c>.
/// This has less priority than <see cref="Full"/> because if there's an
/// identifier named <c>TA</c>, then it should be selected, and not <c>TaskAwaiter</c>.
/// </summary>
FullAcronym,
/// <summary>
/// Full match, case insensitive
/// </summary>
FullIgnoringCase,
/// <summary>
/// Matches start of filter, case sensitive
/// </summary>
Start,
/// <summary>
/// Matches acronyms at start of filter.
/// </summary>
StartAcronym,
/// <summary>
/// Matches start of filter, case insensitive
/// </summary>
StartIgnoringCase,
/// <summary>
/// Matches any location, case sensitive
/// </summary>
AnyLocation,
/// <summary>
/// Matches acronyms at any location.
/// </summary>
AnyLocationAcronym,
/// <summary>
/// Matches any location, case insensitive
/// </summary>
AnyLocationIgnoringCase,
/// <summary>
/// Other match
/// </summary>
Other,
/// <summary>
/// Not a match
/// </summary>
Nothing = int.MaxValue,
}
public Completion? Result => bestCompletion;
readonly string searchText;
MatchPriority matchPriority;
Completion? bestCompletion;
readonly int[]? acronymMatchIndexes;
public BestMatchSelector(string searchText) {
this.searchText = searchText ?? throw new ArgumentNullException(nameof(searchText));
matchPriority = MatchPriority.Nothing;
bestCompletion = null;
acronymMatchIndexes = AcronymSearchHelpers.TryCreateMatchIndexes(searchText);
}
public void Select(Completion completion) {
var newMatchPriority = GetMatchPriority(completion);
if (newMatchPriority < matchPriority) {
bestCompletion = completion;
matchPriority = newMatchPriority;
}
}
int CountUpperCaseLetters(string text) {
int count = 0;
foreach (var c in text) {
if (char.IsUpper(c))
count++;
}
return count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
MatchPriority GetMatchPriority(Completion completion) {
var filterText = completion.TryGetFilterText();
Debug2.Assert(filterText is not null);
if (filterText is null)
return MatchPriority.Other;
if (filterText.Equals(searchText, StringComparison.CurrentCulture))
return MatchPriority.Full;
bool matchedAcronym = acronymMatchIndexes is not null && AcronymSearchHelpers.TryUpdateAcronymIndexes(acronymMatchIndexes, searchText, filterText);
if (matchedAcronym && CountUpperCaseLetters(filterText) == acronymMatchIndexes!.Length)
return MatchPriority.FullAcronym;
if (filterText.Equals(searchText, StringComparison.CurrentCultureIgnoreCase))
return MatchPriority.FullIgnoringCase;
int index = filterText.IndexOf(searchText, StringComparison.CurrentCulture);
if (index == 0)
return MatchPriority.Start;
if (matchedAcronym && acronymMatchIndexes![0] == 0)
return MatchPriority.StartAcronym;
int indexIgnoringCase = filterText.IndexOf(searchText, StringComparison.CurrentCultureIgnoreCase);
if (indexIgnoringCase == 0)
return MatchPriority.StartIgnoringCase;
if (index > 0)
return MatchPriority.AnyLocation;
if (matchedAcronym)
return MatchPriority.AnyLocationAcronym;
if (indexIgnoringCase > 0)
return MatchPriority.AnyLocationIgnoringCase;
return MatchPriority.Other;
}
}
}