Repository: ipython/ipython

Issue:
paste into ipython stop working with vi mode
this works well:

```
[ins] In [46]: In [41]: s = 'Abc Def Ghi'
[ins] In [47]:
```

this does not work.
```
[ins] In [44]: [ins] In [41]: s = 'Abc Def Ghi'                       
          ...:                 
          ...:                                                        
  File "<ipython-input-44-0af6a6a3f6c4>", line 1
    [ins] In [41]: s = 'Abc Def Ghi'
          ^
SyntaxError: invalid syntax
```

is it possible to configure ipython to also accept transcript under vi mode?

Discussion:

Patch:
diff --git a/IPython/__init__.py b/IPython/__init__.py
index 0b182bd84..4fb771076 100644
--- a/IPython/__init__.py
+++ b/IPython/__init__.py
@@ -68,19 +68,20 @@
 
 def embed_kernel(module=None, local_ns=None, **kwargs):
     """Embed and start an IPython kernel in a given scope.
-
+    
     If you don't want the kernel to initialize the namespace
     from the scope of the surrounding function,
     and/or you want to load full IPython configuration,
     you probably want `IPython.start_kernel()` instead.
-
+    
     Parameters
     ----------
     module : types.ModuleType, optional
         The module to load into IPython globals (default: caller)
     local_ns : dict, optional
         The namespace to load into IPython user namespace (default: caller)
-    **kwargs : various, optional
+    
+    kwargs : various, optional
         Further keyword args are relayed to the IPKernelApp constructor,
         allowing configuration of the Kernel.  Will only have an effect
         on the first embed_kernel call for a given process.
@@ -98,25 +99,26 @@ def embed_kernel(module=None, local_ns=None, **kwargs):
 
 def start_ipython(argv=None, **kwargs):
     """Launch a normal IPython instance (as opposed to embedded)
-
+    
     `IPython.embed()` puts a shell in a particular calling scope,
     such as a function or method for debugging purposes,
     which is often not desirable.
-
+    
     `start_ipython()` does full, regular IPython initialization,
     including loading startup files, configuration, etc.
     much of which is skipped by `embed()`.
-
+    
     This is a public API method, and will survive implementation changes.
-
+    
     Parameters
     ----------
+    
     argv : list or None, optional
         If unspecified or None, IPython will parse command-line options from sys.argv.
         To prevent any command-line parsing, pass an empty list: `argv=[]`.
     user_ns : dict, optional
         specify this dictionary to initialize the IPython user namespace with particular values.
-    **kwargs : various, optional
+    kwargs : various, optional
         Any other kwargs will be passed to the Application constructor,
         such as `config`.
     """
@@ -125,23 +127,24 @@ def start_ipython(argv=None, **kwargs):
 
 def start_kernel(argv=None, **kwargs):
     """Launch a normal IPython kernel instance (as opposed to embedded)
-
+    
     `IPython.embed_kernel()` puts a shell in a particular calling scope,
     such as a function or method for debugging purposes,
     which is often not desirable.
-
+    
     `start_kernel()` does full, regular IPython initialization,
     including loading startup files, configuration, etc.
     much of which is skipped by `embed()`.
-
+    
     Parameters
     ----------
+    
     argv : list or None, optional
         If unspecified or None, IPython will parse command-line options from sys.argv.
         To prevent any command-line parsing, pass an empty list: `argv=[]`.
     user_ns : dict, optional
         specify this dictionary to initialize the IPython user namespace with particular values.
-    **kwargs : various, optional
+    kwargs : various, optional
         Any other kwargs will be passed to the Application constructor,
         such as `config`.
     """
diff --git a/IPython/core/application.py b/IPython/core/application.py
index 30bff844a..2aae06241 100644
--- a/IPython/core/application.py
+++ b/IPython/core/application.py
@@ -254,7 +254,7 @@ def unset_crashhandler():
     
     def excepthook(self, etype, evalue, tb):
         """this is sys.excepthook after init_crashhandler
