Skip to content

LT-22524: Add substring search mode to StringSearcher - #395

Merged
thejambi merged 4 commits into
masterfrom
LT-22524
Aug 25, 2026
Merged

LT-22524: Add substring search mode to StringSearcher#395
thejambi merged 4 commits into
masterfrom
LT-22524

Conversation

@thejambi

@thejambi thejambi commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Add a Substring value to SearchType that matches the query anywhere within a string, case- and diacritic-insensitive, backed by a raw-string index scanned with CompareInfo.IndexOf. The existing Exact/Prefix/FullText modes are unchanged. This change enables a change in FieldWorks to use this new SearchType, see sillsdev/FieldWorks#1069

https://jira.sil.org/browse/LT-22524


This change is Reviewable

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

LCM Tests

    16 files  ± 0      16 suites  ±0   2m 15s ⏱️ +9s
 2 880 tests + 5   2 860 ✅ + 5   20 💤 ±0  0 ❌ ±0 
11 468 runs  +20  11 300 ✅ +20  168 💤 ±0  0 ❌ ±0 

Results for commit 07083ee. ± Comparison against base commit 9fdb060.

♻️ This comment has been updated with latest results.

Add a Substring value to SearchType that matches the query anywhere
within a string, case- and diacritic-insensitive, backed by a
raw-string index scanned with CompareInfo.IndexOf. The existing
Exact/Prefix/FullText modes are unchanged.
@thejambi
thejambi marked this pull request as ready for review August 17, 2026 17:32
return Enumerable.Empty<T>();
CompareInfo ci = CultureInfo.InvariantCulture.CompareInfo;
return raw.Where(kv => ci.IndexOf(kv.Value, text,
CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0).Select(kv => kv.Key);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might need to consider if this will work correctly for us, or if we need to create a manged wrapper in icu-dotnet for string search so we can use what ICU provides for this use case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this is the same behavior as FWLite's search, so hopefully it will be a good way to go?

I did find another behavior to include, see the "Fold diacritics only when the search term has none" change here.

@hahn-kev hahn-kev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. I left some suggestions, nothing blocking.

One thing, since the issue specifically calls out matching how FW Lite search works, here's some of our tests which would be relevant here
https://github.com/sillsdev/languageforge-lexbox/blob/bbcc058f267a789302b9570e9542d30968199a3f/backend/FwLite/MiniLcm.Tests/QueryEntryTestsBase.cs#L412-L455

Up to you how closely you try to match what FW Lite does.

Comment thread src/SIL.LCModel.Core/Text/StringSearcher.cs Outdated
Comment thread src/SIL.LCModel.Core/Text/StringSearcher.cs Outdated
Comment thread src/SIL.LCModel.Core/Text/StringSearcher.cs Outdated
@thejambi

Copy link
Copy Markdown
Contributor Author

Thank you! I'm looking into getting some of the enhancements you suggested in.

Zachary Burnham and others added 2 commits August 18, 2026 17:41
- Build the sort-key index only in the search types that use it, so
  Substring no longer creates and discards an unused index (in both Add
  and Search).
- Skip indexing null or empty text in Add instead of coercing it to an
  empty string.
- Replace KeyValuePair<T, string> in the raw index with a named
  SubstringEntry struct for readability.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
- Fold diacritics for substring only when the query itself has none, so an
  unmarked query matches accented text but an accented query is specific.
  Added tests inspired by FWLite mirroring its SuccessfulMatches/NegativeMatches
  and NFC/NFD cases.

Co-Authored-By: Claude Opus <noreply@anthropic.com>

@thejambi thejambi left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thejambi made 3 comments.
Reviewable status: 0 of 2 files reviewed, 4 unresolved discussions (waiting on hahn-kev).

Comment thread src/SIL.LCModel.Core/Text/StringSearcher.cs Outdated
Comment thread src/SIL.LCModel.Core/Text/StringSearcher.cs Outdated
return Enumerable.Empty<T>();
CompareInfo ci = CultureInfo.InvariantCulture.CompareInfo;
return raw.Where(kv => ci.IndexOf(kv.Value, text,
CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0).Select(kv => kv.Key);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this is the same behavior as FWLite's search, so hopefully it will be a good way to go?

I did find another behavior to include, see the "Fold diacritics only when the search term has none" change here.

}

public T Item { get { return m_item; } }
public string Text { get { return m_text; } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could simplify this to public string Text => m_text;

@hahn-kev hahn-kev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Looks great, I just left one minor suggestion, feel free to ignore it.

@thejambi thejambi left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thejambi reviewed 2 files and all commit messages.
Reviewable status: 0 of 2 files reviewed, 5 unresolved discussions (waiting on hahn-kev).

The substring search compared strings with .NET's InvariantCulture
CompareInfo, which does not treat a composed character and its decomposed
form as canonically equal on every runtime. Mono (net462 on Linux) missed
such matches, failing SubstringMatch_isNormalizationInsensitive in CI while
Windows and .NET 8 passed.

Normalize both the stored text and the query to NFD so we always compare
same-form strings. The diacritic-folding behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thejambi
thejambi merged commit 580866f into master Aug 25, 2026
4 of 5 checks passed
@thejambi
thejambi deleted the LT-22524 branch August 25, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants