1/** 2 * Fuzzy match a pattern against a string. 3 * 4 * @param pattern The pattern to search for. 5 * @param text The string to search in. 6 * 7 * Returns a score greater than zero if all characters of `pattern` can be 8 * found in order in `string`. For lowercase characters in `pattern` match both 9 * lower and upper case, for uppercase only an exact match counts. 10 */ 11export function fuzzytest(pattern: string, text: string): number { 12 const casesensitive = pattern === pattern.toLowerCase(); 13 const exact = casesensitive 14 ? text.toLowerCase().indexOf(pattern) 15 : text.indexOf(pattern); 16 if (exact > -1) { 17 return pattern.length ** 2; 18 } 19 let score = 0; 20 let localScore = 0; 21 let pindex = 0; 22 for (let index = 0; index < text.length; index += 1) { 23 const char = text[index]; 24 const search = pattern[pindex]; 25 if (char === search || char.toLowerCase() === search) { 26 pindex += 1; 27 localScore += +1; 28 } else { 29 localScore = 0; 30 } 31 score += localScore; 32 } 33 return pindex === pattern.length ? score : 0; 34} 35 36/** 37 * Filter a list of possible suggestions to only those that match the pattern 38 */ 39export function fuzzyfilter(pattern: string, suggestions: string[]): string[] { 40 if (!pattern) { 41 return suggestions; 42 } 43 return suggestions 44 .map((s): [string, number] => [s, fuzzytest(pattern, s)]) 45 .filter(([, score]) => score) 46 .sort((a, b) => b[1] - a[1]) 47 .map(([s]) => s); 48} 49 50/** 51 * Wrap fuzzy matched characters. 52 * 53 * Wrap all occurences of characters of `pattern` (in order) in `string` in 54 * <span> tags. 55 */ 56export function fuzzywrap(pattern: string, text: string): string { 57 if (!pattern) { 58 return text; 59 } 60 const casesensitive = pattern === pattern.toLowerCase(); 61 const exact = casesensitive 62 ? text.toLowerCase().indexOf(pattern) 63 : text.indexOf(pattern); 64 if (exact > -1) { 65 const before = text.slice(0, exact); 66 const match = text.slice(exact, exact + pattern.length); 67 const after = text.slice(exact + pattern.length); 68 return `${before}<span>${match}</span>${after}`; 69 } 70 let pindex = 0; 71 let inMatch = false; 72 const result = []; 73 for (let index = 0; index < text.length; index += 1) { 74 const char = text[index]; 75 const search = pattern[pindex]; 76 if (char === search || char.toLowerCase() === search) { 77 if (!inMatch) { 78 result.push("<span>"); 79 inMatch = true; 80 } 81 result.push(char); 82 pindex += 1; 83 } else { 84 if (inMatch) { 85 result.push("</span>"); 86 inMatch = false; 87 } 88 result.push(char); 89 } 90 } 91 if (inMatch) { 92 result.push("</span>"); 93 } 94 return result.join(""); 95} 96