-
+        
         set self.verbose_crash=True to use our full crashhandler, instead of
         a regular traceback with a short message (crash_handler_lite)
         """
diff --git a/IPython/core/autocall.py b/IPython/core/autocall.py
index 5f7720bb4..bab7f859c 100644
--- a/IPython/core/autocall.py
+++ b/IPython/core/autocall.py
@@ -41,9 +41,9 @@ def __init__(self, ip=None):
     
     def set_ip(self, ip):
         """ Will be used to set _ip point to current ipython instance b/f call
-
+        
         Override this method if you don't want this to happen.
-
+        
         """
         self._ip = ip
 
diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py
index fd7ac9ab4..50672a195 100644
--- a/IPython/core/compilerop.py
+++ b/IPython/core/compilerop.py
@@ -118,12 +118,12 @@ def get_code_name(self, raw_code, transformed_code, number):
         Parameters
         ----------
         raw_code : str
-            The raw cell code.
+          The raw cell code.
         transformed_code : str
-            The executable Python source code to cache and compile.
+          The executable Python source code to cache and compile.
         number : int
-            A number which forms part of the code's name. Used for the execution
-            counter.
+          A number which forms part of the code's name. Used for the execution
+          counter.
 
         Returns
         -------
@@ -137,12 +137,12 @@ def cache(self, transformed_code, number=0, raw_code=None):
         Parameters
         ----------
         transformed_code : str
-            The executable Python source code to cache and compile.
+          The executable Python source code to cache and compile.
         number : int
-            A number which forms part of the code's name. Used for the execution
-            counter.
+          A number which forms part of the code's name. Used for the execution
+          counter.
         raw_code : str
-            The raw code before transformation, if None, set to `transformed_code`.
+          The raw code before transformation, if None, set to `transformed_code`.
 
         Returns
         -------
diff --git a/IPython/core/completer.py b/IPython/core/completer.py
index 9237929f9..78bc57e12 100644
--- a/IPython/core/completer.py
+++ b/IPython/core/completer.py
@@ -431,9 +431,9 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC:
 
     Parameters
     ----------
-    text : str
+    text: str
         text that should be completed.
-    completions : Iterator[Completion]
+    completions: Iterator[Completion]
         iterator over the completions to deduplicate
 
     Yields
@@ -474,9 +474,9 @@ def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC:
 
     Parameters
     ----------
-    text : str
+    text: str
         text that should be completed.
-    completions : Iterator[Completion]
+    completions: Iterator[Completion]
         iterator over the completions to rectify
 
     Notes
@@ -763,13 +763,13 @@ def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], pre
 
     Parameters
     ----------
-    keys
+    keys:
         list of keys in dictionary currently being completed.
-    prefix
+    prefix:
         Part of the text already typed by the user. E.g. `mydict[b'fo`
-    delims
+    delims:
         String of delimiters to consider when finding the current key.
-    extra_prefix : optional
+    extra_prefix: optional
         Part of the text already typed in multi-key index cases. E.g. for
         `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
 
@@ -993,7 +993,7 @@ def _formatparamchildren(parameter) -> str:
 
     Parameters
     ----------
-    parameter
+    parameter:
         Jedi's function `Param`
 
     Returns
@@ -1013,7 +1013,7 @@ def _make_signature(completion)-> str:
 
     Parameters
     ----------
-    completion : jedi.Completion
+    completion: jedi.Completion
         object does not complete a function type
 
     Returns
@@ -1854,9 +1854,9 @@ def completions(self, text: str, offset: int)->Iterator[Completion]:
 
         Parameters
         ----------
-        text : str
+        text:str
             Full text of the current input, multi line string.
-        offset : int
+        offset:int
             Integer representing the position of the cursor in ``text``. Offset
             is 0-based indexed.
 
diff --git a/IPython/core/crashhandler.py b/IPython/core/crashhandler.py
index 181fc1579..2cfe85ce2 100644
--- a/IPython/core/crashhandler.py
+++ b/IPython/core/crashhandler.py
@@ -101,19 +101,25 @@ def __init__(self, app, contact_name=None, contact_email=None,
 
         Parameters
         ----------
-        app : Application
+        app :  Application
             A running :class:`Application` instance, which will be queried at
             crash time for internal information.
+
         contact_name : str
             A string with the name of the person to contact.
+
         contact_email : str
             A string with the email address of the contact.
+
         bug_tracker : str
             A string with the URL for your project's bug tracker.
+
         show_crash_traceback : bool
             If false, don't print the crash traceback on stderr, only generate
             the on-disk report
-        Non-argument instance attributes
+
+        Non-argument instance attributes:
+
         These instances contain some non-argument attributes which allow for
         further customization of the crash handler's behavior. Please see the
         source for further details.
diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py
index 0f4e6f99d..b0e62cab9 100644
--- a/IPython/core/debugger.py
+++ b/IPython/core/debugger.py
@@ -109,6 +109,7 @@ def __init__(self, colors=None):
 
         Parameters
         ----------
+
         colors : str, optional
             The name of the color scheme to use, it must be one of IPython's
             valid color schemes.  If not given, the function will default to
diff --git a/IPython/core/display_functions.py b/IPython/core/display_functions.py
index aab2b7e71..ab5777a71 100644
--- a/IPython/core/display_functions.py
+++ b/IPython/core/display_functions.py
@@ -62,7 +62,7 @@ def publish_display_data(data, metadata=None, source=None, *, transient=None, **
         Unused.
     transient : dict, keyword-only
         A dictionary of transient data, such as display_id.
-    """
+        """
     from IPython.core.interactiveshell import InteractiveShell
 
     display_pub = InteractiveShell.instance().display_pub
@@ -133,11 +133,12 @@ def display(
     clear : bool, optional
         Should the output area be cleared before displaying anything? If True,
         this will wait for additional output before clearing. [default: False]
-    **kwargs : additional keyword-args, optional
+    kwargs: additional keyword-args, optional
         Additional keyword-arguments are passed through to the display publisher.
 
     Returns
     -------
+
     handle: DisplayHandle
         Returns a handle on updatable displays for use with :func:`update_display`,
         if `display_id` is given. Returns :any:`None` if no `display_id` is given
@@ -145,6 +146,7 @@ def display(
 
     Examples
     --------
+
     >>> class Json(object):
     ...     def __init__(self, json):
     ...         self.json = json
@@ -183,10 +185,12 @@ def display(
 
     See Also
     --------
+
     :func:`update_display`
 
     Notes
     -----
+
     In Python, objects can declare their textual representation using the
     `__repr__` method. IPython expands on this idea and allows objects to declare
     other, rich representations including:
@@ -296,13 +300,15 @@ def update_display(obj, *, display_id, **kwargs):
 
     Parameters
     ----------
-    obj
+
+    obj:
         The object with which to update the display
-    display_id : keyword-only
+    display_id: keyword-only
         The id of the display to update
 
     See Also
     --------
+
     :func:`display`
     """
     kwargs['update'] = True
