1313import ast
1414from codeop import CommandCompiler , Compile
1515import re
16+ import sys
1617import tokenize
1718from typing import List , Tuple , Optional , Any
1819import warnings
1920
21+ from IPython .utils import tokenutil
22+
2023_indent_re = re .compile (r'^[ \t]+' )
2124
2225def leading_empty_lines (lines ):
@@ -269,9 +272,7 @@ def transform(self, lines: List[str]):
269272class SystemAssign (TokenTransformBase ):
270273 """Transformer for assignments from system commands (a = !foo)"""
271274 @classmethod
272- def find (cls , tokens_by_line ):
273- """Find the first system assignment (a = !foo) in the cell.
274- """
275+ def find_pre_312 (cls , tokens_by_line ):
275276 for line in tokens_by_line :
276277 assign_ix = _find_assign_op (line )
277278 if (assign_ix is not None ) \
@@ -287,6 +288,25 @@ def find(cls, tokens_by_line):
287288 break
288289 ix += 1
289290
291+ @classmethod
292+ def find_post_312 (cls , tokens_by_line ):
293+ for line in tokens_by_line :
294+ assign_ix = _find_assign_op (line )
295+ if (assign_ix is not None ) \
296+ and not line [assign_ix ].line .strip ().startswith ('=' ) \
297+ and (len (line ) >= assign_ix + 2 ) \
298+ and (line [assign_ix + 1 ].type == tokenize .OP ) \
299+ and (line [assign_ix + 1 ].string == '!' ):
300+ return cls (line [assign_ix + 1 ].start )
301+
302+ @classmethod
303+ def find (cls , tokens_by_line ):
304+ """Find the first system assignment (a = !foo) in the cell.
305+ """
306+ if sys .version_info < (3 , 12 ):
307+ return cls .find_pre_312 (tokens_by_line )
308+ return cls .find_post_312 (tokens_by_line )
309+
290310 def transform (self , lines : List [str ]):
291311 """Transform a system assignment found by the ``find()`` classmethod.
292312 """
@@ -511,7 +531,8 @@ def make_tokens_by_line(lines:List[str]):
511531 )
512532 parenlev = 0
513533 try :
514- for token in tokenize .generate_tokens (iter (lines ).__next__ ):
534+ for token in tokenutil .generate_tokens_catch_errors (iter (lines ).__next__ ,
535+ extra_errors_to_catch = ['expected EOF' ]):
515536 tokens_by_line [- 1 ].append (token )
516537 if (token .type == NEWLINE ) \
517538 or ((token .type == NL ) and (parenlev <= 0 )):
@@ -677,9 +698,13 @@ def check_complete(self, cell: str):
677698 if not lines :
678699 return 'complete' , None
679700
680- if lines [- 1 ].endswith ('\\ ' ):
681- # Explicit backslash continuation
682- return 'incomplete' , find_last_indent (lines )
701+ for line in reversed (lines ):
702+ if not line .strip ():
703+ continue
704+ elif line .strip ('\n ' ).endswith ('\\ ' ):
705+ return 'incomplete' , find_last_indent (lines )
706+ else :
707+ break
683708
684709 try :
685710 for transform in self .cleanup_transforms :
@@ -717,7 +742,8 @@ def check_complete(self, cell: str):
717742 if not tokens_by_line :
718743 return 'incomplete' , find_last_indent (lines )
719744
720- if tokens_by_line [- 1 ][- 1 ].type != tokenize .ENDMARKER :
745+ if (tokens_by_line [- 1 ][- 1 ].type != tokenize .ENDMARKER
746+ and tokens_by_line [- 1 ][- 1 ].type != tokenize .ERRORTOKEN ):
721747 # We're in a multiline string or expression
722748 return 'incomplete' , find_last_indent (lines )
723749
0 commit comments