Repository: spyder-ide/spyder
Pull request: PR: Make sure processEvents is not called by Pdb
<!--- Make sure to read the Contributing Guidelines:                   --->
<!--- https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md --->
<!--- and follow PEP 8, PEP 257 and Spyder's code style:               --->
<!--- https://github.com/spyder-ide/spyder/wiki/Dev:-Coding-Style      --->

## Description of Changes


* [ ] Wrote at least one-line docstrings (for any new functions)
* [ ] Added unit test(s) covering the changes (if testable)
<!--- Remember that an image/animation is worth a thousand words! --->
* [ ] Included a screenshot or animation (if affecting the UI, see [Licecap](https://www.cockos.com/licecap/))


<!--- Explain what you've done and why --->

Calling QApplication.processEvents() creates a leak of keypresses. This Pr makes sure this doesn't happen again.


### Issue(s) Resolved

<!--- List the issue(s) below, in the form "Fixes #1234"; one per line --->

Fixes #10834
Depends on spyder-ide/spyder-kernels#185


### Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the [Developer Certificate of Origin](https://developercertificate.org)
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

<!--- TYPE YOUR USER/NAME AFTER THE FOLLOWING: --->
I certify the above statement is true and correct:

<!--- Thanks for your help making Spyder better for everyone! --->


Problem statement:
Keyboard events leak from console to editor while debugging
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

create a file `tmp.py`:
```
def a():
    1/0
```
And a file `tmp2.py`:
```
from tmp import a
a()
```
 1. Run tmp2.py
 2. type in the console `%debug`
 3. Type `u+<Return>` and close `tmp.py`
 4. Type `d` without return
 5. Press return as many times and as quickly as possible.
 
I see 1-2 new lines being added to `tmp2.py` before `tmp.py` has time to load and the focus returns to the console.


## Versions

* Spyder version: 4.0.0.dev0 e5f2892d9
* Python version: 3.7.5
* Qt version: 5.12.6
* PyQt5 version: 5.12.3
* Operating System: Darwin 19.0.0

### Dependencies

```
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.1 (OK)
sphinx >=0.6.6               :  2.2.1 (OK)
pylint >=0.25                :  2.4.4 (OK)
psutil >=0.3                 :  5.6.5 (OK)
qtawesome >=0.5.7            :  0.6.0 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.1.1 (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.7 (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                :  None (NOK)
cython >=0.21                :  None (NOK)
IPython >=4.0                :  7.9.0 (OK)
matplotlib >=2.0.0           :  3.1.2 (OK)
pandas >=0.13.1              :  0.25.3 (OK)
numpy >=1.7                  :  1.17.4 (OK)
scipy >=0.17.0               :  1.3.3 (OK)
pyls >=0.31.0;<0.32.0        :  0.31.1 (OK)
applaunchservices >=0.1.7    :  0.2.1 (OK)
```

Hints from issue discussion:

Commit message: fix test

Patch to review:
diff --git a/continuous_integration/azure/install.bat b/continuous_integration/azure/install.bat
index 9da77f1e17d..034542f1152 100644
--- a/continuous_integration/azure/install.bat
+++ b/continuous_integration/azure/install.bat
@@ -38,7 +38,7 @@ conda create -n jedi-test-env -q -y python=3.6 loghub spyder-kernels -c spyder-i
 if errorlevel 1 exit 1
 
 :: Install spyder-kernels from master
-pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels
+pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
 if errorlevel 1 exit 1
 
 :: Install python-language-server from master
diff --git a/continuous_integration/azure/install.sh b/continuous_integration/azure/install.sh
index b62fc6070ea..9c1fe90ac01 100755
--- a/continuous_integration/azure/install.sh
+++ b/continuous_integration/azure/install.sh
@@ -20,7 +20,7 @@ if [ "$USE_CONDA" = "yes" ]; then
     conda install -q -y openssl=1.1.1c
 
     # Install spyder-kernels from Github with no deps
-    pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels
+    pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
 
     # Install python-language-server from Github with no deps
     pip install -q --no-deps git+https://github.com/palantir/python-language-server
@@ -44,7 +44,7 @@ else
     pip install git+https://github.com/jupyter/qtconsole.git
 
     # Install spyder-kernels from Github
-    pip install -q git+https://github.com/spyder-ide/spyder-kernels
+    pip install -q git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
 
     # Install python-language-server from Github
     pip install -q git+https://github.com/palantir/python-language-server
diff --git a/continuous_integration/travis/install.sh b/continuous_integration/travis/install.sh
index e2488b0b448..102403116e5 100755
--- a/continuous_integration/travis/install.sh
+++ b/continuous_integration/travis/install.sh
@@ -35,7 +35,7 @@ if [ "$USE_CONDA" = "yes" ]; then
     pip install -q coveralls
 
     # Install spyder-kernels from Github with no deps
-    pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels
+    pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
 
     # Install python-language-server from Github with no deps
     pip install -q --no-deps git+https://github.com/palantir/python-language-server
@@ -68,7 +68,7 @@ else
     pip install git+https://github.com/jupyter/qtconsole.git
 
     # Install spyder-kernels from Github
-    pip install -q git+https://github.com/spyder-ide/spyder-kernels
+    pip install -q git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
 
     # Install python-language-server from Github
     pip install -q git+https://github.com/palantir/python-language-server
diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py
index 50a7947df0b..ea47878d3e5 100644
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -2756,5 +2756,71 @@ def test_path_manager_updates_clients(qtbot, main_window, tmpdir):
     assert count >= 1
 
 
+@pytest.mark.slow
+@flaky(max_runs=3)
+@pytest.mark.skipif(os.name == 'nt' or sys.platform == 'darwin',
+                    reason="It times out on macOS and Windows")
+def test_pbd_key_leak(main_window, qtbot, tmpdir):
+    """
+    Check that pdb notify spyder doesn't call
+    QApplication.processEvents(). If it does there might be keystoke leakage.
+    see #10834
+    """
+    # Wait until the window is fully up
+    shell = main_window.ipyconsole.get_current_shellwidget()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+                    timeout=SHELL_TIMEOUT)
+    control = shell._control
+
+    # Write code to a file
+    code1 = ("def a():\n"
+             "    1/0")
+    code2 = ("from tmp import a\n"
+             "a()")
+    folder = tmpdir.join('tmp_folder')
+    test_file = folder.join('tmp.py')
+    test_file.write(code1, ensure=True)
+
+    test_file2 = folder.join('tmp2.py')
+    test_file2.write(code2)
+
+    # Run tmp2 and get an error
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('runfile("' + str(test_file2).replace("\\", "/") +
+                      '", wdir="' + str(folder).replace("\\", "/") + '")')
+    assert '1/0' in control.toPlainText()
+
+    # Replace QApplication.processEvents to make sure it is not called
+    super_processEvents = QApplication.processEvents
+
+    def processEvents():
+        processEvents.called = True
+        return super_processEvents()
+
+    processEvents.called = False
+    try:
+        QApplication.processEvents = processEvents
+        # Debug and open both files
+        shell.execute('%debug')
+        qtbot.waitUntil(lambda: control.toPlainText().split()[-1] == 'ipdb>')
+        qtbot.keyClick(control, 'u')
+        qtbot.keyClick(control, Qt.Key_Enter)
+        qtbot.waitUntil(lambda: control.toPlainText().split()[-1] == 'ipdb>')
+
+        # Wait until both files are open
+        qtbot.waitUntil(
+            lambda: osp.normpath(str(test_file)) in [
+                osp.normpath(p) for p in main_window.editor.get_filenames()])
+        qtbot.waitUntil(
+            lambda: str(test_file2) in [
+                osp.normpath(p) for p in main_window.editor.get_filenames()])
+
+        # Make sure the events are not processed.
+        assert not processEvents.called
+
+    finally:
+        QApplication.processEvents = super_processEvents
+
+
 if __name__ == "__main__":
     pytest.main()
diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py
index 3d4dda19c1b..03707ca624b 100644
--- a/spyder/plugins/editor/plugin.py
+++ b/spyder/plugins/editor/plugin.py
@@ -1918,7 +1918,8 @@ def _convert(fname):
                 # (the one that can't be destroyed), then cloning this
                 # editor widget in all other editorstacks:
                 finfo = self.editorstacks[0].load(
-                    filename, set_current=False, add_where=add_where)
+                    filename, set_current=False, add_where=add_where,
+                    processevents=processevents)
                 finfo.path = self.main.get_spyder_pythonpath()
                 self._clone_file_everywhere(finfo)
                 current_editor = current_es.set_current_filename(filename,
diff --git a/spyder/plugins/editor/widgets/editor.py b/spyder/plugins/editor/widgets/editor.py
index d9202e20df3..3275483d11a 100644
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -2641,7 +2641,8 @@ def new(self, filename, encoding, text, default_content=False,
             finfo.editor.document().setModified(False)
         return finfo
 
-    def load(self, filename, set_current=True, add_where='end'):
+    def load(self, filename, set_current=True, add_where='end',
+             processevents=True):
         """
         Load filename, create an editor instance and return it
 
@@ -2652,14 +2653,16 @@ def load(self, filename, set_current=True, add_where='end'):
         plugin (in case multiple editorstack instances are handled)
         """
         filename = osp.abspath(to_text_string(filename))
-        self.starting_long_process.emit(_("Loading %s...") % filename)
+        if processevents:
+            self.starting_long_process.emit(_("Loading %s...") % filename)
         text, enc = encoding.read(filename)
         self.autosave.file_hashes[filename] = hash(text)
         finfo = self.create_new_editor(filename, enc, text, set_current,
                                        add_where=add_where)
         index = self.data.index(finfo)
         self._refresh_outlineexplorer(index, update=True)
-        self.ending_long_process.emit("")
+        if processevents:
+            self.ending_long_process.emit("")
         if self.isVisible() and self.checkeolchars_enabled \
            and sourcecode.has_mixed_eol_chars(text):
             name = osp.basename(filename)


Code review comments:
diff_hunk: @@ -38,7 +38,7 @@ conda create -n jedi-test-env -q -y python=3.6 loghub spyder-kernels -c spyder-i
 if errorlevel 1 exit 1
 
 :: Install spyder-kernels from master
-pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels
+pip install -q --no-deps git+https://github.com/spyder-ide/spyder-kernels.git@refs/pull/185/head
path: continuous_integration/azure/install.bat
text: @user1:
I just merged spyder-ide/spyder-kernels#185, so you can revert these changes.