diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 886b9cd..4dcbdc3 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -1535,6 +1535,39 @@ static int ScoreOf(string s) Assert.IsNotEmpty(result); } + [TestMethod] + [DataRow("foo bar", "foo bar")] + [DataRow("my file.txt", "my file.txt")] + [DataRow("my file.txt", " my file.txt ")] + [DataRow("my file.txt", "my *.txt")] + public void GlobByWholeStringMatchesTextContainingASpace(string text, string filter) + { + Assert.IsTrue(TextFilter.IsMatch(text, filter, TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + } + + [TestMethod] + public void GlobByWholeStringWithASpaceStillNeedsTheWholeTextToMatch() + { + Assert.IsFalse(TextFilter.IsMatch("foo bar baz", "foo bar", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("foo bar", "foo bar", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("Foo bar", "foo bar", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsTrue(TextFilter.IsMatch("Foo Bar", "foo bar", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString, TextFilterCaseSensitivity.CaseInsensitive)); + } + + [TestMethod] + public void GlobByWholeStringKeepsMultiplePatternsAndPrefixedTokens() + { + // #113: each pattern is still tried on its own. + Assert.IsTrue(TextFilter.IsMatch("a.png", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("a.gif", "*.jpg *.png", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + + // A filter with an excluded or required token is not tried whole, so those tokens behave as before. + Assert.IsFalse(TextFilter.IsMatch("a.png", "*.png -a.*", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsTrue(TextFilter.IsMatch("b.png", "*.png -a.*", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsTrue(TextFilter.IsMatch("a.png", "*.png +a.*", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + Assert.IsFalse(TextFilter.IsMatch("b.png", "*.png +a.*", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + } + // Assemblies compiled against 1.6 or earlier bind to these exact signatures, so each must still // exist in the binary even though source callers now resolve to the case-sensitivity overloads. [TestMethod] diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 16701d8..d1d4a15 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -347,6 +347,20 @@ public static bool DoesMatchGlob(string text, string filter, TextFilterMatchOpti return true; // empty filter matches all text } + // The filter is split on whitespace, so under ByWholeString no single token can equal a text that + // contains a space ("my file.txt"). With only plain tokens, try the whole filter as one glob first; + // the per-token pass below still runs, so "*.jpg *.png" keeps matching "a.png" (#113). + string wholeFilter = filter.Trim(); + if (textFilterMatchOptions is TextFilterMatchOptions.ByWholeString + && filterTokens.Count == 1 + && filterTokens.ContainsKey(TextFilterTokenType.Optional) + && wholeFilter.Any(char.IsWhiteSpace) + && ResolveGlob(wholeFilter, caseSensitivity) is Glob[] wholeFilterGlobs + && IsGlobMatch(wholeFilterGlobs, text)) + { + return true; + } + if (!filterTokens.TryGetValue(TextFilterTokenType.Excluded, out HashSet? excludedTokens)) { excludedTokens = [];