Skip to content

Commit 812a262

Browse files
committed
improve-code-strip-logic
1 parent b9d6e2d commit 812a262

2 files changed

Lines changed: 61 additions & 8 deletions

File tree

IPython/core/completer.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,20 +1134,60 @@ def attr_matches(self, text):
11341134
# we simple attribute matching with normal identifiers.
11351135
_ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$")
11361136

1137+
def _strip_code_before_operator(self, code: str) -> str:
1138+
operators = ["=", "==", "!=", ">=", "<=", ">", "<"]
1139+
o_parens = {"(", "[", "{"}
1140+
c_parens = {")", "]", "}"}
1141+
1142+
# Dry-run tokenize to catch errors
1143+
try:
1144+
_ = list(tokenize.generate_tokens(iter(code.splitlines()).__next__))
1145+
except tokenize.TokenError:
1146+
# Try trimming the expression and retrying
1147+
trimmed_code = self._trim_expr(code)
1148+
try:
1149+
_ = list(
1150+
tokenize.generate_tokens(iter(trimmed_code.splitlines()).__next__)
1151+
)
1152+
code = trimmed_code
1153+
except tokenize.TokenError:
1154+
return code
1155+
1156+
tokens = _parse_tokens(code)
1157+
encountered_operator = False
1158+
after_operator = []
1159+
nesting_level = 0
1160+
1161+
for t in tokens:
1162+
if t.type == tokenize.OP:
1163+
if t.string in o_parens:
1164+
nesting_level += 1
1165+
elif t.string in c_parens:
1166+
nesting_level -= 1
1167+
elif t.string in operators and nesting_level == 0:
1168+
encountered_operator = True
1169+
after_operator = []
1170+
continue
1171+
1172+
if encountered_operator:
1173+
after_operator.append(t.string)
1174+
1175+
if encountered_operator:
1176+
return "".join(after_operator)
1177+
else:
1178+
return code
1179+
11371180
def _attr_matches(
11381181
self, text: str, include_prefix: bool = True
11391182
) -> tuple[Sequence[str], str]:
11401183
m2 = self._ATTR_MATCH_RE.match(text)
11411184
if not m2:
11421185
return [], ""
11431186
expr, attr = m2.group(1, 2)
1144-
1145-
operators = ["=", "==", "!=", ">=", "<=", ">", "<"]
1146-
# Split by the operator and take the right side
1147-
if not (expr.startswith("(") and expr.endswith(")")):
1148-
for op in operators:
1149-
if op in expr:
1150-
expr = expr.split(op)[-1]
1187+
try:
1188+
expr = self._strip_code_before_operator(expr)
1189+
except tokenize.TokenError:
1190+
pass
11511191

11521192
obj = self._evaluate_expr(expr)
11531193

tests/test_completer.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,14 +662,27 @@ def _(line, cursor_pos, expect, message, completion):
662662
"Should have completed on `x.upper() == y.`: %s",
663663
Completion(15, 15, "upper"),
664664
)
665+
_(
666+
"(x.upper() == y.",
667+
16,
668+
".upper",
669+
"Should have completed on `x.upper() == y.`: %s",
670+
Completion(16, 16, "upper"),
671+
)
665672
_(
666673
"(x.upper() == y).",
667674
17,
668675
".bit_length",
669676
"Should have completed on `(x.upper() == y).`: %s",
670677
Completion(17, 17, "bit_length"),
671678
)
672-
679+
_(
680+
"{'==', 'abc'}.",
681+
14,
682+
".add",
683+
"Should have completed on `{'==', 'abc'}.`: %s",
684+
Completion(14, 14, "add"),
685+
)
673686
def test_omit__names(self):
674687
# also happens to test IPCompleter as a configurable
675688
ip = get_ipython()

0 commit comments

Comments
 (0)