Rebase cleanup

This commit is contained in:
Soreepeong 2025-08-05 11:34:09 +09:00
parent 361a0a95e9
commit 6ad43b8033
4 changed files with 106 additions and 129 deletions

View file

@ -722,10 +722,10 @@ internal class ConsoleWindow : Window, IDisposable
.OrderBy(s => s) .OrderBy(s => s)
.Prepend("DalamudInternal") .Prepend("DalamudInternal")
.Where( .Where(
name => this.pluginFilter is "" || new FuzzyMatcher( name => string.IsNullOrWhiteSpace(this.pluginFilter) ||
this.pluginFilter.ToLowerInvariant(), this.pluginFilter.FuzzyMatches(
MatchMode.Fuzzy).Matches(name.ToLowerInvariant()) != name,
0) FuzzyMatcherMode.Fuzzy))
.ToList(); .ToList();
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);

View file

@ -1270,7 +1270,7 @@ internal class PluginInstallerWindow : Window, IDisposable
var sortedChangelogs = var sortedChangelogs =
this.searchText.IsNullOrWhitespace() this.searchText.IsNullOrWhitespace()
? changelogs.ToList() ? changelogs.ToList()
: changelogs.Where(x => x.Title.FuzzyMatchesParts(this.searchText)) : changelogs.Where(x => x.Title.FuzzyMatches(this.searchText, FuzzyMatcherMode.FuzzyParts))
.OrderByDescending(x => x.Date) .OrderByDescending(x => x.Date)
.ToList(); .ToList();
@ -3798,22 +3798,18 @@ internal class PluginInstallerWindow : Window, IDisposable
private int GetManifestSearchScore(IPluginManifest manifest) private int GetManifestSearchScore(IPluginManifest manifest)
{ {
var loc = Localization.GetCultureInfoFromLangCode(Service<DalamudConfiguration>.Get().EffectiveLanguage);
var maxScore = 0; var maxScore = 0;
if (manifest.Name.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out var score)) maxScore = Math.Max(maxScore, manifest.Name.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 110);
maxScore = Math.Max(maxScore, score * 110); maxScore = Math.Max(
if (manifest.InternalName.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) maxScore,
maxScore = Math.Max(maxScore, score * 105); manifest.InternalName.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 105);
if (manifest.Author.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) maxScore = Math.Max(maxScore, manifest.Author.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100);
maxScore = Math.Max(maxScore, score * 100); maxScore = Math.Max(
if (manifest.Punchline.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score)) maxScore,
maxScore = Math.Max(maxScore, score * 100); manifest.Punchline.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100);
foreach (var tag in manifest.Tags ?? []) foreach (var tag in manifest.Tags ?? [])
{ maxScore = Math.Max(maxScore, tag.FuzzyScore(this.searchText, FuzzyMatcherMode.FuzzyParts) * 100);
if (tag.FuzzyMatches(this.searchText, FuzzyMatcher.Mode.FuzzyParts, loc, out score))
maxScore = Math.Max(maxScore, score * 100);
}
return maxScore; return maxScore;
} }

View file

