What's missing
TextFilter matches case-sensitively at every TextFilterMatchOptions value, and exposes no way to ask for anything else. DotNet.Glob — the library underneath the glob path — supports case-insensitivity perfectly well; TextFilter just never passes the option through.
Measured at ddb65d4, against DotNet.Glob 3.1.3 (the pinned version):
TextFilter.IsMatch("IMG_1234.JPG", "*.jpg", Glob, ByWholeString) -> False
TextFilter.IsMatch("IMG_1234.JPG", "*.jpg", Glob, ByWordAny) -> False
TextFilter.IsMatch("IMG_1234.JPG", "*.jpg", Glob, ByWordAll) -> False
Glob.Parse("*.jpg").IsMatch("IMG_1234.JPG") -> False
Glob.Parse("*.jpg", caseInsensitive).IsMatch("IMG_1234.JPG") -> True
So the capability is one GlobOptions away and there is no caller-reachable route to it.
Why it matters
This is not hypothetical — it is the recorded blocker on two separate adoption issues, reached independently by two investigations a day apart:
ktsu-dev/GitLfsCache#33 measured TextFilter's glob semantics against its own RepositoryAllowListTests and found segment semantics an exact match on every case except this one. Its conclusion: "Best unblocker would be upstream: a case-insensitivity option on TextFilter.IsMatch's glob path would remove the larger of the two workarounds and make this close to a drop-in."
ktsu-dev/ImGuiApp#409 found the same wall from the other side, and worse: adopting TextFilter in the file-open dialog would stop *.jpg listing IMG_1234.JPG — which is what a camera writes. A user-visible regression, for a cleanup with no user-visible upside. Its conclusion: "There is no case-insensitivity option to reach for... Same gap ktsu-dev/GitLfsCache#33 hit."
Both stopped rather than shipping a ToLowerInvariant layer in the consumer, which is the right call: case-folding at each call site is exactly the hand-rolled code adopting a shared library is supposed to delete.
Suggested shape
An opt-in setting on the public entry points (IsMatch, Filter, DoesMatchGlob, DoesMatchRegex), defaulting to today's case-sensitive behaviour so nothing existing changes. Glob routes it to GlobOptions.Evaluation.CaseInsensitive; regex routes it to RegexOptions.IgnoreCase. The GlobCache/RegexCache keys need to include it, or the two sensitivities collide in the cache.
Acceptance criteria
Two related findings, recorded but not proposed for change here
Both verified at ddb65d4. Neither is asked for above, because neither is clearly a defect:
1. A glob containing a literal space is inexpressible.
TextFilter.IsMatch("a b", "a b", Glob, ByWholeString) -> False
TextFilter.IsMatch("my docs2024.txt", "my docs*.txt", Glob, ByWholeString) -> False
TextFilter.IsMatch("my docs2024.txt", "*.txt", Glob, ByWholeString) -> True [control]
Glob.Parse("my docs*.txt").IsMatch("my docs2024.txt") -> True
ImGuiApp#409 reported this as a wrapper defect. On reading the code I think that framing is wrong and want to correct it: ExtractGlobFilterTokens splits the filter on spaces at every match option because the space is the filter DSL's token separator — GetHint documents the filter as 'optional1* opti?nal2 +required -excluded'. ByWholeString governs how the text is tokenized, not the filter. So the space is inexpressible by design, not by accident, and changing it would break the documented multi-token syntax. Worth a deliberate decision (an escaping or quoting rule?) rather than a drive-by fix.
2. ** does not match the empty string. TextFilter.IsMatch("", "**", Glob, ByWholeString) is False, where GitLfsCache's hand-rolled Translate returns true. Noted in GitLfsCache#33; small, and only observable at the empty string.
What's missing
TextFiltermatches case-sensitively at everyTextFilterMatchOptionsvalue, and exposes no way to ask for anything else.DotNet.Glob— the library underneath the glob path — supports case-insensitivity perfectly well;TextFilterjust never passes the option through.Measured at
ddb65d4, againstDotNet.Glob3.1.3 (the pinned version):So the capability is one
GlobOptionsaway and there is no caller-reachable route to it.Why it matters
This is not hypothetical — it is the recorded blocker on two separate adoption issues, reached independently by two investigations a day apart:
ktsu-dev/GitLfsCache#33measuredTextFilter's glob semantics against its ownRepositoryAllowListTestsand found segment semantics an exact match on every case except this one. Its conclusion: "Best unblocker would be upstream: a case-insensitivity option onTextFilter.IsMatch's glob path would remove the larger of the two workarounds and make this close to a drop-in."ktsu-dev/ImGuiApp#409found the same wall from the other side, and worse: adoptingTextFilterin the file-open dialog would stop*.jpglistingIMG_1234.JPG— which is what a camera writes. A user-visible regression, for a cleanup with no user-visible upside. Its conclusion: "There is no case-insensitivity option to reach for... Same gapktsu-dev/GitLfsCache#33hit."Both stopped rather than shipping a
ToLowerInvariantlayer in the consumer, which is the right call: case-folding at each call site is exactly the hand-rolled code adopting a shared library is supposed to delete.Suggested shape
An opt-in setting on the public entry points (
IsMatch,Filter,DoesMatchGlob,DoesMatchRegex), defaulting to today's case-sensitive behaviour so nothing existing changes. Glob routes it toGlobOptions.Evaluation.CaseInsensitive; regex routes it toRegexOptions.IgnoreCase. TheGlobCache/RegexCachekeys need to include it, or the two sensitivities collide in the cache.Acceptance criteria
*.jpgmatchesIMG_1234.JPGTwo related findings, recorded but not proposed for change here
Both verified at
ddb65d4. Neither is asked for above, because neither is clearly a defect:1. A glob containing a literal space is inexpressible.
ImGuiApp#409reported this as a wrapper defect. On reading the code I think that framing is wrong and want to correct it:ExtractGlobFilterTokenssplits the filter on spaces at every match option because the space is the filter DSL's token separator —GetHintdocuments the filter as'optional1* opti?nal2 +required -excluded'.ByWholeStringgoverns how the text is tokenized, not the filter. So the space is inexpressible by design, not by accident, and changing it would break the documented multi-token syntax. Worth a deliberate decision (an escaping or quoting rule?) rather than a drive-by fix.2.
**does not match the empty string.TextFilter.IsMatch("", "**", Glob, ByWholeString)isFalse, whereGitLfsCache's hand-rolledTranslatereturns true. Noted inGitLfsCache#33; small, and only observable at the empty string.