{"version":3,"sources":["../../../../src/vanilla/algorithm/findFuzzyMatches.ts"],"sourcesContent":["import { isValidWordBoundary } from \"../helpers/isValidBoundary\";\nimport { scoreConsecutiveLetters } from \"../scores/consecutive\";\nimport type { HighlightRanges } from \"../types\";\n\nexport function findFuzzyMatches(\n\tnormalizedItem: string,\n\tnormalizedQuery: string,\n): [number, HighlightRanges] | null {\n\tconst normalizedItemLen = normalizedItem.length;\n\n\t// Match by consecutive letters, but only match beginnings of words or chunks of 3+ letters\n\t// Note that there may be multiple valid ways in which such matching can be done, and we'll only\n\t// match each chunk to the first one found that matches these criteria. It's not perfect as it's\n\t// possible that later chunks will fail to match while there's a better match, for example:\n\t// - query: ABC\n\t// - item: A xABC\n\t//         ^___xx (no match)\n\t//         ___^^^ (better match)\n\t// But we want to limit the algorithmic complexity and this should generally work.\n\n\tconst indices: HighlightRanges = [];\n\tlet queryIdx = 0;\n\tlet queryChar = normalizedQuery[queryIdx];\n\tlet chunkFirstIdx = -1;\n\tlet chunkLastIdx = -2;\n\n\t// eslint-disable-next-line no-constant-condition\n\twhile (true) {\n\t\t// Find match for first letter of chunk\n\t\tconst idx = normalizedItem.indexOf(queryChar, chunkLastIdx + 1);\n\t\tif (idx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Check if chunk starts at word boundary\n\t\tif (idx === 0 || isValidWordBoundary(normalizedItem[idx - 1])) {\n\t\t\tchunkFirstIdx = idx;\n\t\t} else {\n\t\t\t// Else, check if chunk is at least 3+ letters\n\t\t\tconst queryCharsLeft = normalizedQuery.length - queryIdx;\n\t\t\tconst itemCharsLeft = normalizedItem.length - idx;\n\t\t\tconst minimumChunkLen = Math.min(3, queryCharsLeft, itemCharsLeft);\n\t\t\tconst minimumQueryChunk = normalizedQuery.slice(\n\t\t\t\tqueryIdx,\n\t\t\t\tqueryIdx + minimumChunkLen,\n\t\t\t);\n\n\t\t\tif (\n\t\t\t\tnormalizedItem.slice(idx, idx + minimumChunkLen) === minimumQueryChunk\n\t\t\t) {\n\t\t\t\tchunkFirstIdx = idx;\n\t\t\t} else {\n\t\t\t\t// Move index to continue search for valid chunk\n\t\t\t\tchunkLastIdx += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// We have first index of a valid chunk, find its last index\n\t\t// TODO: We could micro-optimize by setting chunkLastIdx earlier if we already know it's len 3 or more\n\t\tfor (\n\t\t\tchunkLastIdx = chunkFirstIdx;\n\t\t\tchunkLastIdx < normalizedItemLen;\n\t\t\tchunkLastIdx += 1\n\t\t) {\n\t\t\tif (normalizedItem[chunkLastIdx] !== queryChar) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tqueryIdx += 1;\n\t\t\tqueryChar = normalizedQuery[queryIdx];\n\t\t}\n\n\t\t// Add chunk to indices\n\t\tchunkLastIdx -= 1; // decrement as we've broken out of loop on non-matching char\n\t\tindices.push([chunkFirstIdx, chunkLastIdx]);\n\n\t\t// Check if we're done\n\t\tif (queryIdx === normalizedQuery.length) {\n\t\t\treturn scoreConsecutiveLetters(indices, normalizedItem);\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"mappings":";AAAA,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAGjC,SAAS,iBACf,gBACA,iBACmC;AACnC,QAAM,oBAAoB,eAAe;AAYzC,QAAM,UAA2B,CAAC;AAClC,MAAI,WAAW;AACf,MAAI,YAAY,gBAAgB,QAAQ;AACxC,MAAI,gBAAgB;AACpB,MAAI,eAAe;AAGnB,SAAO,MAAM;AAEZ,UAAM,MAAM,eAAe,QAAQ,WAAW,eAAe,CAAC;AAC9D,QAAI,QAAQ,IAAI;AACf;AAAA,IACD;AAGA,QAAI,QAAQ,KAAK,oBAAoB,eAAe,MAAM,CAAC,CAAC,GAAG;AAC9D,sBAAgB;AAAA,IACjB,OAAO;AAEN,YAAM,iBAAiB,gBAAgB,SAAS;AAChD,YAAM,gBAAgB,eAAe,SAAS;AAC9C,YAAM,kBAAkB,KAAK,IAAI,GAAG,gBAAgB,aAAa;AACjE,YAAM,oBAAoB,gBAAgB;AAAA,QACzC;AAAA,QACA,WAAW;AAAA,MACZ;AAEA,UACC,eAAe,MAAM,KAAK,MAAM,eAAe,MAAM,mBACpD;AACD,wBAAgB;AAAA,MACjB,OAAO;AAEN,wBAAgB;AAChB;AAAA,MACD;AAAA,IACD;AAIA,SACC,eAAe,eACf,eAAe,mBACf,gBAAgB,GACf;AACD,UAAI,eAAe,YAAY,MAAM,WAAW;AAC/C;AAAA,MACD;AAEA,kBAAY;AACZ,kBAAY,gBAAgB,QAAQ;AAAA,IACrC;AAGA,oBAAgB;AAChB,YAAQ,KAAK,CAAC,eAAe,YAAY,CAAC;AAG1C,QAAI,aAAa,gBAAgB,QAAQ;AACxC,aAAO,wBAAwB,SAAS,cAAc;AAAA,IACvD;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}