| 1292 | return None |
| 1293 | |
| 1294 | def find_fuzzy_matches( |
| 1295 | self, |
| 1296 | last: str, |
| 1297 | text: str, |
| 1298 | collection: Collection[Any], |
| 1299 | ) -> list[tuple[str, int]]: |
| 1300 | completions: list[tuple[str, int]] = [] |
| 1301 | regex = '.{0,3}?'.join(map(re.escape, text)) |
| 1302 | pattern = re.compile(f'({regex})') |
| 1303 | under_words_text = [x for x in text.split('_') if x] |
| 1304 | case_words_text = re.split(_CASE_CHANGE_PAT, last) |
| 1305 | |
| 1306 | for item in collection: |
| 1307 | fuzziness = self.find_fuzzy_match(item, pattern, under_words_text, case_words_text) |
| 1308 | if fuzziness is not None: |
| 1309 | completions.append((item, fuzziness)) |
| 1310 | |
| 1311 | if len(text) >= 4: |
| 1312 | rapidfuzz_matches = rapidfuzz.process.extract( |
| 1313 | text, |
| 1314 | collection, |
| 1315 | scorer=rapidfuzz.fuzz.WRatio, |
| 1316 | # todo: maybe make our own processor which only does case-folding |
| 1317 | # because underscores are valuable info |
| 1318 | processor=rapidfuzz.utils.default_process, |
| 1319 | limit=20, |
| 1320 | score_cutoff=75, |
| 1321 | ) |
| 1322 | existing = {c[0] for c in completions} |
| 1323 | for item, _score, _type in rapidfuzz_matches: |
| 1324 | if len(item) < len(text) / 1.5 or item in existing: |
| 1325 | continue |
| 1326 | completions.append((item, Fuzziness.RAPIDFUZZ)) |
| 1327 | |
| 1328 | return completions |
| 1329 | |
| 1330 | def find_perfect_matches( |
| 1331 | self, |