TextAPI’s search engine uses Boyer-Moore-Horspool for plain-text patterns (O(n)) and .NET’s Regex for regex patterns, both operating directly on the piece table with piece-boundary bridging so no full-text copy is needed.
| Navigation: Overview | TextDocument | Examples | Decorations |
using TextAPI.Core.Search;
// Case-insensitive regex find-all
var opts = new SearchOptions { CaseSensitive = false, UseRegex = true };
foreach (var m in doc.FindAll(@"\bTODO\b", opts))
Console.WriteLine($"offset {m.Offset}, length {m.Length}");
// Simple forward scan
SearchMatch? next = doc.FindNext("hello", fromOffset: 0);
// Count occurrences
int n = doc.CountMatches("TODO");
| Property | Type | Default | Description |
|---|---|---|---|
CaseSensitive |
bool |
true |
When false, performs a case-insensitive match. |
WholeWord |
bool |
false |
Match only when surrounded by non-word characters. |
UseRegex |
bool |
false |
Treat the pattern as a .NET regular expression. |
MaxResults |
int |
0 |
Cap the number of results. 0 = unlimited. |
// Plain-text, whole-word, case-insensitive
var opts = new SearchOptions
{
CaseSensitive = false,
WholeWord = true
};
// Regex, limited to first 50 results
var opts2 = new SearchOptions { UseRegex = true, MaxResults = 50 };
| Field | Type | Description |
|---|---|---|
Offset |
int |
Start offset of the match in the document. |
Length |
int |
Character length of the match. |
SearchMatch? m = doc.FindNext("World", 0);
if (m is not null)
{
string text = doc.GetText(m.Value.Offset, m.Value.Length);
Console.WriteLine($"Found: '{text}' at {m.Value.Offset}");
}
| Method | Returns | Description |
|---|---|---|
FindAll(pattern, opts?) |
IEnumerable<SearchMatch> |
All matches in document order. Lazy enumeration. |
FindNext(pattern, fromOffset, opts?) |
SearchMatch? |
First match at or after fromOffset. Null if none. |
FindPrev(pattern, beforeOffset, opts?) |
SearchMatch? |
Last match before beforeOffset. Null if none. |
CountMatches(pattern, opts?) |
int |
Count without materializing matches. O(n). |
FindAll returns an IEnumerable that searches lazily. You can break early or use LINQ without scanning the full document:
// First 10 matches only
var first10 = doc.FindAll("TODO").Take(10).ToList();
// Only matches on even offsets (contrived, but shows LINQ composability)
var even = doc.FindAll("x").Where(m => m.Offset % 2 == 0);
Implement wrap-around search (like Ctrl+F “find next” in an editor):
int caretOffset = cursor.CaretOffset;
string pattern = "hello";
// Try forward from current position
SearchMatch? m = doc.FindNext(pattern, caretOffset + 1);
// Wrap around if not found
m ??= doc.FindNext(pattern, 0);
if (m is not null)
cursor.SetSelection(m.Value.Offset, m.Value.Offset + m.Value.Length);
When UseRegex = true, the pattern is compiled as a .NET Regex. All .NET regex features are supported:
var opts = new SearchOptions { UseRegex = true, CaseSensitive = false };
// Match any ISO date
var dates = doc.FindAll(@"\d{4}-\d{2}-\d{2}", opts);
// Match function declarations
var fns = doc.FindAll(@"^\s*(public|private|protected)\s+\w+\s+\w+\s*\(", opts);
The lower-level TextSearcher operates directly on the piece table. Use it when building a component that doesn’t go through TextDocument:
using TextAPI.Core.Search;
var searcher = new TextSearcher(doc.PieceTable);
SearchMatch? first = searcher.FindFirst("TODO");
IEnumerable<SearchMatch> all = searcher.FindAll("TODO");
SearchMatch? next = searcher.FindNext("TODO", fromOffset: 100);
SearchMatch? prev = searcher.FindPrev("TODO", beforeOffset: 500);
int count = searcher.Count("TODO");
Span<char>.MaxResults to cap results for UI autocomplete or preview scenarios.