@@ -337,9 +343,10 @@ def display(self, obj, **kwargs):
 
         Parameters
         ----------
-        obj
+
+        obj:
             object to display
-        **kwargs
+        **kwargs:
             additional keyword arguments passed to display
         """
         display(obj, display_id=self.display_id, **kwargs)
@@ -349,9 +356,10 @@ def update(self, obj, **kwargs):
 
         Parameters
         ----------
-        obj
+
+        obj:
             object to display
-        **kwargs
+        **kwargs:
             additional keyword arguments passed to update_display
         """
         update_display(obj, display_id=self.display_id, **kwargs)
diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index c7deab83b..fefc523d7 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -89,7 +89,30 @@ def __call__(self, lines):
     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/docs/source/whatsnew/pr/vi-prompt-strip.rst b/docs/source/whatsnew/pr/vi-prompt-strip.rst
new file mode 100644
index 000000000..e642a4d1f
--- /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 "<ipython-input-1-727bb88eaf33>", 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``


Test patch:
diff --git a/IPython/core/tests/test_inputtransformer2_line.py b/IPython/core/tests/test_inputtransformer2_line.py
index 263bbd9ee..8643a46ce 100644
--- a/IPython/core/tests/test_inputtransformer2_line.py
+++ b/IPython/core/tests/test_inputtransformer2_line.py
@@ -61,10 +61,50 @@ def test_classic_prompt():
     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 @@ def test_leading_empty_lines():
 
 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)