/* 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 . */ using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.VisualStudio.Language.Intellisense; namespace dnSpy.Contracts.Language.Intellisense { struct BestMatchSelector { enum MatchPriority { /// /// Full match, case sensitive /// Full, /// /// Full acronym match, eg. TA matches TaskAwaiter. /// This has less priority than because if there's an /// identifier named TA, then it should be selected, and not TaskAwaiter. /// FullAcronym, /// /// Full match, case insensitive /// FullIgnoringCase, /// /// Matches start of filter, case sensitive /// Start, /// /// Matches acronyms at start of filter. /// StartAcronym, /// /// Matches start of filter, case insensitive /// StartIgnoringCase, /// /// Matches any location, case sensitive /// AnyLocation, /// /// Matches acronyms at any location. /// AnyLocationAcronym, /// /// Matches any location, case insensitive /// AnyLocationIgnoringCase, /// /// Other match /// Other, /// /// Not a match /// 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; } } }