1interface ThreadSearchTextPart {
2 readonly text: string;
3 readonly highlighted: boolean;
4 readonly start: number;
5}
6
7function foldThreadSearchCase(text: string): string {
8 const lowered = text.toLowerCase().replace(/ς/g, "σ");
9 if (lowered.length === text.length) return lowered;
10
11 let folded = "";
12 for (const character of text) {
13 const loweredCharacter = character.toLowerCase().replace(/ς/g, "σ");
14 folded += loweredCharacter.length === character.length ? loweredCharacter : character;
15 }
16 return folded;
17}
18
19function scanThreadSearchOccurrences(text: string, query: string, offsets?: number[]): number {
20 if (text.length === 0 || query.length === 0) return 0;
21
22 const foldedText = foldThreadSearchCase(text);
23 const foldedQuery = foldThreadSearchCase(query);
24 let count = 0;
25 let cursor = 0;
26
27 while (cursor <= foldedText.length - foldedQuery.length) {
28 const offset = foldedText.indexOf(foldedQuery, cursor);
29 if (offset === -1) break;
30 offsets?.push(offset);
31 count += 1;
32 cursor = offset + foldedQuery.length;
33 }
34 return count;
35}
36
37export function countThreadSearchOccurrences(text: string, query: string): number {
38 return scanThreadSearchOccurrences(text, query);
39}
40
41export function findThreadSearchOccurrences(text: string, query: string): number[] {
42 const offsets: number[] = [];
43 scanThreadSearchOccurrences(text, query, offsets);
44 return offsets;
45}
46
47export function splitThreadSearchText(text: string, query: string): ThreadSearchTextPart[] {
48 const normalizedQuery = query.trim();
49 if (normalizedQuery.length === 0) {
50 return [{ text, highlighted: false, start: 0 }];
51 }
52
53 const parts: ThreadSearchTextPart[] = [];
54 let cursor = 0;
55 for (const offset of findThreadSearchOccurrences(text, normalizedQuery)) {
56 if (offset > cursor) {
57 parts.push({ text: text.slice(cursor, offset), highlighted: false, start: cursor });
58 }
59 parts.push({
60 text: text.slice(offset, offset + normalizedQuery.length),
61 highlighted: true,
62 start: offset,
63 });
64 cursor = offset + normalizedQuery.length;
65 }
66 if (cursor < text.length) {
67 parts.push({ text: text.slice(cursor), highlighted: false, start: cursor });
68 }
69 return parts;
70}