Repository: spyder-ide/spyder

Issue:
Spyder closes unexpectedly when trying to change plain text font
## Problem Description

Trying to change plain text font make Spyder crash.

### What steps reproduce the problem?

1. Try to change plain text font
2. Wait for Spyder to crash

![spyder_crash](https://user-images.githubusercontent.com/10170372/69907320-4a928480-13a1-11ea-8045-d872b6647953.gif)


### Paste Traceback/Error Below (if applicable)

This is what is written in my Windows Terminal.
```python-traceback
QBackingStore::endPaint() called with active painter on backingstore paint device
```
This is what I managed to retrieve one time in my internal console:
```python-traceback
DirectWrite: CreateFontFaceFromHDC() failed (Indique une erreur dans un fichier d’entrée, tel qu’un fichier de polices.) for QFontDef(Family="Fixedsys", pointsize=12, pixelsize=15, styleHint=5, weight=50, stretch=100, hintingPreference=0) LOGFONT("Fixedsys", lfWidth=0, lfHeight=-15) dpi=96
```

If I understand https://bugreports.qt.io/browse/QTBUG-57180 correctly, the `DirectWrite: CreateFontFaceFromHDC() [...]` should be a harmless warning and shouldn't make Spyder crash. So I digged a little bit and found out that indeed, Spyder was crashing because of the Internal console and more specifically because of the call to `QCoreApplication.processEvents()` here :

https://github.com/spyder-ide/spyder/blob/73f958425ecc0d79788a413ce4c13fad5b29146e/spyder/plugins/console/widgets/shell.py#L561-L566

If I remove this line, then the warning appears in the internal console, but Spyder doesn't crash anymore.

I've also noticed for a bit that Spyder was crashing often when adding temporarily `print` within the code while debugging and developing.

## Versions

* Spyder 4.0.0.dev0 (Commit: 8357c073c) 
* Python 3.7.3 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
cloudpickle >=0.5.0          :  1.2.2 (OK)
pygments >=2.0               :  2.4.2 (OK)
qtconsole >=4.6.0            :  4.7.0.dev0 (OK)
nbconvert >=4.0              :  5.6.0 (OK)
sphinx >=0.6.6               :  2.2.1 (OK)
pylint >=0.25                :  2.4.3 (OK)
psutil >=0.3                 :  5.6.3 (OK)
qtawesome >=0.5.7            :  0.6.0.dev0 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.1.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.8.0;<2.0.0:  1.9.0.dev0 (OK)
qdarkstyle >=2.7             :  2.8 (OK)
atomicwrites >=1.2.0         :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
intervaltree                 :  None (OK)
watchdog                     :  None (OK)
keyring                      :  None (OK)
pexpect >=4.4.0              :  4.7.0 (OK)
pympler                      :  None (OK)
sympy >=0.7.3                :  1.4 (OK)
cython >=0.21                :  0.29.13 (OK)
IPython >=4.0                :  7.9.0 (OK)
matplotlib >=2.0.0           :  3.1.1 (OK)
pandas >=0.13.1              :  0.25.2 (OK)
numpy >=1.7                  :  1.17.2 (OK)
scipy >=0.17.0               :  1.3.1 (OK)
pyls >=0.31.1;<0.32.0        :  0.31.1 (OK)
rtree >=0.8.3                :  0.8.3 (OK)

```



Discussion:
I can't reproduce this on Windows 7. @CAM-Gerlach, are you able to reproduce this error?
I can reproduce this bug also by adding a `print` in the code of Spyder. If the `print` is called at some critical time during runtime, Spyder crash.
So basically, I think this is not a bug with the preferences, but with the internal console. Anything that send a message to the internal console  during runtime can possibly cause this bug 
I couldn't repro this either on 8.1 and Qt/PyQt 5.9.7/5.9.2; I can load the font menu and switch fonts fine. It would seem your system may have a problem font that triggers the interaction with the internal console that then in turn causes the error.
@CAM-Gerlach, but did you add a print that can be triggered while changing fonts takes effect?
No, I just tested in my currently running that I'm working in right now. I guess it was unclear from the above, but what I meant was that presumably @jnsebgosselin 's system has some front that triggers (A) the Qt error, which then triggers (B) the print to the internal console, which then triggers (C) the error as he explained. Given B, I presume C can be reproduced reliably; I was simply confirming my system does not have the initial trigger A to cause B.

However, now that I think of it, I _have_ reproduced something similar in the past; when I enabled the new indent guides during testing and got the issues described [here](https://github.com/spyder-ide/spyder/issues/8864#issuecomment-555804188). If enabled from the Source menu, as the repaint is triggered I get the Windows console error as here and only the first line of the error gets printed to the Internal Console and then it stops responding, while if I do the same from preferences, Spyder prints the full error to the Internal Console (possibly due to race/timing differences).

If you want me to confirm I can repro this with an arbitrary print statement somewhere, just let me know you want me to stick it exactly where it consistently repros the error for you. Thanks.
I will find a place in the code where a print trigger the crash consistently and report back. I have encountered this issue recently while working on the preferences for the keyboard shortcuts, so that should not be too hard.
Ok, adding a print after `painter = QPainter(self)` triggers the crash 100% of the time for me when `Show indent guide` is checked.

https://github.com/spyder-ide/spyder/blob/2d5d450fdcef232fb7f38e7fefc27f0e7f704c9a/spyder/plugins/editor/panels/indentationguides.py#L40-L47

As for the case I reported in the OP, I also get the following error:

```python
QBackingStore::endPaint() called with active painter on backingstore paint device
```
I have encountered this issue a couple of times while developing Spyder, but it is the first time that I've encountered it as a user, thus the reason why I decided to report it.
To prevent the crash, I need to remove the call to `QCoreApplication.processEvents()` and replace `repaint` by `update` in the internal console.

https://github.com/spyder-ide/spyder/blob/73f958425ecc0d79788a413ce4c13fad5b29146e/spyder/plugins/console/widgets/shell.py#L561-L566
@jnsebgosselin Confirmed, if I add the print I get very similar behavior to when indent guides weren't working, and the same two messages in the system console, except a full on crash instead of the UI just going crazy.
When I tried to change font size with mouse whell, spyder is closed unexpectedly! It also does not save anything if you didnt, very ciritical :/
@maliozer Thanks; as discussed here the Spyder team is aware of the problem, and they're working on a fix. Also, in Spyder 4 (due out any day now), the new autosave feature will prevent you loosing any unsaved work if Spyder does crash for any reason.
> To prevent the crash, I need to remove the call to QCoreApplication.processEvents() and replace repaint by update in the internal console.

This could have some unexpected side effects when running code on the internal console, so please leave the current code `if not DEV`, so the core team doesn't have problems due to this.

And thanks for finding the solution to this nasty crash @jnsebgosselin!
@jnsebgosselin, I'm going to take care of this one.
> 
> 
> @jnsebgosselin, I'm going to take care of this one.

Ok thanks!

Resolving patch:
diff --git a/spyder/plugins/console/widgets/console.py b/spyder/plugins/console/widgets/console.py
--- a/spyder/plugins/console/widgets/console.py
+++ b/spyder/plugins/console/widgets/console.py
@@ -154,10 +154,7 @@ class ConsoleBaseWidget(TextEditBaseWidget):
     def __init__(self, parent=None):
         TextEditBaseWidget.__init__(self, parent)
 
-        # We use an object name to set the right background
-        # color when changing interface theme. This seems to
-        # be a Qt bug.
-        # Fixes spyder-ide/spyder#8072.
+        # To adjust some things for the internal console
         self.setObjectName('console')
 
         self.setMaximumBlockCount(300)
diff --git a/spyder/plugins/console/widgets/shell.py b/spyder/plugins/console/widgets/shell.py
--- a/spyder/plugins/console/widgets/shell.py
+++ b/spyder/plugins/console/widgets/shell.py
@@ -560,8 +560,15 @@ def flush(self, error=False, prompt=False):
 
         self.__buffer = []
         self.insert_text(text, at_end=True, error=error, prompt=prompt)
-        QCoreApplication.processEvents()
-        self.repaint()
+
+        # The lines below are causing a hard crash when Qt generates
+        # internal warnings. We replaced them instead for self.update(),
+        # which prevents the crash.
+        # See spyder-ide/spyder#10893
+        # QCoreApplication.processEvents()
+        # self.repaint()
+        self.update()
+
         # Clear input buffer:
         self.new_input_line = True
 
diff --git a/spyder/plugins/editor/widgets/base.py b/spyder/plugins/editor/widgets/base.py
--- a/spyder/plugins/editor/widgets/base.py
+++ b/spyder/plugins/editor/widgets/base.py
@@ -912,11 +912,15 @@ def insert_completion(self, completion, completion_position):
         self.setTextCursor(cursor)
 
         # Add text
-        if self.code_snippets:
-            self.sig_insert_completion.emit(text)
-        else:
+        if self.objectName() == 'console':
+            # Handle completions for the internal console
             self.insert_text(text)
-        self.document_did_change()
+        else:
+            if self.code_snippets:
+                self.sig_insert_completion.emit(text)
+            else:
+                self.insert_text(text)
+            self.document_did_change()
 
     def is_completion_widget_visible(self):
         """Return True is completion list widget is visible"""


Test patch:
diff --git a/spyder/plugins/console/tests/__init__.py b/spyder/plugins/console/tests/__init__.py
new file mode 100644
--- /dev/null
+++ b/spyder/plugins/console/tests/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) Spyder Project Contributors
+#
+# Licensed under the terms of the MIT License
+# (see LICENSE.txt for details)
+# -----------------------------------------------------------------------------
+
+"""Tests."""
diff --git a/spyder/plugins/console/tests/test_plugin.py b/spyder/plugins/console/tests/test_plugin.py
new file mode 100644
--- /dev/null
+++ b/spyder/plugins/console/tests/test_plugin.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright © Spyder Project Contributors
+#
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+# -----------------------------------------------------------------------------
+
+"""
+Tests for the console plugin.
+"""
+
+# Standard library imports
+try:
+    from unittest.mock import Mock
+except ImportError:
+    from mock import Mock  # Python 2
+
+# Third party imports
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QMainWindow
+import pytest
+from flaky import flaky
+
+# Local imports
+from spyder.plugins.console.plugin import Console
+
+
+# =============================================================================
+# Fixtures
+# =============================================================================
+@pytest.fixture
+def console_plugin(qtbot):
+    """Console plugin fixture."""
+
+    class MainWindowMock(QMainWindow):
+        def __getattr__(self, attr):
+            return Mock()
+
+    window = MainWindowMock()
+    console_plugin = Console(parent=window)
+    window.setCentralWidget(console_plugin)
+
+    qtbot.addWidget(window)
+    window.resize(640, 480)
+    window.show()
+    return console_plugin
+
+
+# =============================================================================
+# Tests
+# =============================================================================
+@flaky(max_runs=3)
+def test_run_code(console_plugin, capsys):
+    """Test that the console runs code."""
+    shell = console_plugin.shell
+
+    # Run a simple code
+    shell.insert_text('2+2', at_end=True)
+    shell._key_enter()
+
+    # Capture stdout and assert that it's the expected one
+    sys_stream = capsys.readouterr()
+    assert sys_stream.out == u'4\n'
+
+
+@flaky(max_runs=3)
+def test_completions(console_plugin, qtbot):
+    """Test that completions work as expected."""
+    shell = console_plugin.shell
+
+    # Get completions
+    qtbot.keyClicks(shell, 'impor')
+    qtbot.keyClick(shell, Qt.Key_Tab)
+    qtbot.keyClick(shell.completion_widget, Qt.Key_Enter)
+
+    # Assert completion was introduced in the console
+    assert u'import' in shell.toPlainText()
+
+
+if __name__ == "__main__":
+    pytest.main()