Repository: spyder-ide/spyder

Issue:
Editor unindents when typing :
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

Enter the following in an editor (without pressing enter after typing 'else'):

    def foo(x):
        if x == 1:
        else


To do that you would have had to press backspace before typing 'else' because the editor automatically indents the cursor to col 8 after hitting return on the 'if' line. This is all correct behavior.

Now, with the cursor at the end of else, type a colon. At this point the editor unindents the else, and you have

    def foo(x):
        if x == 1:
    else:

In other words, the else is unindented to column 1, when it should be lined up with the if, like so:

    def foo(x):
        if x == 1:
        else:

3.x correctly (IMO) doesn't unindent the else; it should be okay to enter your code in a not entirely linear fashion (in this case I was thinking more about how to implement the logic I needed for the else part, and so I just wanted to ignore coding the case for x==1 for the moment)





## Versions

* Spyder version: 4.0.0b4 
* Python version: 3.7.3
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 7

### Dependencies

```
pygments >=2.0    :  2.4.2 (OK)
qdarkstyle >=2.6.4:  2.7 (OK)
sphinx >=0.6.6    :  2.1.2 (OK)
pyls >=0.27.0     :  0.28.2 (OK)
nbconvert >=4.0   :  5.5.0 (OK)
pandas >=0.13.1   :  0.25.1 (OK)
numpy >=1.7       :  1.16.4 (OK)
sympy >=0.7.3     :  1.4 (OK)
cython >=0.21     :  0.29.13 (OK)
qtconsole >=4.5.2 :  4.5.4 (OK)
IPython >=4.0     :  7.8.0 (OK)
matplotlib >=2.0.0:  3.1.1 (OK)
pylint >=0.25     :  2.3.1 (OK)
```

Editor unindents when typing :
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

Enter the following in an editor (without pressing enter after typing 'else'):

    def foo(x):
        if x == 1:
        else


To do that you would have had to press backspace before typing 'else' because the editor automatically indents the cursor to col 8 after hitting return on the 'if' line. This is all correct behavior.

Now, with the cursor at the end of else, type a colon. At this point the editor unindents the else, and you have

    def foo(x):
        if x == 1:
    else:

In other words, the else is unindented to column 1, when it should be lined up with the if, like so:

    def foo(x):
        if x == 1:
        else:

3.x correctly (IMO) doesn't unindent the else; it should be okay to enter your code in a not entirely linear fashion (in this case I was thinking more about how to implement the logic I needed for the else part, and so I just wanted to ignore coding the case for x==1 for the moment)





## Versions

* Spyder version: 4.0.0b4 
* Python version: 3.7.3
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 7

### Dependencies

```
pygments >=2.0    :  2.4.2 (OK)
qdarkstyle >=2.6.4:  2.7 (OK)
sphinx >=0.6.6    :  2.1.2 (OK)
pyls >=0.27.0     :  0.28.2 (OK)
nbconvert >=4.0   :  5.5.0 (OK)
pandas >=0.13.1   :  0.25.1 (OK)
numpy >=1.7       :  1.16.4 (OK)
sympy >=0.7.3     :  1.4 (OK)
cython >=0.21     :  0.29.13 (OK)
qtconsole >=4.5.2 :  4.5.4 (OK)
IPython >=4.0     :  7.8.0 (OK)
matplotlib >=2.0.0:  3.1.1 (OK)
pylint >=0.25     :  2.3.1 (OK)
```



Discussion:
I think we should probably remove the logic that automatically unindents stuff after writing a colon for certain keywords because it's hard to make this work correctly in all cases with automatic indentation.

@goanpeca, what do you think?
@rlabbe I do not follow the logic of your example, since on Python having a if: and then no logic will trump any other IDE. The "standard" way if leaving empty blocks of codes is to use the `pass` keyword like this:

