diff --git a/TextFilter.Test/GlobDefaultOptionsTests.cs b/TextFilter.Test/GlobDefaultOptionsTests.cs
new file mode 100644
index 0000000..136ee36
--- /dev/null
+++ b/TextFilter.Test/GlobDefaultOptionsTests.cs
@@ -0,0 +1,42 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace TextFilter.Test;
+
+using DotNet.Globbing;
+using ktsu.TextFilter;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// TextFilter's case-sensitive globs stay case-sensitive when the host flips DotNet.Glob's
+/// process-wide (ktsu-dev/TextFilter#188).
+///
+[TestClass]
+public class GlobDefaultOptionsTests
+{
+ // Patterns no other test uses, so none of them is already in TextFilter's glob cache when the
+ // default is flipped and each is parsed for the first time under it.
+ [TestMethod]
+ [DoNotParallelize]
+ public void CaseSensitiveGlobIgnoresCaseInsensitiveGlobalDefault()
+ {
+ bool original = GlobOptions.Default.Evaluation.CaseInsensitive;
+ GlobOptions.Default.Evaluation.CaseInsensitive = true;
+ try
+ {
+ Assert.IsFalse(TextFilter.IsMatch("IMG.JPG188", "*.jpg188"),
+ "the default sensitivity should still be case-sensitive");
+ Assert.IsFalse(TextFilter.IsMatch("IMG.PNG188", "*.png188", TextFilterType.Glob,
+ TextFilterMatchOptions.ByWordAny, TextFilterCaseSensitivity.CaseSensitive),
+ "an explicit CaseSensitive should still be case-sensitive");
+ Assert.IsTrue(TextFilter.IsMatch("IMG.GIF188", "-*.gif188"),
+ "an exclusion should not hide an item that differs only in case");
+ Assert.IsTrue(TextFilter.IsMatch("IMG.BMP188", "*.bmp188", TextFilterType.Glob,
+ TextFilterMatchOptions.ByWordAny, TextFilterCaseSensitivity.CaseInsensitive),
+ "CaseInsensitive should still ignore case");
+ }
+ finally
+ {
+ GlobOptions.Default.Evaluation.CaseInsensitive = original;
+ }
+ }
+}
diff --git a/TextFilter.Test/ReadmeTests.cs b/TextFilter.Test/ReadmeTests.cs
index 7ced909..17fc9c2 100644
--- a/TextFilter.Test/ReadmeTests.cs
+++ b/TextFilter.Test/ReadmeTests.cs
@@ -4,6 +4,7 @@ namespace TextFilter.Test;
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -89,7 +90,10 @@ public void EveryMethodInTheReadmeApiReferenceExistsWithItsDocumentedDefaults()
foreach (Match row in rows)
{
string methodName = row.Groups["method"].Value;
- MethodInfo[] overloads = [.. methods.Where(m => m.Name == methodName)];
+ // The binary-compatibility overloads are hidden from IntelliSense and deliberately carry no
+ // defaults, since defaults on both sets would make calls ambiguous. The README documents the
+ // visible overloads, so only those are held to its defaults.
+ MethodInfo[] overloads = [.. methods.Where(m => m.Name == methodName && !IsHiddenFromCallers(m))];
if (overloads.Length == 0)
{
problems.Add($"{methodName} does not exist");
@@ -115,6 +119,9 @@ public void EveryMethodInTheReadmeApiReferenceExistsWithItsDocumentedDefaults()
Assert.IsEmpty(problems, string.Join("; ", problems));
}
+ private static bool IsHiddenFromCallers(MethodInfo method) =>
+ method.GetCustomAttribute()?.State == EditorBrowsableState.Never;
+
[TestMethod]
public void ReadmeMatchOptionsSampleGivesTheDocumentedResults()
{
diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs
index 16701d8..058e4bc 100644
--- a/TextFilter/TextFilter.cs
+++ b/TextFilter/TextFilter.cs
@@ -117,6 +117,10 @@ private static void AddBounded(ConcurrentDictionary cach
private static string CacheKey(string pattern, TextFilterCaseSensitivity caseSensitivity) =>
caseSensitivity is TextFilterCaseSensitivity.CaseInsensitive ? "i:" + pattern : "s:" + pattern;
+ // Both sensitivities pass their own options. Glob.Parse without options reads DotNet.Glob's static,
+ // mutable GlobOptions.Default, so a host that sets GlobOptions.Default.Evaluation.CaseInsensitive
+ // would otherwise turn TextFilter's case-sensitive globs case-insensitive.
+ private static readonly GlobOptions CaseSensitiveGlobOptions = new() { Evaluation = { CaseInsensitive = false } };
private static readonly GlobOptions CaseInsensitiveGlobOptions = new() { Evaluation = { CaseInsensitive = true } };
// Filter patterns are caller-supplied text, so a pattern with catastrophic backtracking would
@@ -672,7 +676,7 @@ private static bool SpansPathSeparator(char first, char last) =>
.Where(pattern => pattern.Length != 0)
.Select(pattern => caseSensitivity is TextFilterCaseSensitivity.CaseInsensitive
? Glob.Parse(pattern, CaseInsensitiveGlobOptions)
- : Glob.Parse(pattern))];
+ : Glob.Parse(pattern, CaseSensitiveGlobOptions))];
globs = parsed is { Length: > 0 } ? parsed : null;
}
catch (Exception ex) when (ex is not OutOfMemoryException)