From 583c00725392c3d3025c2c9ff183834b250717ad Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Thu, 4 Jul 2024 01:37:25 +0900 Subject: [PATCH] Add new default 'search score' sorting method for plugin search (#1882) --- .../PluginInstaller/PluginInstallerWindow.cs | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index b0c1e0b5f..953301091 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -124,6 +124,7 @@ internal class PluginInstallerWindow : Window, IDisposable private PluginSortKind sortKind = PluginSortKind.Alphabetical; private string filterText = Locs.SortBy_Alphabetical; + private bool adaptiveSort = true; private OperationStatus installStatus = OperationStatus.Idle; private OperationStatus updateStatus = OperationStatus.Idle; @@ -208,6 +209,7 @@ internal class PluginInstallerWindow : Window, IDisposable NotInstalled, EnabledDisabled, ProfileOrNot, + SearchScore, } [Flags] @@ -280,6 +282,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (!this.isSearchTextPrefilled) this.searchText = string.Empty; this.sortKind = PluginSortKind.Alphabetical; this.filterText = Locs.SortBy_Alphabetical; + this.adaptiveSort = true; if (this.updateStatus == OperationStatus.Complete || this.updateStatus == OperationStatus.Idle) { @@ -599,6 +602,7 @@ internal class PluginInstallerWindow : Window, IDisposable var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; var sortSelectables = new (string Localization, PluginSortKind SortKind)[] { + (Locs.SortBy_SearchScore, PluginSortKind.SearchScore), (Locs.SortBy_Alphabetical, PluginSortKind.Alphabetical), (Locs.SortBy_DownloadCounts, PluginSortKind.DownloadCount), (Locs.SortBy_LastUpdate, PluginSortKind.LastUpdate), @@ -651,7 +655,29 @@ internal class PluginInstallerWindow : Window, IDisposable } if (searchTextChanged) + { + if (this.adaptiveSort) + { + if (string.IsNullOrWhiteSpace(this.searchText)) + { + this.sortKind = PluginSortKind.Alphabetical; + this.filterText = Locs.SortBy_Alphabetical; + } + else + { + this.sortKind = PluginSortKind.SearchScore; + this.filterText = Locs.SortBy_SearchScore; + } + + this.ResortPlugins(); + } + else if (this.sortKind == PluginSortKind.SearchScore) + { + this.ResortPlugins(); + } + this.UpdateCategoriesOnSearchChange(prevSearchText); + } } // Disable sort if changelogs or profile editor @@ -664,10 +690,14 @@ internal class PluginInstallerWindow : Window, IDisposable { foreach (var selectable in sortSelectables) { + if (selectable.SortKind == PluginSortKind.SearchScore && string.IsNullOrWhiteSpace(this.searchText)) + continue; + if (ImGui.Selectable(selectable.Localization)) { this.sortKind = selectable.SortKind; this.filterText = selectable.Localization; + this.adaptiveSort = false; lock (this.listLock) { @@ -3492,8 +3522,6 @@ internal class PluginInstallerWindow : Window, IDisposable private bool IsManifestFiltered(IPluginManifest manifest) { - var searchString = this.searchText.ToLowerInvariant(); - var matcher = new FuzzyMatcher(searchString, MatchMode.FuzzyParts); var hasSearchString = !string.IsNullOrWhiteSpace(this.searchText); var oldApi = (manifest.TestingDalamudApiLevel == null || manifest.TestingDalamudApiLevel < PluginManager.DalamudApiLevel) @@ -3503,12 +3531,30 @@ internal class PluginInstallerWindow : Window, IDisposable if (oldApi && !hasSearchString && !installed) return true; - return hasSearchString && !( - (!manifest.Name.IsNullOrEmpty() && matcher.Matches(manifest.Name.ToLowerInvariant()) > 0) || - (!manifest.InternalName.IsNullOrEmpty() && matcher.Matches(manifest.InternalName.ToLowerInvariant()) > 0) || - (!manifest.Author.IsNullOrEmpty() && matcher.Matches(manifest.Author.ToLowerInvariant()) > 0) || - (!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) || - (manifest.Tags != null && matcher.MatchesAny(manifest.Tags.Select(term => term.ToLowerInvariant()).ToArray()) > 0)); + if (!hasSearchString) + return false; + + return this.GetManifestSearchScore(manifest) < 1; + } + + private int GetManifestSearchScore(IPluginManifest manifest) + { + var searchString = this.searchText.ToLowerInvariant(); + var matcher = new FuzzyMatcher(searchString, MatchMode.FuzzyParts); + var scores = new List { 0 }; + + if (!manifest.Name.IsNullOrEmpty()) + scores.Add(matcher.Matches(manifest.Name.ToLowerInvariant()) * 110); + if (!manifest.InternalName.IsNullOrEmpty()) + scores.Add(matcher.Matches(manifest.InternalName.ToLowerInvariant()) * 105); + if (!manifest.Author.IsNullOrEmpty()) + scores.Add(matcher.Matches(manifest.Author.ToLowerInvariant()) * 100); + if (!manifest.Punchline.IsNullOrEmpty()) + scores.Add(matcher.Matches(manifest.Punchline.ToLowerInvariant()) * 100); + if (manifest.Tags != null) + scores.Add(matcher.MatchesAny(manifest.Tags.ToArray()) * 100); + + return scores.Max(); } private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(IPluginManifest? manifest) @@ -3609,6 +3655,10 @@ internal class PluginInstallerWindow : Window, IDisposable var profman = Service.Get(); this.pluginListInstalled.Sort((p1, p2) => profman.IsInDefaultProfile(p1.EffectiveWorkingPluginId).CompareTo(profman.IsInDefaultProfile(p2.EffectiveWorkingPluginId))); break; + case PluginSortKind.SearchScore: + this.pluginListAvailable = this.pluginListAvailable.OrderByDescending(this.GetManifestSearchScore).ThenBy(m => m.Name).ToList(); + this.pluginListInstalled = this.pluginListInstalled.OrderByDescending(p => this.GetManifestSearchScore(p.Manifest)).ThenBy(m => m.Name).ToList(); + break; default: throw new InvalidEnumArgumentException("Unknown plugin sort type."); } @@ -3754,6 +3804,8 @@ internal class PluginInstallerWindow : Window, IDisposable #region SortBy + public static string SortBy_SearchScore => Loc.Localize("InstallerSearchScore", "Search score"); + public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical"); public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count");