// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.CommandPalette.Extensions.Toolkit; public partial class ListHelpers { // Generate a score for a list item. public static int ScoreListItem(string query, ICommandItem listItem) { if (string.IsNullOrEmpty(query) || string.IsNullOrWhiteSpace(query)) { return 1; } if (string.IsNullOrEmpty(listItem.Title)) { return 0; } var nameMatchScore = FuzzyStringMatcher.ScoreFuzzy(query, listItem.Title); // var locNameMatch = StringMatcher.FuzzySearch(query, NameLocalized); var descriptionMatchScore = FuzzyStringMatcher.ScoreFuzzy(query, listItem.Subtitle); // var executableNameMatch = StringMatcher.FuzzySearch(query, ExePath); // var locExecutableNameMatch = StringMatcher.FuzzySearch(query, ExecutableNameLocalized); // var lnkResolvedExecutableNameMatch = StringMatcher.FuzzySearch(query, LnkResolvedExecutableName); // var locLnkResolvedExecutableNameMatch = StringMatcher.FuzzySearch(query, LnkResolvedExecutableNameLocalized); // var score = new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, executableNameMatch.Score }.Max(); return new[] { nameMatchScore, (descriptionMatchScore - 4) / 2, 0 }.Max(); } public static IEnumerable FilterList(IEnumerable items, string query) { var scores = items .Select(li => new ScoredListItem() { ListItem = li, Score = ScoreListItem(query, li) }) .Where(score => score.Score > 0) .OrderByDescending(score => score.Score); return scores .Select(score => score.ListItem); } public static IEnumerable FilterList(IEnumerable items, string query, Func scoreFunction) { return FilterListWithScores(items, query, scoreFunction) .Select(score => score.Item); } public static IEnumerable> FilterListWithScores(IEnumerable items, string query, Func scoreFunction) { var scores = items .Select(li => new Scored() { Item = li, Score = scoreFunction(query, li) }) .Where(score => score.Score > 0) .OrderByDescending(score => score.Score); return scores; } /// /// Modifies the contents of `original` in-place, to match those of /// `newContents`. The canonical use being: /// ```cs /// ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(ItemsToFilter, TextToFilterOn)); /// ``` /// /// Any type that can be compared for equality /// Collection to modify /// The enumerable which `original` should match public static void InPlaceUpdateList(IList original, IEnumerable newContents) where T : class { InPlaceUpdateList(original, newContents, out _); } /// /// Modifies the contents of `original` in-place, to match those of /// `newContents`. The canonical use being: /// ```cs /// ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(ItemsToFilter, TextToFilterOn)); /// ``` /// /// Any type that can be compared for equality /// Collection to modify /// The enumerable which `original` should match /// List of items that were removed from the original collection public static void InPlaceUpdateList(IList original, IEnumerable newContents, out List removedItems) where T : class { removedItems = []; // we're not changing newContents - stash this so we don't re-evaluate it every time var numberOfNew = newContents.Count(); // Short circuit - new contents should just be empty if (numberOfNew == 0) { removedItems.AddRange(original); original.Clear(); return; } var i = 0; foreach (var newItem in newContents) { if (i >= original.Count) { break; } for (var j = i; j < original.Count; j++) { var og_2 = original[j]; var areEqual_2 = og_2?.Equals(newItem) ?? false; if (areEqual_2) { for (var k = i; k < j; k++) { // This item from the original list was not in the new list. Remove it. removedItems.Add(original[i]); original.RemoveAt(i); } break; } } var og = original[i]; var areEqual = og?.Equals(newItem) ?? false; // Is this new item already in the list? if (areEqual) { // It is already in the list } else { // it isn't. Add it. original.Insert(i, newItem); } i++; } // Remove any extra trailing items from the destination while (original.Count > numberOfNew) { // RemoveAtEnd removedItems.Add(original[original.Count - 1]); original.RemoveAt(original.Count - 1); } // Add any extra trailing items from the source if (original.Count < numberOfNew) { var remaining = newContents.Skip(original.Count); foreach (var item in remaining) { original.Add(item); } } } } public struct ScoredListItem { public int Score; public IListItem ListItem; } public struct Scored { public int Score; public T Item; }