Skip to content

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

Open
thejambi wants to merge 3 commits into
masterfrom
LT-22524
Open

LT-22524: Add substring search mode to StringSearcher#395
thejambi wants to merge 3 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

    8 files   -     8      8 suites   - 8   1m 4s ⏱️ - 1m 2s
2 880 tests +    5  2 837 ✅  -    18  43 💤 +23  0 ❌ ±0 
5 734 runs   - 5 714  5 648 ✅  - 5 632  86 💤  - 82  0 ❌ ±0 

Results for commit 3296b5c. ± Comparison against base commit 9fdb060.

This pull request skips 23 tests.
SIL.LCModel.Utils.FileUtilsTest ‑ ActualFilePath_DirectoryNameComposedFilenameExistsWithDifferentCase_Linux
SIL.LCModel.Utils.FileUtilsTest ‑ ActualFilePath_DirectoryNameDecomposedFilenameExistsWithDifferentCase_Linux
SIL.LCModel.Utils.FileUtilsTest ‑ ActualFilePath_DirectoryNameExactMatchFilenameExistsWithDifferentCase_Linux
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeLinuxPathIfWindows_inLinux_unchanged
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinuxPreservingPrefix_doesNotStartWithPrefixAndHasLowercaseDrive_works
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinuxPreservingPrefix_lowercaseDrive_works
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinux_DoubleDriveLetters_changed
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinux_OneBackslash_changed
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinux_arbitraryDrive_error
SIL.LCModel.Utils.FileUtilsTest ‑ ChangeWindowsPathIfLinux_driveOnlyLowercase_changed
…

♻️ 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 <[email protected]>
- 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 <[email protected]>

@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.

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