|
17 | 17 | re.IGNORECASE, |
18 | 18 | ) |
19 | 19 |
|
| 20 | +# missing because not binary |
| 21 | +# BETWEEN |
| 22 | +# CASE |
| 23 | +# missing because parens are used |
| 24 | +# IN(), and others |
| 25 | +# unary operands might need to have another set |
| 26 | +# not, !, ~ |
| 27 | +# arrow operators only take a literal on the right |
| 28 | +# and so might need different treatment |
| 29 | +# := might also need a different context |
| 30 | +# sqlparse would call these identifiers, so they are excluded |
| 31 | +# xor |
| 32 | +# these are hitting the recursion guard, and so not completing after |
| 33 | +# so we might as well leave them out: |
| 34 | +# is, 'is not', mod |
| 35 | +# sqlparse might also parse "not null" together |
| 36 | +# should also verify how sqlparse parses every space-containing case |
| 37 | +BINARY_OPERANDS = { |
| 38 | + '&', '>', '>>', '>=', '<', '<>', '!=', '<<', '<=', '<=>', '%', |
| 39 | + '*', '+', '-', '->', '->>', '/', ':=', '=', '^', 'and', '&&', 'div', |
| 40 | + 'like', 'not like', 'not regexp', 'or', '||', 'regexp', 'rlike', |
| 41 | + 'sounds like', '|', |
| 42 | +} # fmt: skip |
| 43 | + |
20 | 44 |
|
21 | 45 | def _enum_value_suggestion(text_before_cursor: str, full_text: str) -> dict[str, Any] | None: |
22 | 46 | match = _ENUM_VALUE_RE.search(text_before_cursor) |
@@ -333,8 +357,6 @@ def suggest_based_on_last_token( |
333 | 357 | else: |
334 | 358 | token_v = token.value.lower() |
335 | 359 |
|
336 | | - is_operand = lambda x: x and any(x.endswith(op) for op in ["+", "-", "*", "/"]) # noqa: E731 |
337 | | - |
338 | 360 | if not token: |
339 | 361 | return [{"type": "keyword"}, {"type": "special"}] |
340 | 362 |
|
@@ -512,11 +534,19 @@ def suggest_based_on_last_token( |
512 | 534 | elif is_inside_quotes(text_before_cursor, -1) in ['single', 'double']: |
513 | 535 | return [] |
514 | 536 |
|
515 | | - elif token_v.endswith(",") or is_operand(token_v) or token_v in ["=", "and", "or"]: |
| 537 | + elif token_v.endswith(",") or token_v in BINARY_OPERANDS: |
516 | 538 | original_text = text_before_cursor |
517 | 539 | prev_keyword, text_before_cursor = find_prev_keyword(text_before_cursor) |
518 | 540 | enum_suggestion = _enum_value_suggestion(original_text, full_text) |
519 | | - fallback = suggest_based_on_last_token(prev_keyword, text_before_cursor, None, full_text, identifier) if prev_keyword else [] |
| 541 | + |
| 542 | + # guard against non-progressing parser rewinds, which can otherwise |
| 543 | + # recurse forever on some operator shapes. |
| 544 | + if prev_keyword and text_before_cursor.rstrip() != original_text.rstrip(): |
| 545 | + fallback = suggest_based_on_last_token(prev_keyword, text_before_cursor, None, full_text, identifier) |
| 546 | + else: |
| 547 | + # perhaps this fallback should include columns |
| 548 | + fallback = [{"type": "keyword"}] |
| 549 | + |
520 | 550 | if enum_suggestion and _is_where_or_having(prev_keyword): |
521 | 551 | return [enum_suggestion] + fallback |
522 | 552 | return fallback |
|
0 commit comments