t3-code-android-nightly/packages/shared/src/threadSearch.ts
Jono Kemball 12069eefd7
Some checks are pending
CI / Lint (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Build (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test Web (push) Waiting to run
CI / Test Server 1 (push) Waiting to run
CI / Test Server 2 (push) Waiting to run
CI / Test Server 3 (push) Waiting to run
CI / Test Server 4 (push) Waiting to run
CI / Test Server 5 (push) Waiting to run
CI / Test Server 6 (push) Waiting to run
CI / Transfer report artifact (push) Blocked by required conditions
CI / Rust (push) Waiting to run
CI / Mobile Native Changes (push) Waiting to run
CI / Mobile Native Static Analysis (push) Blocked by required conditions
CI / Release Smoke (push) Waiting to run
CI / Check (push) Blocked by required conditions
Forward to Cursor hygiene / POST to Cursor (push) Waiting to run
Deploy T3 Connect relay / Deploy production relay (push) Waiting to run
Mobile EAS Production / EAS production (push) Waiting to run
feat(web): find messages and plans in the current thread (#10439)
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:24:50 -05:00

70 lines
2.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

interface ThreadSearchTextPart {
readonly text: string;
readonly highlighted: boolean;
readonly start: number;
}
function foldThreadSearchCase(text: string): string {
const lowered = text.toLowerCase().replace(/ς/g, "σ");
if (lowered.length === text.length) return lowered;
let folded = "";
for (const character of text) {
const loweredCharacter = character.toLowerCase().replace(/ς/g, "σ");
folded += loweredCharacter.length === character.length ? loweredCharacter : character;
}
return folded;
}
function scanThreadSearchOccurrences(text: string, query: string, offsets?: number[]): number {
if (text.length === 0 || query.length === 0) return 0;
const foldedText = foldThreadSearchCase(text);
const foldedQuery = foldThreadSearchCase(query);
let count = 0;
let cursor = 0;
while (cursor <= foldedText.length - foldedQuery.length) {
const offset = foldedText.indexOf(foldedQuery, cursor);
if (offset === -1) break;
offsets?.push(offset);
count += 1;
cursor = offset + foldedQuery.length;
}
return count;
}
export function countThreadSearchOccurrences(text: string, query: string): number {
return scanThreadSearchOccurrences(text, query);
}
export function findThreadSearchOccurrences(text: string, query: string): number[] {
const offsets: number[] = [];
scanThreadSearchOccurrences(text, query, offsets);
return offsets;
}
export function splitThreadSearchText(text: string, query: string): ThreadSearchTextPart[] {
const normalizedQuery = query.trim();
if (normalizedQuery.length === 0) {
return [{ text, highlighted: false, start: 0 }];
}
const parts: ThreadSearchTextPart[] = [];
let cursor = 0;
for (const offset of findThreadSearchOccurrences(text, normalizedQuery)) {
if (offset > cursor) {
parts.push({ text: text.slice(cursor, offset), highlighted: false, start: cursor });
}
parts.push({
text: text.slice(offset, offset + normalizedQuery.length),
highlighted: true,
start: offset,
});
cursor = offset + normalizedQuery.length;
}
if (cursor < text.length) {
parts.push({ text: text.slice(cursor), highlighted: false, start: cursor });
}
return parts;
}