@ -3,27 +3,9 @@
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Dalamud.Configuration.Internal;
namespace Dalamud.Utility; namespace Dalamud.Utility;
#pragma warning disable SA1600
#pragma warning disable SA1602
/// <summary>
/// Specify fuzzy match mode.
/// </summary>
internal enum MatchMode
{
Simple,
/// <summary>
/// The string is considered for fuzzy matching as a whole.
/// </summary>
Fuzzy,
/// <summary>
/// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way.
/// </summary>
FuzzyParts,
}
/// <summary> /// <summary>
/// Matches a string in a fuzzy way. /// Matches a string in a fuzzy way.
@ -31,95 +13,93 @@ internal enum MatchMode
internal static class FuzzyMatcher internal static class FuzzyMatcher
{ {
/// <summary> /// <summary>
/// Specify fuzzy match mode. /// Scores how well <paramref name="needle"/> can be found in <paramref name="haystack"/> in a fuzzy way.
/// </summary> /// </summary>
internal enum Mode /// <param name="haystack">The string to search in.</param>
/// <param name="needle">The substring to search for.</param>
/// <param name="mode">Fuzzy match mode.</param>
/// <param name="cultureInfo">Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language.</param>
/// <returns>The score. 0 means that the string did not match. The scores are meaningful only across matches using the same <paramref name="needle"/> value.</returns>
public static int FuzzyScore(
this ReadOnlySpan<char> haystack,
ReadOnlySpan<char> needle,
FuzzyMatcherMode mode = FuzzyMatcherMode.Simple,
CultureInfo? cultureInfo = null)
{ {
/// <summary> cultureInfo ??=
/// The string is considered for fuzzy matching as a whole. Service<DalamudConfiguration>.GetNullable().EffectiveLanguage is { } effectiveLanguage
/// </summary> ? Localization.GetCultureInfoFromLangCode(effectiveLanguage)
Fuzzy, : CultureInfo.CurrentCulture;
/// <summary> switch (mode)
/// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way. {
/// </summary> case var _ when needle.Length == 0:
FuzzyParts, 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);
}
} }
/// <inheritdoc cref="FuzzyScore(ReadOnlySpan{char},ReadOnlySpan{char},FuzzyMatcherMode,CultureInfo?)"/>
public static int FuzzyScore(
this string? haystack,
ReadOnlySpan<char> needle,
FuzzyMatcherMode mode = FuzzyMatcherMode.Simple,
CultureInfo? cultureInfo = null) => haystack.AsSpan().FuzzyScore(needle, mode, cultureInfo);
/// <summary> /// <summary>
/// Determines if <paramref name="needle"/> can be found in <paramref name="haystack"/> in a fuzzy way. /// Determines if <paramref name="needle"/> can be found in <paramref name="haystack"/> in a fuzzy way.
/// </summary> /// </summary>
/// <param name="haystack">The string to search from.</param> /// <param name="haystack">The string to search from.</param>
/// <param name="needle">The substring to search for.</param> /// <param name="needle">The substring to search for.</param>
/// <param name="mode">Fuzzy match mode.</param> /// <param name="mode">Fuzzy match mode.</param>
/// <param name="cultureInfo">Culture info for case insensitive matching.</param> /// <param name="cultureInfo">Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language.</param>
/// <param name="score">The score. 0 means that the string did not match. The scores are meaningful only across matches using the same <paramref name="needle"/> value.</param>
/// <returns><c>true</c> if matches.</returns> /// <returns><c>true</c> if matches.</returns>
public static bool FuzzyMatches( public static bool FuzzyMatches(
this ReadOnlySpan<char> haystack, this ReadOnlySpan<char> haystack,
ReadOnlySpan<char> needle, ReadOnlySpan<char> needle,
Mode mode, FuzzyMatcherMode mode = FuzzyMatcherMode.Simple,
CultureInfo cultureInfo, CultureInfo? cultureInfo = null) => haystack.FuzzyScore(needle, mode, cultureInfo) > 0;
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;
}
/// <inheritdoc cref="FuzzyMatches(ReadOnlySpan{char},ReadOnlySpan{char},Mode,CultureInfo,out int)"/>
public static bool FuzzyMatches(
this string? haystack,
ReadOnlySpan<char> needle,
Mode mode,
CultureInfo cultureInfo,
out int score) => haystack.AsSpan().FuzzyMatches(needle, mode, cultureInfo, out score);
/// <summary> /// <summary>
/// Determines if <paramref name="needle"/> can be found in <paramref name="haystack"/> using the mode /// Determines if <paramref name="needle"/> can be found in <paramref name="haystack"/> in a fuzzy way.
/// <see cref="Mode.FuzzyParts"/>.
/// </summary> /// </summary>
/// <param name="haystack">The string to search from.</param> /// <param name="haystack">The string to search from.</param>
/// <param name="needle">The substring to search for.</param> /// <param name="needle">The substring to search for.</param>
/// <param name="mode">Fuzzy match mode.</param>
/// <param name="cultureInfo">Culture info for case-insensitive matching. Defaults to the culture corresponding to Dalamud language.</param>
/// <returns><c>true</c> if matches.</returns> /// <returns><c>true</c> if matches.</returns>
public static bool FuzzyMatchesParts(this string? haystack, ReadOnlySpan<char> needle) => public static bool FuzzyMatches(
haystack.FuzzyMatches(needle, Mode.FuzzyParts, CultureInfo.InvariantCulture, out _); this string? haystack,
ReadOnlySpan<char> needle,
FuzzyMatcherMode mode = FuzzyMatcherMode.Simple,
CultureInfo? cultureInfo = null) => haystack.FuzzyScore(needle, mode, cultureInfo) > 0;
private static int GetRawScore(ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, CultureInfo cultureInfo) private static int GetRawScore(ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, CultureInfo cultureInfo)
{ {
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needle, cultureInfo); var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needle, cultureInfo);
if (startPos < 0) if (startPos < 0)
{
return 0; return 0;
}
var score = CalculateRawScore(needle.Length, startPos, gaps, consecutive, borderMatches); var score = CalculateRawScore(needle.Length, startPos, gaps, consecutive, borderMatches);
// PluginLog.Debug( // PluginLog.Debug(
@ -136,12 +116,12 @@ internal static class FuzzyMatcher
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches) private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches)
{ {
var score = 100 var score = 100;
+ needleSize * 3 score += needleSize * 3;
+ borderMatches * 3 score += borderMatches * 3;
+ consecutive * 5 score += consecutive * 5;
- startPos score -= startPos;
- gaps * 2; score -= gaps * 2;
if (startPos == 0) if (startPos == 0)
score += 5; score += 5;
return score < 1 ? 1 : score; return score < 1 ? 1 : score;
@ -168,36 +148,26 @@ internal static class FuzzyMatcher
if (haystackIndex > 0) if (haystackIndex > 0)
{ {
if (!char.IsLetterOrDigit(haystack[haystackIndex - 1])) if (!char.IsLetterOrDigit(haystack[haystackIndex - 1]))
{
borderMatches++; borderMatches++;
}
} }
#endif #endif
needleIndex++; needleIndex++;
if (haystackIndex == lastMatchIndex + 1) if (haystackIndex == lastMatchIndex + 1)
{
consecutive++; consecutive++;
}
if (needleIndex >= needle.Length) if (needleIndex >= needle.Length)
{
return (startPos, gaps, consecutive, borderMatches, haystackIndex); return (startPos, gaps, consecutive, borderMatches, haystackIndex);
}
lastMatchIndex = haystackIndex; lastMatchIndex = haystackIndex;
} }
else else
{ {
if (needleIndex > 0) if (needleIndex > 0)
{
gaps++; gaps++;
}
else else
{
startPos++; startPos++;
}
} }
} }
@ -224,23 +194,17 @@ internal static class FuzzyMatcher
if (haystackIndex > 0) if (haystackIndex > 0)
{ {
if (!char.IsLetterOrDigit(haystack[haystackIndex - 1])) if (!char.IsLetterOrDigit(haystack[haystackIndex - 1]))
{
borderMatches++; borderMatches++;
}
} }
#endif #endif
needleIndex--; needleIndex--;
if (haystackIndex == revLastMatchIndex - 1) if (haystackIndex == revLastMatchIndex - 1)
{
consecutive++; consecutive++;
}
if (needleIndex < 0) if (needleIndex < 0)
{
return (haystackIndex, gaps, consecutive, borderMatches); return (haystackIndex, gaps, consecutive, borderMatches);
}
revLastMatchIndex = haystackIndex; revLastMatchIndex = haystackIndex;
} }
@ -253,17 +217,12 @@ internal static class FuzzyMatcher
return (-1, 0, 0, 0); return (-1, 0, 0, 0);
} }
private ref struct WordEnumerator private ref struct WordEnumerator(ReadOnlySpan<char> fullNeedle)
{ {
private readonly ReadOnlySpan<char> fullNeedle; private readonly ReadOnlySpan<char> fullNeedle = fullNeedle;
private int start = -1; private int start = -1;
private int end = 0; private int end = 0;
public WordEnumerator(ReadOnlySpan<char> fullNeedle)
{
this.fullNeedle = fullNeedle;
}
public ReadOnlySpan<char> Current public ReadOnlySpan<char> Current
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View file

@ -0,0 +1,22 @@
namespace Dalamud.Utility;
/// <summary>
/// Specify fuzzy match mode.
/// </summary>
internal enum FuzzyMatcherMode
{
/// <summary>
/// The matcher only considers whether the haystack contains the needle (case-insensitive.)
/// </summary>
Simple,
/// <summary>
/// The string is considered for fuzzy matching as a whole.
/// </summary>
Fuzzy,
/// <summary>
/// Each part of the string, separated by whitespace, is considered for fuzzy matching; each part must match in a fuzzy way.
/// </summary>
FuzzyParts,
}