From f64f72bc79cee53d8326ecbb30d5ded925f31488 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 08:25:00 +0000 Subject: [PATCH 1/2] Split filters and text on all whitespace, not only ' ' [patch] A chunk of other whitespace (tab, CR/LF, NBSP) between spaces survived RemoveEmptyEntries and then trimmed to an empty token. The glob tokenizer read its first character and threw, and regex ByWordAll saw a phantom empty word, which also undid the blank-text guard from #107. Fixes #112 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZUfnvhMFzj96bfFNzbfpn --- .gitignore | 18 +++++++++++++++++ TextFilter.Test/TextFilterTests.cs | 31 ++++++++++++++++++++++++++++++ TextFilter/TextFilter.cs | 6 +++--- 3 files changed, 52 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..4792ebd 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -838,4 +838,35 @@ public void RegexByWordAllFilterDropsBlankEntries() new List { "hello" }, TextFilter.Filter(["", "hello"], "hel", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll).ToList()); } + + [TestMethod] + [DataRow("foo \t")] + [DataRow("foo \r\n")] + [DataRow("foo \u00a0")] + public void GlobWithNonSpaceWhitespaceDoesNotThrow(string filter) + { + // Splitting only on ' ' left a chunk of other whitespace that trimmed to an empty token, and + // the glob tokenizer then read its first character. A pasted trailing tab or newline is enough. + Assert.IsTrue(TextFilter.IsMatch("foo", filter)); + CollectionAssert.AreEqual( + new List { "foo" }, + TextFilter.Filter(["foo"], filter).ToList()); + } + + [TestMethod] + public void RegexByWordAllIgnoresNonSpaceWhitespaceBetweenWords() + { + Assert.IsTrue(TextFilter.IsMatch("hello \t world", "o", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll)); + Assert.IsTrue(TextFilter.IsMatch("hello\tworld", "o", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll)); + Assert.IsFalse(TextFilter.IsMatch("hello\tbye", "o", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll)); + } + + [TestMethod] + [DataRow(" \t ")] + [DataRow("\r\n")] + [DataRow("\u00a0")] + public void RegexByWordAllDoesNotMatchTextOfOnlyNonSpaceWhitespace(string text) + { + Assert.IsFalse(TextFilter.IsMatch(text, "a*", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll)); + } } diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 201095b..16dc5dc 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -261,15 +261,15 @@ internal static HashSet ExtractTextTokens(string text, TextFilterMatchOp return textFilterMatchOptions switch { TextFilterMatchOptions.ByWholeString => [text], - TextFilterMatchOptions.ByWordAll => [.. text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())], - TextFilterMatchOptions.ByWordAny => [.. text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())], + TextFilterMatchOptions.ByWordAll => [.. text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)], + TextFilterMatchOptions.ByWordAny => [.. text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)], _ => throw new NotImplementedException($"{nameof(TextFilterMatchOptions)}.{textFilterMatchOptions} has not been implemented"), }; } internal static Dictionary> ExtractGlobFilterTokens(string filter) { - string[] filterTokens = [.. filter.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())]; + string[] filterTokens = [.. filter.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)]; return filterTokens.GroupBy(t => { char prefix = t.First(); From 61ed4f6cc9be2e4feeea0afd76cae6f39ea68212 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 08:27:29 +0000 Subject: [PATCH 2/2] Drop the SDK's .gitignore rewrite from this change Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZUfnvhMFzj96bfFNzbfpn --- .gitignore | 18 ------------------ 1 file changed, 18 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