diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py index f668f46..77f69f3 100644 --- a/IPython/core/inputtransformer.py +++ b/IPython/core/inputtransformer.py @@ -193,7 +193,7 @@ def assemble_logical_lines(): line = ''.join(parts) # Utilities -def _make_help_call(target, esc, lspace, next_input=None): +def _make_help_call(target, esc, lspace): """Prepares a pinfo(2)/psearch call from a target name and the escape (i.e. ? or ??)""" method = 'pinfo2' if esc == '??' \ @@ -203,12 +203,13 @@ def _make_help_call(target, esc, lspace, next_input=None): #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args) t_magic_name, _, t_magic_arg_s = arg.partition(' ') t_magic_name = t_magic_name.lstrip(ESC_MAGIC) - if next_input is None: - return '%sget_ipython().run_line_magic(%r, %r)' % (lspace, t_magic_name, t_magic_arg_s) - else: - return '%sget_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \ - (lspace, next_input, t_magic_name, t_magic_arg_s) - + return "%sget_ipython().run_line_magic(%r, %r)" % ( + lspace, + t_magic_name, + t_magic_arg_s, + ) + + # These define the transformations for the different escape characters. def _tr_system(line_info): "Translate lines escaped with: !" @@ -349,10 +350,7 @@ def help_end(line): esc = m.group(3) lspace = _initial_space_re.match(line).group(0) - # If we're mid-command, put it back on the next prompt for the user. - next_input = line.rstrip('?') if line.strip() != m.group(0) else None - - return _make_help_call(target, esc, lspace, next_input) + return _make_help_call(target, esc, lspace) @CoroutineInputTransformer.wrap diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 3a56007..a8f676f 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -325,7 +325,7 @@ ESC_PAREN = '/' # Call first argument with rest of line as arguments ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'} ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately -def _make_help_call(target, esc, next_input=None): +def _make_help_call(target, esc): """Prepares a pinfo(2)/psearch call from a target name and the escape (i.e. ? or ??)""" method = 'pinfo2' if esc == '??' \ @@ -335,11 +335,8 @@ def _make_help_call(target, esc, next_input=None): #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args) t_magic_name, _, t_magic_arg_s = arg.partition(' ') t_magic_name = t_magic_name.lstrip(ESC_MAGIC) - if next_input is None: - return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s) - else: - return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \ - (next_input, t_magic_name, t_magic_arg_s) + return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s) + def _tr_help(content): """Translate lines escaped with: ? @@ -480,13 +477,8 @@ class HelpEnd(TokenTransformBase): target = m.group(1) esc = m.group(3) - # If we're mid-command, put it back on the next prompt for the user. - next_input = None - if (not lines_before) and (not lines_after) \ - and content.strip() != m.group(0): - next_input = content.rstrip('?\n') - call = _make_help_call(target, esc, next_input=next_input) + call = _make_help_call(target, esc) new_line = indent + call + '\n' return lines_before + [new_line] + lines_after diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py index 4de97b8..bfc936d 100644 --- a/IPython/core/tests/test_inputtransformer.py +++ b/IPython/core/tests/test_inputtransformer.py @@ -59,108 +59,93 @@ syntax = \ ('x=1', 'x=1'), # normal input is unmodified (' ',' '), # blank lines are kept intact ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"), - ], - - classic_prompt = - [('>>> x=1', 'x=1'), - ('x=1', 'x=1'), # normal input is unmodified - (' ', ' '), # blank lines are kept intact - ], - - ipy_prompt = - [('In [1]: x=1', 'x=1'), - ('x=1', 'x=1'), # normal input is unmodified - (' ',' '), # blank lines are kept intact - ], - - # Tests for the escape transformer to leave normal code alone - escaped_noesc = - [ (' ', ' '), - ('x=1', 'x=1'), - ], - - # System calls - escaped_shell = - [ ('!ls', "get_ipython().system('ls')"), - # Double-escape shell, this means to capture the output of the - # subprocess and return it - ('!!ls', "get_ipython().getoutput('ls')"), - ], - - # Help/object info - escaped_help = - [ ('?', 'get_ipython().show_usage()'), - ('?x1', "get_ipython().run_line_magic('pinfo', 'x1')"), - ('??x2', "get_ipython().run_line_magic('pinfo2', 'x2')"), - ('?a.*s', "get_ipython().run_line_magic('psearch', 'a.*s')"), - ('?%hist1', "get_ipython().run_line_magic('pinfo', '%hist1')"), - ('?%%hist2', "get_ipython().run_line_magic('pinfo', '%%hist2')"), - ('?abc = qwe', "get_ipython().run_line_magic('pinfo', 'abc')"), - ], - - end_help = - [ ('x3?', "get_ipython().run_line_magic('pinfo', 'x3')"), - ('x4??', "get_ipython().run_line_magic('pinfo2', 'x4')"), - ('%hist1?', "get_ipython().run_line_magic('pinfo', '%hist1')"), - ('%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"), - ('%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"), - ('%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"), - ('π.foo?', "get_ipython().run_line_magic('pinfo', 'π.foo')"), - ('f*?', "get_ipython().run_line_magic('psearch', 'f*')"), - ('ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"), - ('a = abc?', "get_ipython().set_next_input('a = abc');" - "get_ipython().run_line_magic('pinfo', 'abc')"), - ('a = abc.qe??', "get_ipython().set_next_input('a = abc.qe');" - "get_ipython().run_line_magic('pinfo2', 'abc.qe')"), - ('a = *.items?', "get_ipython().set_next_input('a = *.items');" - "get_ipython().run_line_magic('psearch', '*.items')"), - ('plot(a?', "get_ipython().set_next_input('plot(a');" - "get_ipython().run_line_magic('pinfo', 'a')"), - ('a*2 #comment?', 'a*2 #comment?'), - ], - - # Explicit magic calls - escaped_magic = - [ ('%cd', "get_ipython().run_line_magic('cd', '')"), - ('%cd /home', "get_ipython().run_line_magic('cd', '/home')"), - # Backslashes need to be escaped. - ('%cd C:\\User', "get_ipython().run_line_magic('cd', 'C:\\\\User')"), - (' %magic', " get_ipython().run_line_magic('magic', '')"), - ], - - # Quoting with separate arguments - escaped_quote = - [ (',f', 'f("")'), - (',f x', 'f("x")'), - (' ,f y', ' f("y")'), - (',f a b', 'f("a", "b")'), - ], - - # Quoting with single argument - escaped_quote2 = - [ (';f', 'f("")'), - (';f x', 'f("x")'), - (' ;f y', ' f("y")'), - (';f a b', 'f("a b")'), - ], - - # Simply apply parens - escaped_paren = - [ ('/f', 'f()'), - ('/f x', 'f(x)'), - (' /f y', ' f(y)'), - ('/f a b', 'f(a, b)'), - ], - - # Check that we transform prompts before other transforms - mixed = - [ ('In [1]: %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"), - ('>>> %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"), - ('In [2]: !ls', "get_ipython().system('ls')"), - ('In [3]: abs?', "get_ipython().run_line_magic('pinfo', 'abs')"), - ('In [4]: b = %who', "b = get_ipython().run_line_magic('who', '')"), - ], - ) + ], + classic_prompt=[ + (">>> x=1", "x=1"), + ("x=1", "x=1"), # normal input is unmodified + (" ", " "), # blank lines are kept intact + ], + ipy_prompt=[ + ("In [1]: x=1", "x=1"), + ("x=1", "x=1"), # normal input is unmodified + (" ", " "), # blank lines are kept intact + ], + # Tests for the escape transformer to leave normal code alone + escaped_noesc=[ + (" ", " "), + ("x=1", "x=1"), + ], + # System calls + escaped_shell=[ + ("!ls", "get_ipython().system('ls')"), + # Double-escape shell, this means to capture the output of the + # subprocess and return it + ("!!ls", "get_ipython().getoutput('ls')"), + ], + # Help/object info + escaped_help=[ + ("?", "get_ipython().show_usage()"), + ("?x1", "get_ipython().run_line_magic('pinfo', 'x1')"), + ("??x2", "get_ipython().run_line_magic('pinfo2', 'x2')"), + ("?a.*s", "get_ipython().run_line_magic('psearch', 'a.*s')"), + ("?%hist1", "get_ipython().run_line_magic('pinfo', '%hist1')"), + ("?%%hist2", "get_ipython().run_line_magic('pinfo', '%%hist2')"), + ("?abc = qwe", "get_ipython().run_line_magic('pinfo', 'abc')"), + ], + end_help=[ + ("x3?", "get_ipython().run_line_magic('pinfo', 'x3')"), + ("x4??", "get_ipython().run_line_magic('pinfo2', 'x4')"), + ("%hist1?", "get_ipython().run_line_magic('pinfo', '%hist1')"), + ("%hist2??", "get_ipython().run_line_magic('pinfo2', '%hist2')"), + ("%%hist3?", "get_ipython().run_line_magic('pinfo', '%%hist3')"), + ("%%hist4??", "get_ipython().run_line_magic('pinfo2', '%%hist4')"), + ("π.foo?", "get_ipython().run_line_magic('pinfo', 'π.foo')"), + ("f*?", "get_ipython().run_line_magic('psearch', 'f*')"), + ("ax.*aspe*?", "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"), + ("a = abc?", "get_ipython().run_line_magic('pinfo', 'abc')"), + ("a = abc.qe??", "get_ipython().run_line_magic('pinfo2', 'abc.qe')"), + ("a = *.items?", "get_ipython().run_line_magic('psearch', '*.items')"), + ("plot(a?", "get_ipython().run_line_magic('pinfo', 'a')"), + ("a*2 #comment?", "a*2 #comment?"), + ], + # Explicit magic calls + escaped_magic=[ + ("%cd", "get_ipython().run_line_magic('cd', '')"), + ("%cd /home", "get_ipython().run_line_magic('cd', '/home')"), + # Backslashes need to be escaped. + ("%cd C:\\User", "get_ipython().run_line_magic('cd', 'C:\\\\User')"), + (" %magic", " get_ipython().run_line_magic('magic', '')"), + ], + # Quoting with separate arguments + escaped_quote=[ + (",f", 'f("")'), + (",f x", 'f("x")'), + (" ,f y", ' f("y")'), + (",f a b", 'f("a", "b")'), + ], + # Quoting with single argument + escaped_quote2=[ + (";f", 'f("")'), + (";f x", 'f("x")'), + (" ;f y", ' f("y")'), + (";f a b", 'f("a b")'), + ], + # Simply apply parens + escaped_paren=[ + ("/f", "f()"), + ("/f x", "f(x)"), + (" /f y", " f(y)"), + ("/f a b", "f(a, b)"), + ], + # Check that we transform prompts before other transforms + mixed=[ + ("In [1]: %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), + (">>> %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), + ("In [2]: !ls", "get_ipython().system('ls')"), + ("In [3]: abs?", "get_ipython().run_line_magic('pinfo', 'abs')"), + ("In [4]: b = %who", "b = get_ipython().run_line_magic('who', '')"), + ], +) # multiline syntax examples. Each of these should be a list of lists, with # each entry itself having pairs of raw/transformed input. The union (with diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index abc6303..0613dc0 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -14,45 +14,65 @@ import pytest from IPython.core import inputtransformer2 as ipt2 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line -MULTILINE_MAGIC = ("""\ +MULTILINE_MAGIC = ( + """\ a = f() %foo \\ bar g() -""".splitlines(keepends=True), (2, 0), """\ +""".splitlines( + keepends=True + ), + (2, 0), + """\ a = f() get_ipython().run_line_magic('foo', ' bar') g() -""".splitlines(keepends=True)) +""".splitlines( + keepends=True + ), +) -INDENTED_MAGIC = ("""\ +INDENTED_MAGIC = ( + """\ for a in range(5): %ls -""".splitlines(keepends=True), (2, 4), """\ +""".splitlines( + keepends=True + ), + (2, 4), + """\ for a in range(5): get_ipython().run_line_magic('ls', '') -""".splitlines(keepends=True)) +""".splitlines( + keepends=True + ), +) -CRLF_MAGIC = ([ - "a = f()\n", - "%ls\r\n", - "g()\n" -], (2, 0), [ - "a = f()\n", - "get_ipython().run_line_magic('ls', '')\n", - "g()\n" -]) - -MULTILINE_MAGIC_ASSIGN = ("""\ +CRLF_MAGIC = ( + ["a = f()\n", "%ls\r\n", "g()\n"], + (2, 0), + ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"], +) + +MULTILINE_MAGIC_ASSIGN = ( + """\ a = f() b = %foo \\ bar g() -""".splitlines(keepends=True), (2, 4), """\ +""".splitlines( + keepends=True + ), + (2, 4), + """\ a = f() b = get_ipython().run_line_magic('foo', ' bar') g() -""".splitlines(keepends=True)) +""".splitlines( + keepends=True + ), +) MULTILINE_SYSTEM_ASSIGN = ("""\ a = f() @@ -72,68 +92,70 @@ def test(): for i in range(1): print(i) res =! ls -""".splitlines(keepends=True), (4, 7), '''\ +""".splitlines( + keepends=True + ), + (4, 7), + """\ def test(): for i in range(1): print(i) res =get_ipython().getoutput(\' ls\') -'''.splitlines(keepends=True)) +""".splitlines( + keepends=True + ), +) ###### -AUTOCALL_QUOTE = ( - [",f 1 2 3\n"], (1, 0), - ['f("1", "2", "3")\n'] -) +AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n']) -AUTOCALL_QUOTE2 = ( - [";f 1 2 3\n"], (1, 0), - ['f("1 2 3")\n'] -) +AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n']) -AUTOCALL_PAREN = ( - ["/f 1 2 3\n"], (1, 0), - ['f(1, 2, 3)\n'] -) +AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"]) -SIMPLE_HELP = ( - ["foo?\n"], (1, 0), - ["get_ipython().run_line_magic('pinfo', 'foo')\n"] -) +SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"]) DETAILED_HELP = ( - ["foo??\n"], (1, 0), - ["get_ipython().run_line_magic('pinfo2', 'foo')\n"] + ["foo??\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo2', 'foo')\n"], ) -MAGIC_HELP = ( - ["%foo?\n"], (1, 0), - ["get_ipython().run_line_magic('pinfo', '%foo')\n"] -) +MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"]) HELP_IN_EXPR = ( - ["a = b + c?\n"], (1, 0), - ["get_ipython().set_next_input('a = b + c');" - "get_ipython().run_line_magic('pinfo', 'c')\n"] + ["a = b + c?\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo', 'c')\n"], ) -HELP_CONTINUED_LINE = ("""\ +HELP_CONTINUED_LINE = ( + """\ a = \\ zip? -""".splitlines(keepends=True), (1, 0), -[r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] +""".splitlines( + keepends=True + ), + (1, 0), + [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"], ) -HELP_MULTILINE = ("""\ +HELP_MULTILINE = ( + """\ (a, b) = zip? -""".splitlines(keepends=True), (1, 0), -[r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] +""".splitlines( + keepends=True + ), + (1, 0), + [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"], ) HELP_UNICODE = ( - ["π.foo?\n"], (1, 0), - ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"] + ["π.foo?\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"], ) @@ -149,6 +171,7 @@ def test_check_make_token_by_line_never_ends_empty(): Check that not sequence of single or double characters ends up leading to en empty list of tokens """ from string import printable + for c in printable: assert make_tokens_by_line(c)[-1] != [] for k in printable: @@ -156,7 +179,7 @@ def test_check_make_token_by_line_never_ends_empty(): def check_find(transformer, case, match=True): - sample, expected_start, _ = case + sample, expected_start, _ = case tbl = make_tokens_by_line(sample) res = transformer.find(tbl) if match: @@ -166,25 +189,30 @@ def check_find(transformer, case, match=True): else: assert res is None + def check_transform(transformer_cls, case): lines, start, expected = case transformer = transformer_cls(start) assert transformer.transform(lines) == expected + def test_continued_line(): lines = MULTILINE_MAGIC_ASSIGN[0] assert ipt2.find_end_of_continued_line(lines, 1) == 2 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar" + def test_find_assign_magic(): check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False) check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False) + def test_transform_assign_magic(): check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) + def test_find_assign_system(): check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) @@ -192,30 +220,36 @@ def test_find_assign_system(): check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None)) check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False) + def test_transform_assign_system(): check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) + def test_find_magic_escape(): check_find(ipt2.EscapedCommand, MULTILINE_MAGIC) check_find(ipt2.EscapedCommand, INDENTED_MAGIC) check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False) + def test_transform_magic_escape(): check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC) check_transform(ipt2.EscapedCommand, INDENTED_MAGIC) check_transform(ipt2.EscapedCommand, CRLF_MAGIC) + def test_find_autocalls(): for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: print("Testing %r" % case[0]) check_find(ipt2.EscapedCommand, case) + def test_transform_autocall(): for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: print("Testing %r" % case[0]) check_transform(ipt2.EscapedCommand, case) + def test_find_help(): for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]: check_find(ipt2.HelpEnd, case) @@ -233,6 +267,7 @@ def test_find_help(): # Nor in a string check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False) + def test_transform_help(): tf = ipt2.HelpEnd((1, 0), (1, 9)) assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2] @@ -246,10 +281,12 @@ def test_transform_help(): tf = ipt2.HelpEnd((1, 0), (1, 0)) assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2] + def test_find_assign_op_dedent(): """ be careful that empty token like dedent are not counted as parens """ + class Tk: def __init__(self, s): self.string = s @@ -302,21 +339,23 @@ def test_check_complete_param(code, expected, number): def test_check_complete(): cc = ipt2.TransformerManager().check_complete - example = dedent(""" + example = dedent( + """ if True: - a=1""" ) + a=1""" + ) assert cc(example) == ("incomplete", 4) assert cc(example + "\n") == ("complete", None) assert cc(example + "\n ") == ("complete", None) # no need to loop on all the letters/numbers. - short = '12abAB'+string.printable[62:] + short = "12abAB" + string.printable[62:] for c in short: # test does not raise: cc(c) for k in short: - cc(c+k) + cc(c + k) assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2) @@ -371,10 +410,9 @@ def test_null_cleanup_transformer(): assert manager.transform_cell("") == "" - - def test_side_effects_I(): count = 0 + def counter(lines): nonlocal count count += 1 @@ -384,14 +422,13 @@ def test_side_effects_I(): manager = ipt2.TransformerManager() manager.cleanup_transforms.insert(0, counter) - assert manager.check_complete("a=1\n") == ('complete', None) + assert manager.check_complete("a=1\n") == ("complete", None) assert count == 0 - - def test_side_effects_II(): count = 0 + def counter(lines): nonlocal count count += 1 @@ -401,5 +438,5 @@ def test_side_effects_II(): manager = ipt2.TransformerManager() manager.line_transforms.insert(0, counter) - assert manager.check_complete("b=1\n") == ('complete', None) + assert manager.check_complete("b=1\n") == ("complete", None) assert count == 0 diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index b557eb1..24fed4b 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -8,6 +8,11 @@ IPython 8.3.0 ------------- + - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call + ``set_next_input`` as most frontend allow proper multiline editing and it was + causing issues for many users of multi-cell frontends. + + - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on the info object when frontend provide it.