Repository: ipython/ipython

Issue:
Auto-suggestion (matching brackets code maybe ?) creates broken code even on trival statements
This is in a fresh `python:latest` docker container where I installed the latest ipython version with pip.

```
Python 3.10.1 (main, Dec 21 2021, 09:01:08) [GCC 10.2.1 20210110]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.0.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: foo = 'bar''
```

I typed `foo = 'bar'` and it added a second single quote at the end, which is obviously invalid. The same happens with double quotes, so as much as I dislike this black integration, it seems unrelated since it happens at input time and not only when I run the code.


Discussion:
I think currently autosuggestion only pick up things from history that you had already typed, regardless of wether they are valid or not. Maybe we should only suggest Commands that did not raise. 
That's why I used a fresh docker container with no history etc whatsoever ;)
> That's why I used a fresh docker container with no history etc whatsoever ;)

Haha. 

I just avoid writing code, it's easier. 
Just in case it was misunderstood, my point was that it happens with an empty history as well, so I'd call that a bug :)

In any case, I think when I type something and just press enter it should not autocomplete anything? There's the ctrl+r search for that anyway... maybe TAB could autocomplete history instead of using ENTER for it?
Hum that is strange, it's probably not autosuggestion, it's likely something else, and it's definitely a bug. Sorry for misunderstanding. 
https://asciinema.org/a/KOAWLkIXYUTTIuRuZFFFUjYc8
Yeah, I can see it, I think it's one of those lines:

https://github.com/ipython/ipython/blob/c0abea7a6dfe52c1f74c9d0387d4accadba7cc14/IPython/terminal/shortcuts.py#L142-L200
-

On Sat, 15 Jan 2022 at 20:56, Adrian ***@***.***> wrote:

> https://asciinema.org/a/KOAWLkIXYUTTIuRuZFFFUjYc8
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/ipython/ipython/issues/13465#issuecomment-1013744795>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACR5TZNIDLLEAZDQJZDVSDUWHGNFANCNFSM5MA32NYA>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>

I just went through the code and I couldn't understand that if the bug persists, why does it occur only inside docker containers?. Because the regex should work in the same way.  On my linux OS, the bug is not present.
Also, as per the clip, following the text, it changed the single quote to double, after you pressed Enter.
I looked into this bug. #13479 helped me understand it better.
I don't really understand why, but the auto_match flag is not used in the `raw string` section in `shortcuts.py`:
https://github.com/ipython/ipython/blob/46a51ed69cdf41b4333943d9ceeb945c4ede5668/IPython/terminal/shortcuts.py#L152-L191
So when auto_match is off, this causes the regex `r".*(r|R)$"` to add a character (`'` for example) when inserting the first one just after `r` or `R`, so `r''` and `R""` will happen while `f''` won't.
The same happens with `(`, `{` and `[`: in `r"()"` the `)` will be inserted automatically, but not here: `f"()"`.
Honestly, I have no idea why this regex doesn't catch `r` and `R` when using auto_match, and why (r|R) is there to begin with.

Adding the `auto_match` flag to the decorators in the `raw string` section fixes this issue, but I want to make sure this is the right thing to do.
This was pretty hard to explain, so I hope it was clear enough.

Resolving patch:
diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py
--- a/IPython/terminal/shortcuts.py
+++ b/IPython/terminal/shortcuts.py
@@ -150,7 +150,9 @@ def _(event):
         event.current_buffer.cursor_left()
 
     # raw string
-    @kb.add("(", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
+    @kb.add(
+        "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
+    )
     def _(event):
         matches = re.match(
             r".*(r|R)[\"'](-*)",
@@ -160,7 +162,9 @@ def _(event):
         event.current_buffer.insert_text("()" + dashes)
         event.current_buffer.cursor_left(len(dashes) + 1)
 
-    @kb.add("[", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
+    @kb.add(
+        "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
+    )
     def _(event):
         matches = re.match(
             r".*(r|R)[\"'](-*)",
@@ -170,7 +174,9 @@ def _(event):
         event.current_buffer.insert_text("[]" + dashes)
         event.current_buffer.cursor_left(len(dashes) + 1)
 
-    @kb.add("{", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
+    @kb.add(
+        "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
+    )
     def _(event):
         matches = re.match(
             r".*(r|R)[\"'](-*)",
@@ -180,12 +186,12 @@ def _(event):
         event.current_buffer.insert_text("{}" + dashes)
         event.current_buffer.cursor_left(len(dashes) + 1)
 
-    @kb.add('"', filter=focused_insert & preceding_text(r".*(r|R)$"))
+    @kb.add('"', filter=focused_insert & auto_match & preceding_text(r".*(r|R)$"))
     def _(event):
         event.current_buffer.insert_text('""')
         event.current_buffer.cursor_left()
 
-    @kb.add("'", filter=focused_insert & preceding_text(r".*(r|R)$"))
+    @kb.add("'", filter=focused_insert & auto_match & preceding_text(r".*(r|R)$"))
     def _(event):
         event.current_buffer.insert_text("''")
         event.current_buffer.cursor_left()


Test patch:
diff --git a/IPython/tests/cve.py b/IPython/tests/cve.py
--- a/IPython/tests/cve.py
+++ b/IPython/tests/cve.py
@@ -11,46 +11,55 @@
 import subprocess
 import time
 
+
 def test_cve_2022_21699():
     """
     Here we test CVE-2022-21699.
 
-    We create a temporary directory, cd into it. 
-    Make a profile file that should not be executed and start IPython in a subprocess, 
+    We create a temporary directory, cd into it.
+    Make a profile file that should not be executed and start IPython in a subprocess,
     checking for the value.
 
 
 
     """
 
-    dangerous_profile_dir = Path('profile_default')
+    dangerous_profile_dir = Path("profile_default")
 
-    dangerous_startup_dir = dangerous_profile_dir / 'startup'
-    dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)])
+    dangerous_startup_dir = dangerous_profile_dir / "startup"
+    dangerous_expected = "CVE-2022-21699-" + "".join(
+        [random.choice(string.ascii_letters) for i in range(10)]
+    )
 
     with TemporaryWorkingDirectory() as t:
         dangerous_startup_dir.mkdir(parents=True)
-        (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")')
+        (dangerous_startup_dir / "foo.py").write_text(f'print("{dangerous_expected}")')
         # 1 sec to make sure FS is flushed.
-        #time.sleep(1)
-        cmd = [sys.executable,'-m', 'IPython']
+        # time.sleep(1)
+        cmd = [sys.executable, "-m", "IPython"]
         env = os.environ.copy()
-        env['IPY_TEST_SIMPLE_PROMPT'] = '1'
-
+        env["IPY_TEST_SIMPLE_PROMPT"] = "1"
 
         # First we fake old behavior, making sure the profile is/was actually dangerous
-        p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE,
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        p_dangerous = subprocess.Popen(
+            cmd + [f"--profile-dir={dangerous_profile_dir}"],
+            env=env,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        )
         out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
         assert dangerous_expected in out_dangerous.decode()
 
         # Now that we know it _would_ have been dangerous, we test it's not loaded
-        p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE,
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        p = subprocess.Popen(
+            cmd,
+            env=env,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        )
         out, err = p.communicate(b"exit\r")
-        assert b'IPython' in out
+        assert b"IPython" in out
         assert dangerous_expected not in out.decode()
-        assert err == b''
-
-
-
+        assert err == b""