diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index c7deab8..fefc523 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -89,7 +89,30 @@ classic_prompt = PromptStripper( initial_re=re.compile(r'^>>>( |$)') ) -ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')) +ipython_prompt = PromptStripper( + re.compile( + r""" + ^( # Match from the beginning of a line, either: + + # 1. First-line prompt: + ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there + In\ # The 'In' of the prompt, with a space + \[\d+\]: # Command index, as displayed in the prompt + \ # With a mandatory trailing space + + | # ... or ... + + # 2. The three dots of the multiline prompt + \s* # All leading whitespace characters + \.{3,}: # The three (or more) dots + \ ? # With an optional trailing space + + ) + """, + re.VERBOSE, + ) +) + def cell_magic(lines): if not lines or not lines[0].startswith('%%'): diff --git a/IPython/core/tests/test_inputtransformer2_line.py b/IPython/core/tests/test_inputtransformer2_line.py index 263bbd9..8643a46 100644 --- a/IPython/core/tests/test_inputtransformer2_line.py +++ b/IPython/core/tests/test_inputtransformer2_line.py @@ -61,10 +61,50 @@ for a in range(5): print(a ** 2) """) + +IPYTHON_PROMPT_VI_INS = ( + """\ +[ins] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + +IPYTHON_PROMPT_VI_NAV = ( + """\ +[nav] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + + def test_ipython_prompt(): - for sample, expected in [IPYTHON_PROMPT, IPYTHON_PROMPT_L2]: - nt.assert_equal(ipt2.ipython_prompt(sample.splitlines(keepends=True)), - expected.splitlines(keepends=True)) + for sample, expected in [ + IPYTHON_PROMPT, + IPYTHON_PROMPT_L2, + IPYTHON_PROMPT_VI_INS, + IPYTHON_PROMPT_VI_NAV, + ]: + nt.assert_equal( + ipt2.ipython_prompt(sample.splitlines(keepends=True)), + expected.splitlines(keepends=True), + ) + INDENT_SPACES = ("""\ if True: @@ -123,4 +163,4 @@ CRLF_MAGIC = ([ def test_crlf_magic(): for sample, expected in [CRLF_MAGIC]: - nt.assert_equal(ipt2.cell_magic(sample), expected) \ No newline at end of file + nt.assert_equal(ipt2.cell_magic(sample), expected) diff --git a/docs/source/whatsnew/pr/vi-prompt-strip.rst b/docs/source/whatsnew/pr/vi-prompt-strip.rst new file mode 100644 index 0000000..e642a4d --- /dev/null +++ b/docs/source/whatsnew/pr/vi-prompt-strip.rst @@ -0,0 +1,29 @@ +Automatic Vi prompt stripping +============================= + +When pasting code into IPython, it will strip the leading prompt characters if +there are any. For example, you can paste the following code into the console - +it will still work, even though each line is prefixed with prompts (`In`, +`Out`):: + + In [1]: 2 * 2 == 4 + Out[1]: True + + In [2]: print("This still works as pasted") + + +Previously, this was not the case for the Vi-mode prompts:: + + In [1]: [ins] In [13]: 2 * 2 == 4 + ...: Out[13]: True + ...: + File "", line 1 + [ins] In [13]: 2 * 2 == 4 + ^ + SyntaxError: invalid syntax + +This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are +skipped just as the normal ``In`` would be. + +IPython shell can be started in the Vi mode using ``ipython +--TerminalInteractiveShell.editing_mode=vi``