![if](https://user-images.githubusercontent.com/3627835/64624975-1070a280-d3b1-11e9-96f9-ce03d4cc0bd4.gif)

This behaves like I expect it to, so I am not sure @ccordoba12 there is a real bug here.
@rlabbe is not leaving anything between the `if` and the `else` that's probably what cause the error.
>if and the else that's probably what cause the error.

I know but that is not proper Python, so I do not understand why we should handle that "corner case".

Why should we be required to type code in the order that it eventually will exist? 

I was slightly imprecise in my description of what happened because I tried to boil it down to the simplest case to reproduce the problem. 

What actually happened is I had a few lines of code

    foo(x)
    flip(y)

While debugging I then realized needed a conditional; this code shouldn't always run. So I went and started filling in the code, starting with boilerplate, because I hadn't fully decided how to express the condition or what would happen in the if statement.

You can argue this is some artificial problem as this is clearly not valid Python. But almost all code is not valid while you are typing it. Furthermore Spyder 3.x handles this fine. Finally, this is just one example; I am endlessly fighting the 4.x editor as it unindents on me too far. I'm not going to submit PRs on every different case because I've been told I'm submitting too many bugs. 

I never had this editing problem with 3.x. It's a frustrating experience, whereas 3.x editor was great at this. It's not a 'corner' case, it is what happens when you edit code in anything but a perfectly linear manner. 

Here is another way to reproduce it, in a far more linear manner. Enter:

    def xx():
        if x:
             foo()

Now, with the cursor at the end of the 'foo' line, hit return twice. The cursor is under the 'f' as you would expect. Try to unindent to the 'if' by pressing backspace. You'll end up at column 1, not column 4.






> Finally, this is just one example; I am endlessly fighting the 4.x editor as it unindents on me too far. I'm not going to submit PRs on every different case because I've been told I'm submitting too many bugs.

Please submit them here. I think it's better to have everything unified in a single issue.

----

And by the way, you can keep submitting issues, just not so much per day. I think that was clear enough from my previous messages on the topic. My intention was not to discourage you (as it seems you took it) but to work together in a reasonable way.

The point is not flood us with an amount of issues that we're not able to handle and process, because you simply won't receive the feedback you'd expect from our side due to our limited resources.
@juanis2112, please take a look at this one. To reproduce this problem, please copy this code as is

```python
def foo(x):
    if x == 1:
    else
```

paste it in the editor and write `:` after the `else`. Then you'll see this code after that:

```python
def foo(x):
    if x == 1:
else:
```

Please find what part of our code is doing that (most probably in `spyder/plugins/editor/widgets/codeeditor.py`) and remove it, so that automatic auto-indentation doesn't happen anymore.
I can reproduce this issue with valid Python code:
```
if True:
    if True:
        pass
    
    else
```

Adding `:` after `else` will unindent the code if "Automatically strip trailing spaces on changed lines" is disabled.

If "Automatically strip trailing spaces on changed lines" is enabled AND/OR if there is no empty line before `else`, the indentation is preserved.


 
@steff456, please take a look at this one.
I think we should probably remove the logic that automatically unindents stuff after writing a colon for certain keywords because it's hard to make this work correctly in all cases with automatic indentation.

@goanpeca, what do you think?
@rlabbe I do not follow the logic of your example, since on Python having a if: and then no logic will trump any other IDE. The "standard" way if leaving empty blocks of codes is to use the `pass` keyword like this:

![if](https://user-images.githubusercontent.com/3627835/64624975-1070a280-d3b1-11e9-96f9-ce03d4cc0bd4.gif)

This behaves like I expect it to, so I am not sure @ccordoba12 there is a real bug here.
@rlabbe is not leaving anything between the `if` and the `else` that's probably what cause the error.
>if and the else that's probably what cause the error.

I know but that is not proper Python, so I do not understand why we should handle that "corner case".

Why should we be required to type code in the order that it eventually will exist? 

I was slightly imprecise in my description of what happened because I tried to boil it down to the simplest case to reproduce the problem. 

What actually happened is I had a few lines of code

    foo(x)
    flip(y)

While debugging I then realized needed a conditional; this code shouldn't always run. So I went and started filling in the code, starting with boilerplate, because I hadn't fully decided how to express the condition or what would happen in the if statement.

You can argue this is some artificial problem as this is clearly not valid Python. But almost all code is not valid while you are typing it. Furthermore Spyder 3.x handles this fine. Finally, this is just one example; I am endlessly fighting the 4.x editor as it unindents on me too far. I'm not going to submit PRs on every different case because I've been told I'm submitting too many bugs. 

I never had this editing problem with 3.x. It's a frustrating experience, whereas 3.x editor was great at this. It's not a 'corner' case, it is what happens when you edit code in anything but a perfectly linear manner. 

Here is another way to reproduce it, in a far more linear manner. Enter:

    def xx():
        if x:
             foo()

Now, with the cursor at the end of the 'foo' line, hit return twice. The cursor is under the 'f' as you would expect. Try to unindent to the 'if' by pressing backspace. You'll end up at column 1, not column 4.






> Finally, this is just one example; I am endlessly fighting the 4.x editor as it unindents on me too far. I'm not going to submit PRs on every different case because I've been told I'm submitting too many bugs.

Please submit them here. I think it's better to have everything unified in a single issue.

----

And by the way, you can keep submitting issues, just not so much per day. I think that was clear enough from my previous messages on the topic. My intention was not to discourage you (as it seems you took it) but to work together in a reasonable way.

The point is not flood us with an amount of issues that we're not able to handle and process, because you simply won't receive the feedback you'd expect from our side due to our limited resources.
@juanis2112, please take a look at this one. To reproduce this problem, please copy this code as is

```python
def foo(x):
    if x == 1:
    else
```

paste it in the editor and write `:` after the `else`. Then you'll see this code after that:

```python
def foo(x):
    if x == 1:
else:
```

Please find what part of our code is doing that (most probably in `spyder/plugins/editor/widgets/codeeditor.py`) and remove it, so that automatic auto-indentation doesn't happen anymore.
I can reproduce this issue with valid Python code:
```
if True:
    if True:
        pass
    
    else
```

Adding `:` after `else` will unindent the code if "Automatically strip trailing spaces on changed lines" is disabled.

If "Automatically strip trailing spaces on changed lines" is enabled AND/OR if there is no empty line before `else`, the indentation is preserved.


 
@steff456, please take a look at this one.

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -3982,6 +3982,8 @@ def insert_text(event):
                 ind = lambda txt: len(txt)-len(txt.lstrip())
                 prevtxt = to_text_string(self.textCursor(
                                                 ).block().previous().text())
+                if self.language == 'Python':
+                    prevtxt = prevtxt.rstrip()
                 if ind(leading_text) == ind(prevtxt):
                     self.unindent(force=True)
             insert_text(event)
@@ -3993,6 +3995,8 @@ def insert_text(event):
                 ind = lambda txt: len(txt)-len(txt.lstrip())
                 prevtxt = to_text_string(self.textCursor(
                                                 ).block().previous().text())
+                if self.language == 'Python':
+                    prevtxt = prevtxt.rstrip()
                 if ind(leading_text) == ind(prevtxt):
                     self.unindent(force=True)
             insert_text(event)


Test patch: