Repository: ipython/ipython

Issue:
Regression in 9.0.1 from 8.33.0 - No longer dedents inputs.
Running `ipython` in the terminal if I copy and paste this block of code:

```python
    # Comment
    x = 1
```

with the leading indentation on both lines, in 8.33.0 it works fine. IPython recognizes the code has consistent leading whitespace, presumably dedents it under the hood, and executes it.

```python
In [1]:     # Comment
   ...:     x = 1

In [2]: 
```

However, in 9.0.1

```python
In [1]:     # Comment
   ...:     x = 1
   ...: 
  Cell In[1], line 2
    x = 1
    ^
IndentationError: unexpected indent

```

I'm not sure if this was intentional or not, but this is a major usability issue for me. My entire workflow is based around copy/pasting chunks of code. I've been working this way for 15 years. I've [written libraries](https://github.com/Erotemic/vimtk) around it. There is a [PR awaiting merge](https://github.com/python/cpython/pull/103998) in CPython to add this dedent feature to "python -c". 

If this is intentional, I beg the maintainers to reconsider. Otherwise, this is a major bug.

Tested on Ubuntu 24.04 on CPython 3.13.2 and 3.11.2


Discussion:
This is not intentional.

I'm not sure I'll have the time to bisect though.
I can help with that. 

The bad commit is: 2d3f2c619d8ad56a41e3d31f41526d28ab999b56

I will see if it is an easy fix.

Interestingly the indentation needs to start with a comment for the bug to trigger:

```
# This works
ipython -c "
    x = 1
    print(x)
    "

# But this raises an error 
ipython -c "
    # Hello World
    x = 1
    print(x)
    "
```

The offending MR is: https://github.com/ipython/ipython/pull/14721 and corresponding issue is: https://github.com/ipython/ipython/issues/14242

Looking into this, it looks that the test added in the offending MR isn't being run correctly: https://github.com/ipython/ipython/blob/main/tests/test_inputtransformer2_line.py#L185

They define a new test case, but don't actually test it. 



Patch:
diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index e2157d636d..4f9bcfe6de 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -17,6 +17,7 @@
 import tokenize
 from typing import List, Tuple, Optional, Any
 import warnings
+from textwrap import dedent
 
 from IPython.utils import tokenutil
 
@@ -38,18 +39,11 @@ def leading_empty_lines(lines):
 def leading_indent(lines):
     """Remove leading indentation.
 
-    If the first line starts with a spaces or tabs, the same whitespace will be
-    removed from each following line in the cell.
+    Removes the minimum common leading indentation from all lines.
     """
     if not lines:
         return lines
-    m = _indent_re.match(lines[0])
-    if not m or lines[0].strip().startswith("#"):
-        return lines
-    space = m.group(0)
-    n = len(space)
-    return [l[n:] if l.startswith(space) else l
-            for l in lines]
+    return dedent("".join(lines)).splitlines(keepends=True)
 
 class PromptStripper:
     """Remove matching input prompts from a block of input.


Test patch:
diff --git a/tests/test_inputtransformer2_line.py b/tests/test_inputtransformer2_line.py
index ae94f58b80..c861656bc3 100644
--- a/tests/test_inputtransformer2_line.py
+++ b/tests/test_inputtransformer2_line.py
@@ -4,6 +4,8 @@
 more complex. See test_inputtransformer2 for tests for token-based transformers.
 """
 
+import pytest
+
 from IPython.core import inputtransformer2 as ipt2
 
 CELL_MAGIC = (
@@ -164,7 +166,7 @@ def test_leading_indent():
     """\
     # comment
 if True:
-   a = 3
+    a = 3
 """,
 )
 
@@ -182,11 +184,28 @@ def test_leading_indent():
 )
 
 
-def test_leading_indent():
-    for sample, expected in [INDENT_SPACES, INDENT_TABS]:
-        assert ipt2.leading_indent(
-            sample.splitlines(keepends=True)
-        ) == expected.splitlines(keepends=True)
+INDENTED_CODE_WITH_ALIGNED_COMMENT = (
+    """\
+    # comment
+    x = 1
+    print(x)
+""",
+    """\
+# comment
+x = 1
+print(x)
+""",
+)
+
+
+@pytest.mark.parametrize(
+    "sample, expected",
+    [INDENT_SPACES_COMMENT, INDENT_TABS_COMMENT, INDENTED_CODE_WITH_ALIGNED_COMMENT],
+)
+def test_leading_indent_comment(sample, expected):
+    assert ipt2.leading_indent(sample.splitlines(keepends=True)) == expected.splitlines(
+        keepends=True
+    )
 
 
 LEADING_EMPTY_LINES = (