Skip to content

No way to ask for case-insensitive matching, which blocks two adoption issues #97

Description

@matt-edmondson

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

  • A caller can ask for case-insensitive matching on the glob path, and *.jpg matches IMG_1234.JPG
  • The same setting works on the regex path
  • Default behaviour is unchanged — every existing test passes untouched
  • Cache entries for the two sensitivities do not collide

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions