218218 EvaluationContext ,
219219 _validate_policy_overrides ,
220220)
221- from IPython .core .error import TryNext
221+ from IPython .core .error import TryNext , UsageError
222222from IPython .core .inputtransformer2 import ESC_MAGIC
223223from IPython .core .latex_symbols import latex_symbols , reverse_latex_symbol
224224from IPython .testing .skipdoctest import skip_doctest
@@ -1219,6 +1219,10 @@ def _strip_code_before_operator(self, code: str) -> str:
12191219 else :
12201220 return code
12211221
1222+ def _extract_code (self , line : str ):
1223+ """No-op in Completer, but can be used in subclasses to customise behaviour"""
1224+ return line
1225+
12221226 def _attr_matches (
12231227 self , text : str , include_prefix : bool = True
12241228 ) -> tuple [Sequence [str ], str ]:
@@ -2244,6 +2248,57 @@ def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
22442248 "suppress" : False ,
22452249 }
22462250
2251+ def _extract_code (self , line : str ) -> str :
2252+ """Extract code from magics if any."""
2253+
2254+ if not line :
2255+ return line
2256+ maybe_magic , * rest = line .split (maxsplit = 1 )
2257+ if not rest :
2258+ return line
2259+ args = rest [0 ]
2260+ known_magics = self .shell .magics_manager .lsmagic ()
2261+ line_magics = known_magics ["line" ]
2262+ magic_name = maybe_magic .lstrip (self .magic_escape )
2263+ if magic_name not in line_magics :
2264+ return line
2265+
2266+ if not maybe_magic .startswith (self .magic_escape ):
2267+ all_variables = [* self .namespace .keys (), * self .global_namespace .keys ()]
2268+ if magic_name in all_variables :
2269+ # short circuit if we see a line starting with say `time`
2270+ # but time is defined as a variable (in addition to being
2271+ # a magic). In these cases users need to use explicit `%time`.
2272+ return line
2273+
2274+ magic_method = line_magics [magic_name ]
2275+
2276+ try :
2277+ if magic_name == "timeit" :
2278+ opts , stmt = magic_method .__self__ .parse_options (
2279+ args ,
2280+ "n:r:tcp:qov:" ,
2281+ posix = False ,
2282+ strict = False ,
2283+ preserve_non_opts = True ,
2284+ )
2285+ return stmt
2286+ elif magic_name == "prun" :
2287+ opts , stmt = magic_method .__self__ .parse_options (
2288+ args , "D:l:rs:T:q" , list_all = True , posix = False
2289+ )
2290+ return stmt
2291+ elif hasattr (magic_method , "parser" ) and getattr (
2292+ magic_method , "has_arguments" , False
2293+ ):
2294+ # e.g. %debug, %time
2295+ args , extra = magic_method .parser .parse_argstring (args , partial = True )
2296+ return " " .join (extra )
2297+ except UsageError :
2298+ return line
2299+
2300+ return line
2301+
22472302 @context_matcher ()
22482303 def magic_matcher (self , context : CompletionContext ) -> SimpleMatcherResult :
22492304 """Match magics."""
@@ -2255,7 +2310,7 @@ def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
22552310 line_magics = lsm ['line' ]
22562311 cell_magics = lsm ['cell' ]
22572312 pre = self .magic_escape
2258- pre2 = pre + pre
2313+ pre2 = pre + pre
22592314
22602315 explicit_magic = text .startswith (pre )
22612316
@@ -2619,6 +2674,7 @@ def _is_in_string_or_comment(self, text):
26192674 def python_matcher (self , context : CompletionContext ) -> SimpleMatcherResult :
26202675 """Match attributes or global python names"""
26212676 text = context .text_until_cursor
2677+ text = self ._extract_code (text )
26222678 completion_type = self ._determine_completion_context (text )
26232679 if completion_type == self ._CompletionContextType .ATTRIBUTE :
26242680 try :
@@ -3476,7 +3532,7 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
34763532 full_text = full_text ,
34773533 cursor_position = cursor_pos ,
34783534 cursor_line = cursor_line ,
3479- token = text ,
3535+ token = self . _extract_code ( text ) ,
34803536 limit = MATCHES_LIMIT ,
34813537 )
34823538
0 commit comments