| 630 | |
| 631 | @functools.lru_cache(maxsize=128) |
| 632 | def is_inside_quotes(text: str, pos: int) -> Literal[False, 'single', 'double', 'backtick']: |
| 633 | in_single = False |
| 634 | in_double = False |
| 635 | in_backticks = False |
| 636 | escaped = False |
| 637 | doubled_backtick_positions = [] |
| 638 | single_quote = "'" |
| 639 | double_quote = '"' |
| 640 | backtick = '`' |
| 641 | backslash = '\\' |
| 642 | |
| 643 | # scanning the string twice seems to be needed to handle doubled backticks |
| 644 | doubled_backtick_positions = _find_doubled_backticks(text) |
| 645 | |
| 646 | length = len(text) |
| 647 | if pos < 0: |
| 648 | pos = length + pos |
| 649 | pos = max(pos, 0) |
| 650 | pos = min(length, pos) |
| 651 | |
| 652 | # optimization |
| 653 | up_to_pos = text[:pos] |
| 654 | if backtick not in up_to_pos and single_quote not in up_to_pos and double_quote not in up_to_pos: |
| 655 | return False |
| 656 | |
| 657 | for index in range(0, pos): |
| 658 | ch = text[index] |
| 659 | if index in doubled_backtick_positions: |
| 660 | index += 1 |
| 661 | continue |
| 662 | if escaped and (in_double or in_single): |
| 663 | escaped = False |
| 664 | index += 1 |
| 665 | continue |
| 666 | if ch == backslash and (in_double or in_single): |
| 667 | escaped = True |
| 668 | index += 1 |
| 669 | continue |
| 670 | if ch == backtick and not in_double and not in_single: |
| 671 | in_backticks = not in_backticks |
| 672 | elif ch == single_quote and not in_double and not in_backticks: |
| 673 | in_single = not in_single |
| 674 | elif ch == double_quote and not in_single and not in_backticks: |
| 675 | in_double = not in_double |
| 676 | index += 1 |
| 677 | |
| 678 | if in_single: |
| 679 | return 'single' |
| 680 | elif in_double: |
| 681 | return 'double' |
| 682 | elif in_backticks: |
| 683 | return 'backtick' |
| 684 | else: |
| 685 | return False |
| 686 | |
| 687 | |
| 688 | def suggest_type(full_text: str, text_before_cursor: str) -> list[dict[str, Any]]: |