From 6ad43b80339531c37e01d7a834e168c9a7b3cc6a Mon Sep 17 00:00:00 2001 From: Soreepeong <3614868+Soreepeong@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:34:09 +0900 Subject: [PATCH] Rebase cleanup --- .../Internal/Windows/ConsoleWindow.cs | 8 +- .../PluginInstaller/PluginInstallerWindow.cs | 24 +-- Dalamud/Utility/FuzzyMatcher.cs | 181 +++++++----------- Dalamud/Utility/FuzzyMatcherMode.cs | 22 +++ 4 files changed, 106 insertions(+), 129 deletions(-) create mode 100644 Dalamud/Utility/FuzzyMatcherMode.cs diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index 74df500db..49bdebe64 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -722,10 +722,10 @@ internal class ConsoleWindow : Window, IDisposable .OrderBy(s => s) .Prepend("DalamudInternal") .Where( - name => this.pluginFilter is "" || new FuzzyMatcher( - this.pluginFilter.ToLowerInvariant(), - MatchMode.Fuzzy).Matches(name.ToLowerInvariant()) != - 0) + name => string.IsNullOrWhiteSpace(this.pluginFilter) || + this.pluginFilter.FuzzyMatches( + name, + FuzzyMatcherMode.Fuzzy)) .ToList(); ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index fcf9d4f6d..c432739bf 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1270,7 +1270,7 @@ internal class PluginInstallerWindow : Window, IDisposable var sortedChangelogs = this.searchText.IsNullOrWhitespace() ? changelogs.ToList() - : changelogs.Where(x => x.Title.FuzzyMatchesParts(this.searchText)) + : changelogs.Where(x => x.Title.FuzzyMatches(this.searchText, FuzzyMatcherMode.FuzzyParts)) .OrderByDescending(x => x.Date) .ToList(); @@ -3798,22 +3798,18 @@ internal class PluginInstallerWindow : Window, IDisposable private int GetManifestSearchScore(IPluginManifest manifest) { - var loc = Localization.GetCultureInfoFromLangCode(Service.Get().EffectiveLanguage); var maxScore = 0; - if (manifest.Name.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out var score)) - maxScore = Math.Max(maxScore, score * 110); - if (manifest.InternalName.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) - maxScore = Math.Max(maxScore, score * 105); - if (manifest.Author.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) - maxScore = Math.Max(maxScore, score * 100); - if (manifest.Punchline.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) - maxScore = Math.Max(maxScore, score * 100); + maxScore = Math.Max(maxScore, manifest.Name.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 110); + maxScore = Math.Max( + maxScore, + manifest.InternalName.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 105); + maxScore = Math.Max(maxScore, manifest.Author.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100); + maxScore = Math.Max( + maxScore, + manifest.Punchline.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100); foreach (var tag in manifest.Tags ?? []) - { - if (tag.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) - maxScore = Math.Max(maxScore, score * 100); - } + maxScore = Math.Max(maxScore, tag.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100); return maxScore; } diff --git a/Dalamud/Utility/FuzzyMatcher.cs b/Dalamud/Utility/FuzzyMatcher.cs index b58e1fe92..4f50c02ba 100644 --- a/Dalamud/Utility/FuzzyMatcher.cs +++ b/Dalamud/Utility/FuzzyMatcher.cs @@ -3,27 +3,9 @@ using System.Globalization; using System.Runtime.CompilerServices; +using Dalamud.Configuration.Internal; + namespace Dalamud.Utility; -#pragma warning disable SA1600 -#pragma warning disable SA1602 - -/// -/// Specify fuzzy match mode. -/// -internal enum MatchMode -{ - Simple, - - /// - /// The string is considered for fuzzy matching as a whole. - /// - Fuzzy, - - /// - /// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way. - /// - FuzzyParts, -} /// /// Matches a string in a fuzzy way. @@ -31,95 +13,93 @@ internal enum MatchMode internal static class FuzzyMatcher { /// - /// Specify fuzzy match mode. + /// Scores how well can be found in in a fuzzy way. /// - internal enum Mode + /// The string to search in. + /// The substring to search for. + /// Fuzzy match mode. + /// Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language. + /// The score. 0 means that the string did not match. The scores are meaningful only across matches using the same value. + public static int FuzzyScore( + this ReadOnlySpan haystack, + ReadOnlySpan needle, + FuzzyMatcherMode mode = FuzzyMatcherMode.Simple, + CultureInfo? cultureInfo = null) { - /// - /// The string is considered for fuzzy matching as a whole. - /// - Fuzzy, + cultureInfo ??= + Service.GetNullable().EffectiveLanguage is { } effectiveLanguage + ? Localization.GetCultureInfoFromLangCode(effectiveLanguage) + : CultureInfo.CurrentCulture; - /// - /// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way. - /// - FuzzyParts, + switch (mode) + { + case var _ when needle.Length == 0: + return 0; + + case FuzzyMatcherMode.Simple: + return cultureInfo.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) != -1 ? 1 : 0; + + case FuzzyMatcherMode.Fuzzy: + return GetRawScore(haystack, needle, cultureInfo); + + case FuzzyMatcherMode.FuzzyParts: + var score = 0; + foreach (var needleSegment in new WordEnumerator(needle)) + { + var cur = GetRawScore(haystack, needleSegment, cultureInfo); + if (cur == 0) + return 0; + + score += cur; + } + + return score; + + default: + throw new ArgumentOutOfRangeException(nameof(mode), mode, null); + } } + /// + public static int FuzzyScore( + this string? haystack, + ReadOnlySpan needle, + FuzzyMatcherMode mode = FuzzyMatcherMode.Simple, + CultureInfo? cultureInfo = null) => haystack.AsSpan().FuzzyScore(needle, mode, cultureInfo); + /// /// Determines if can be found in in a fuzzy way. /// /// The string to search from. /// The substring to search for. /// Fuzzy match mode. - /// Culture info for case insensitive matching. - /// The score. 0 means that the string did not match. The scores are meaningful only across matches using the same value. + /// Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language. /// true if matches. public static bool FuzzyMatches( this ReadOnlySpan haystack, ReadOnlySpan needle, - Mode mode, - CultureInfo cultureInfo, - out int score) - { - score = 0; - switch (mode) - { - case var _ when needle.Length == 0: - score = 0; - break; - - case Mode.Fuzzy: - score = GetRawScore(haystack, needle, cultureInfo); - break; - - case Mode.FuzzyParts: - foreach (var needleSegment in new WordEnumerator(needle)) - { - var cur = GetRawScore(haystack, needleSegment, cultureInfo); - if (cur == 0) - { - score = 0; - break; - } - - score += cur; - } - - break; - - default: - throw new ArgumentOutOfRangeException(nameof(mode), mode, null); - } - - return score > 0; - } - - /// - public static bool FuzzyMatches( - this string? haystack, - ReadOnlySpan needle, - Mode mode, - CultureInfo cultureInfo, - out int score) => haystack.AsSpan().FuzzyMatches(needle, mode, cultureInfo, out score); + FuzzyMatcherMode mode = FuzzyMatcherMode.Simple, + CultureInfo? cultureInfo = null) => haystack.FuzzyScore(needle, mode, cultureInfo) > 0; /// - /// Determines if can be found in using the mode - /// . + /// Determines if can be found in in a fuzzy way. /// /// The string to search from. /// The substring to search for. + /// Fuzzy match mode. + /// Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language. /// true if matches. - public static bool FuzzyMatchesParts(this string? haystack, ReadOnlySpan needle) => - haystack.FuzzyMatches(needle, Mode.FuzzyParts, CultureInfo.InvariantCulture, out _); + public static bool FuzzyMatches( + this string? haystack, + ReadOnlySpan needle, + FuzzyMatcherMode mode = FuzzyMatcherMode.Simple, + CultureInfo? cultureInfo = null) => haystack.FuzzyScore(needle, mode, cultureInfo) > 0; private static int GetRawScore(ReadOnlySpan haystack, ReadOnlySpan needle, CultureInfo cultureInfo) { var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needle, cultureInfo); if (startPos < 0) - { return 0; - } var score = CalculateRawScore(needle.Length, startPos, gaps, consecutive, borderMatches); // PluginLog.Debug( @@ -136,12 +116,12 @@ internal static class FuzzyMatcher [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches) { - var score = 100 - + needleSize * 3 - + borderMatches * 3 - + consecutive * 5 - - startPos - - gaps * 2; + var score = 100; + score += needleSize * 3; + score += borderMatches * 3; + score += consecutive * 5; + score -= startPos; + score -= gaps * 2; if (startPos == 0) score += 5; return score < 1 ? 1 : score; @@ -168,36 +148,26 @@ internal static class FuzzyMatcher if (haystackIndex > 0) { if (!char.IsLetterOrDigit(haystack[haystackIndex - 1])) - { borderMatches++; - } } #endif needleIndex++; if (haystackIndex == lastMatchIndex + 1) - { consecutive++; - } if (needleIndex >= needle.Length) - { return (startPos, gaps, consecutive, borderMatches, haystackIndex); - } lastMatchIndex = haystackIndex; } else { if (needleIndex > 0) - { gaps++; - } else - { startPos++; - } } } @@ -224,23 +194,17 @@ internal static class FuzzyMatcher if (haystackIndex > 0) { if (!char.IsLetterOrDigit(haystack[haystackIndex - 1])) - { borderMatches++; - } } #endif needleIndex--; if (haystackIndex == revLastMatchIndex - 1) - { consecutive++; - } if (needleIndex < 0) - { return (haystackIndex, gaps, consecutive, borderMatches); - } revLastMatchIndex = haystackIndex; } @@ -253,17 +217,12 @@ internal static class FuzzyMatcher return (-1, 0, 0, 0); } - private ref struct WordEnumerator + private ref struct WordEnumerator(ReadOnlySpan fullNeedle) { - private readonly ReadOnlySpan fullNeedle; + private readonly ReadOnlySpan fullNeedle = fullNeedle; private int start = -1; private int end = 0; - public WordEnumerator(ReadOnlySpan fullNeedle) - { - this.fullNeedle = fullNeedle; - } - public ReadOnlySpan Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Dalamud/Utility/FuzzyMatcherMode.cs b/Dalamud/Utility/FuzzyMatcherMode.cs new file mode 100644 index 000000000..1be814a78 --- /dev/null +++ b/Dalamud/Utility/FuzzyMatcherMode.cs @@ -0,0 +1,22 @@ +namespace Dalamud.Utility; + +/// +/// Specify fuzzy match mode. +/// +internal enum FuzzyMatcherMode +{ + /// + /// The matcher only considers whether the haystack contains the needle (case-insensitive.) + /// + Simple, + + /// + /// The string is considered for fuzzy matching as a whole. + /// + Fuzzy, + + /// + /// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way. + /// + FuzzyParts, +}