Repository: spyder-ide/spyder

Issue:
Files not autoformatted on save when other options that work on save are set
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1. Turn on auto-format on save on, with black enabled. (haven't tested with autopep8)

2. create a new file, input poorly formatted code such as

    a = [3,
              4]

3. press save. 

4. inspect file in external editor to see it has not been formatted.

5. In spyder, press CTRL+ALT+I to format the file. You get the expected

    a = [3, 4]

formatting. This proves black is working and spyder knows how to invoke it correctly. 

Also, if you put several blank lines at the end of the file, when I press save the lines are correctly deleted, proving that format during save works in general (I have the check box for removing extra lines on save checked)

## Versions

* Spyder version: 5.3.0 
* Python version: 3.8.8
* Qt version: 5.12.10
* PyQt5 version: 5.12.3
* Operating System: Windows 7

### Dependencies

```
# Mandatory:
atomicwrites >=1.2.0          :  1.4.0 (OK)
chardet >=2.0.0               :  4.0.0 (OK)
cloudpickle >=0.5.0           :  1.6.0 (OK)
cookiecutter >=1.6.0          :  1.7.3 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.1.0 (OK)
IPython >=7.31.1;<8.0.0       :  7.32.0 (OK)
jedi >=0.17.2;<0.19.0         :  0.17.2 (OK)
jellyfish >=0.7               :  0.9.0 (OK)
jsonschema >=3.2.0            :  3.2.0 (OK)
keyring >=17.0.0              :  22.3.0 (OK)
nbconvert >=4.0               :  6.0.7 (OK)
numpydoc >=0.6.0              :  1.1.0 (OK)
paramiko >=2.4.0              :  2.7.2 (OK)
parso >=0.7.0;<0.9.0          :  0.7.0 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.8.0 (OK)
pygments >=2.0                :  2.8.1 (OK)
pylint >=2.5.0                :  2.7.4 (OK)
pyls_spyder >=0.4.0           :  0.4.0 (OK)
pylsp >=1.4.1;<1.5.0          :  1.4.1 (OK)
pylsp_black >=1.2.0           :  1.2.1 (OK)
qdarkstyle >=3.0.2;<3.1.0     :  3.0.3 (OK)
qstylizer >=0.1.10            :  0.2.1 (OK)
qtawesome >=1.0.2             :  1.0.2 (OK)
qtconsole >=5.3.0;<5.4.0      :  5.3.0 (OK)
qtpy >=2.0.1                  :  2.0.1 (OK)
rtree >=0.9.7                 :  0.9.7 (OK)
setuptools >=49.6.0           :  52.0.0.post20210125 (OK)
sphinx >=0.6.6                :  4.0.1 (OK)
spyder_kernels >=2.3.0;<2.4.0 :  2.3.0 (OK)
textdistance >=4.2.0          :  4.2.1 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3             :  1.0.2 (OK)
zmq >=17                      :  22.3.0 (OK)

# Optional:
cython >=0.21                 :  0.29.23 (OK)
matplotlib >=3.0.0            :  3.3.4 (OK)
numpy >=1.7                   :  1.20.1 (OK)
pandas >=1.1.1                :  1.2.4 (OK)
scipy >=0.17.0                :  1.6.2 (OK)
sympy >=0.7.3                 :  1.8 (OK)
```



Discussion:
Update - whether it works or not depends on work flow. If I just press the save button or use CTRL+S, the file is not formatted. If I run the code and it has not been saved, Spyder reformats it using black, saves the reformatted file, and then runs the file. 
Hey @rlabbe, thanks for reporting. Could you post a video or animated gif of the process you followed to get this error? I tried to reproduce it locally and didn't get your results.

For the gif, you can use a little program called [Licecap](https://www.cockos.com/licecap/).
No, I had to uninstall for other reasons and am back to 4.x. I don't understand how a screen capture would help, you can't see me pressing CTRL S or CTRL ALT I. 
Sorry, I can't reproduce this. Unless you give us more details, I'm afraid I'll have to close this issue.
I have hit the same issue with Linux, both with the Debian package and the PyPI version.  And I think I may have figured out where the problem lies!

Here's my really simple example file:
```python
test = 3


test = test +   2



```
When I do ctrl-S, the blank lines are removed as I have selected all of the options in the Editor > Source code preferences pane except for "Tab always indent", but the extra spaces in `test +   2` are not removed, even though I have selected "Enable code style linting" and "Autoformat files on save" with "black" on the Completion and linting > Code style and formatting pane.  All of the other options are at their defaults.  The same behaviour is seen if "autopep8" is chosen.

However, if I turn off the options in Editor > Source code which relate to formatting the file on save, it gets passed to black and the extra spaces in `test +   2` are removed.  So it seems that the options are interfering with each other, with the Editor > Source code format-on-save ones preventing the "Autoformat files on save" option from working.
Thanks for the additional details @juliangilbey! I can reproduce this bug now.

We'll try to fix it on our next version (5.3.1), to be released in mid May.
@dalthviz, please take care of this one. The options @juliangilbey is referring to are these ones:

![imagen](https://user-images.githubusercontent.com/365293/165795869-692ca2b3-53a3-494c-b891-fb9b41b01889.png)

I think the solution for this would be to disable those options in the interface when `Autoformat on save` is on, add tooltip to them when that's the case to explain why they are disabled, and don't use them in our code because autoformatting takes care of all that.

Resolving patch:
diff --git a/spyder/plugins/editor/confpage.py b/spyder/plugins/editor/confpage.py
--- a/spyder/plugins/editor/confpage.py
+++ b/spyder/plugins/editor/confpage.py
@@ -9,6 +9,8 @@
 from qtpy.QtWidgets import (QGridLayout, QGroupBox, QHBoxLayout, QLabel,
                             QTabWidget, QVBoxLayout, QWidget)
 
+from spyder.api.config.decorators import on_conf_change
+from spyder.api.config.mixins import SpyderConfigurationObserver
 from spyder.api.preferences import PluginConfigPage
 from spyder.config.base import _
 from spyder.config.manager import CONF
@@ -20,7 +22,14 @@
 DOCSTRING_SHORTCUT = CONF.get('shortcuts', 'editor/docstring')
 
 
-class EditorConfigPage(PluginConfigPage):
+class EditorConfigPage(PluginConfigPage, SpyderConfigurationObserver):
+    def __init__(self, plugin, parent):
+        PluginConfigPage.__init__(self, plugin, parent)
+        SpyderConfigurationObserver.__init__(self)
+        self.removetrail_box = None
+        self.add_newline_box = None
+        self.remove_trail_newline_box = None
+
     def get_name(self):
         return _("Editor")
 
@@ -117,16 +126,16 @@ def setup_page(self):
             _("Intelligent backspace"),
             'intelligent_backspace',
             default=True)
-        removetrail_box = newcb(
+        self.removetrail_box = newcb(
             _("Automatically remove trailing spaces when saving files"),
             'always_remove_trailing_spaces',
             default=False)
-        add_newline_box = newcb(
+        self.add_newline_box = newcb(
             _("Insert a newline at the end if one does not exist when saving "
               "a file"),
             'add_newline',
             default=False)
-        remove_trail_newline_box = newcb(
+        self.remove_trail_newline_box = newcb(
             _("Trim all newlines after the final one when saving a file"),
             'always_remove_trailing_newlines',
             default=False)
@@ -148,6 +157,13 @@ def setup_page(self):
             'tab_stop_width_spaces',
             default=4, min_=1, max_=8, step=1)
 
+        format_on_save = CONF.get(
+            'completions',
+            ('provider_configuration', 'lsp', 'values', 'format_on_save'),
+            False
+        )
+        self.on_format_save_state(format_on_save)
+
         def enable_tabwidth_spin(index):
             if index == 7:  # Tabulations
                 tabwidth_spin.plabel.setEnabled(True)
@@ -177,9 +193,9 @@ def enable_tabwidth_spin(index):
         sourcecode_layout.addWidget(close_quotes_box)
         sourcecode_layout.addWidget(tab_mode_box)
         sourcecode_layout.addWidget(ibackspace_box)
-        sourcecode_layout.addWidget(removetrail_box)
-        sourcecode_layout.addWidget(add_newline_box)
-        sourcecode_layout.addWidget(remove_trail_newline_box)
+        sourcecode_layout.addWidget(self.removetrail_box)
+        sourcecode_layout.addWidget(self.add_newline_box)
+        sourcecode_layout.addWidget(self.remove_trail_newline_box)
         sourcecode_layout.addWidget(strip_mode_box)
         sourcecode_layout.addLayout(indent_tab_layout)
 
@@ -322,3 +338,36 @@ def enable_tabwidth_spin(index):
         vlayout = QVBoxLayout()
         vlayout.addWidget(self.tabs)
         self.setLayout(vlayout)
+
+    @on_conf_change(
+        option=('provider_configuration', 'lsp', 'values', 'format_on_save'),
+        section='completions'
+    )
+    def on_format_save_state(self, value):
+        """
+        Change options following the `format_on_save` completion option.
+
+        Parameters
+        ----------
+        value : bool
+            If the completion `format_on_save` option is enabled or disabled.
+
+        Returns
+        -------
+        None.
+
+        """
+        options = [
+            self.removetrail_box,
+            self.add_newline_box,
+            self.remove_trail_newline_box]
+        for option in options:
+            if option:
+                if value:
+                    option.setToolTip(
+                        _("This option is disabled since the "
+                          "<i>Autoformat files on save</i> option is active.")
+                    )
+                else:
+                    option.setToolTip("")
+                option.setDisabled(value)
diff --git a/spyder/plugins/editor/widgets/editor.py b/spyder/plugins/editor/widgets/editor.py
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -1880,12 +1880,18 @@ def save(self, index=None, force=False, save_new_files=True):
                 return self.save_as(index=index)
             # The file doesn't need to be saved
             return True
-        if self.always_remove_trailing_spaces:
+
+        # The following options (`always_remove_trailing_spaces`,
+        # `remove_trailing_newlines` and `add_newline`) also depend on the
+        # `format_on_save` value.
+        # See spyder-ide/spyder#17716
+        if self.always_remove_trailing_spaces and not self.format_on_save:
             self.remove_trailing_spaces(index)
-        if self.remove_trailing_newlines:
+        if self.remove_trailing_newlines and not self.format_on_save:
             self.trim_trailing_newlines(index)
-        if self.add_newline:
+        if self.add_newline and not self.format_on_save:
             self.add_newline_to_file(index)
+
         if self.convert_eol_on_save:
             # hack to account for the fact that the config file saves
             # CR/LF/CRLF while set_os_eol_chars wants the os.name value.


Test patch: