From e64a1c1d8bd44246b8574e9639b32234873d03cc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 08:26:23 +0000 Subject: [PATCH 1/2] Match any optional glob token under ByWholeString [patch] DoesMatchGlob combined optional tokens with All for everything except ByWordAny, so ByWholeString required text to satisfy every alternative at once and "*.jpg *.png" matched nothing. Only ByWordAll requires all of them now, which is what the glob hint documents. Fixes #113 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZUfnvhMFzj96bfFNzbfpn --- .gitignore | 18 ++++++++++++++++++ TextFilter.Test/TextFilterTests.cs | 19 +++++++++++++++++++ TextFilter/TextFilter.cs | 6 +++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index dc0470a..e043c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -203,6 +203,11 @@ PublishScripts/ **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ +# and except a Unity project's Packages/, which is source: Unity's package manifest and its +# resolved lock file are both meant to be committed, and a NuGet restore folder never contains +# a file by either name. +!**/[Pp]ackages/manifest.json +!**/[Pp]ackages/packages-lock.json # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files @@ -651,3 +656,16 @@ Temporary Items # ImGui.ini files imgui.ini + +# Game engine projects +# +# Godot: the import cache, and the mono/temp bin+obj a C# build writes. +.godot/ + +# Unity: .meta files are source, not the Visual Studio C++ build artifact that the `*.meta` rule +# further up targets. Unity generates one per asset and it carries the GUID that scenes, prefabs +# and serialized references point at, so ignoring them gives every clone fresh GUIDs and silently +# breaks those references - including for a plug-in whose .dll is itself a build output. This +# negation has to come after that rule to win, and is scoped to the asset tree so the Visual +# Studio artifact stays ignored everywhere else. +!**/[Aa]ssets/**/*.meta diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 40ba5c6..35b2872 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -838,4 +838,23 @@ public void RegexByWordAllFilterDropsBlankEntries() new List { "hello" }, TextFilter.Filter(["", "hello"], "hel", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll).ToList()); } + + [TestMethod] + public void GlobByWholeStringNeedsOnlyOneOptionalTokenToMatch() + { + // The glob hint promises "one of the optional tokens". ByWholeString used to require all of + // them, so a filter of alternatives such as extension globs matched nothing at all. + Assert.IsTrue(TextFilter.IsMatch("photo.jpg", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("notes.txt", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + CollectionAssert.AreEqual( + new List { "a.jpg", "b.png" }, + TextFilter.Filter(["a.jpg", "b.png", "c.txt"], "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString).ToList()); + } + + [TestMethod] + public void GlobByWordAllStillNeedsEveryOptionalTokenToMatch() + { + Assert.IsTrue(TextFilter.IsMatch("red apple", "red* app*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); + Assert.IsFalse(TextFilter.IsMatch("red apple", "red* pear*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); + } } diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 201095b..ba6ec0e 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -343,9 +343,9 @@ public static bool DoesMatchGlob(string text, string filter, TextFilterMatchOpti return false; // text contains an excluded token } - Func, Func, bool> optionalMatchFunc = textFilterMatchOptions is TextFilterMatchOptions.ByWordAny - ? Enumerable.Any - : Enumerable.All; + Func, Func, bool> optionalMatchFunc = textFilterMatchOptions is TextFilterMatchOptions.ByWordAll + ? Enumerable.All + : Enumerable.Any; bool anyOptionalMatches = optionalMatchFunc(optionalTokens, filterToken => AnyTokenMatchesGlobFilter(filterToken, textTokens, caseSensitivity)); From a8265977cd9eabbb12b6d4f2c26b079507a29d6a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 08:27:26 +0000 Subject: [PATCH 2/2] Move the #113 tests beside the other glob case tests and drop the SDK's .gitignore rewrite Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZUfnvhMFzj96bfFNzbfpn --- .gitignore | 18 -------------- TextFilter.Test/TextFilterTests.cs | 38 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index e043c9f..dc0470a 100644 --- a/.gitignore +++ b/.gitignore @@ -203,11 +203,6 @@ PublishScripts/ **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ -# and except a Unity project's Packages/, which is source: Unity's package manifest and its -# resolved lock file are both meant to be committed, and a NuGet restore folder never contains -# a file by either name. -!**/[Pp]ackages/manifest.json -!**/[Pp]ackages/packages-lock.json # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files @@ -656,16 +651,3 @@ Temporary Items # ImGui.ini files imgui.ini - -# Game engine projects -# -# Godot: the import cache, and the mono/temp bin+obj a C# build writes. -.godot/ - -# Unity: .meta files are source, not the Visual Studio C++ build artifact that the `*.meta` rule -# further up targets. Unity generates one per asset and it carries the GUID that scenes, prefabs -# and serialized references point at, so ignoring them gives every clone fresh GUIDs and silently -# breaks those references - including for a plug-in whose .dll is itself a build output. This -# negation has to come after that rule to win, and is scoped to the asset tree so the Visual -# Studio artifact stays ignored everywhere else. -!**/[Aa]ssets/**/*.meta diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 35b2872..6e44b63 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -608,6 +608,25 @@ public void TheTwoSensitivitiesDoNotCollideInTheGlobCache() Assert.IsFalse(TextFilter.IsMatch("B.MD", "*.md", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString, TextFilterCaseSensitivity.CaseSensitive)); } + [TestMethod] + public void GlobByWholeStringNeedsOnlyOneOptionalTokenToMatch() + { + // The glob hint promises "one of the optional tokens". ByWholeString used to require all of + // them, so a filter of alternatives such as extension globs matched nothing at all. + Assert.IsTrue(TextFilter.IsMatch("photo.jpg", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("notes.txt", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + CollectionAssert.AreEqual( + new List { "a.jpg", "b.png" }, + TextFilter.Filter(["a.jpg", "b.png", "c.txt"], "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString).ToList()); + } + + [TestMethod] + public void GlobByWordAllStillNeedsEveryOptionalTokenToMatch() + { + Assert.IsTrue(TextFilter.IsMatch("red apple", "red* app*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); + Assert.IsFalse(TextFilter.IsMatch("red apple", "red* pear*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); + } + [TestMethod] public void RegexCaseInsensitivityDoesNotDependOnTheCurrentCulture() { @@ -838,23 +857,4 @@ public void RegexByWordAllFilterDropsBlankEntries() new List { "hello" }, TextFilter.Filter(["", "hello"], "hel", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll).ToList()); } - - [TestMethod] - public void GlobByWholeStringNeedsOnlyOneOptionalTokenToMatch() - { - // The glob hint promises "one of the optional tokens". ByWholeString used to require all of - // them, so a filter of alternatives such as extension globs matched nothing at all. - Assert.IsTrue(TextFilter.IsMatch("photo.jpg", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); - Assert.IsFalse(TextFilter.IsMatch("notes.txt", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); - CollectionAssert.AreEqual( - new List { "a.jpg", "b.png" }, - TextFilter.Filter(["a.jpg", "b.png", "c.txt"], "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString).ToList()); - } - - [TestMethod] - public void GlobByWordAllStillNeedsEveryOptionalTokenToMatch() - { - Assert.IsTrue(TextFilter.IsMatch("red apple", "red* app*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); - Assert.IsFalse(TextFilter.IsMatch("red apple", "red* pear*", TextFilterType.Glob, TextFilterMatchOptions.ByWordAll)); - } }