Repository: psychopy/psychopy

Issue:
Code "Shell" raises error box on exception
When using the interactive Python shell in Coder, the unhandled exception dialog is presented on error instead of stdout being diverted to the shell. We might have to hook an event on the shell to redirect stdout to the console on focus and back to the error dialog when focus is lost.

This reveals another longstanding issue, PyShell operates on the same interpreter thread as the GUI. This is useful for debugging, allowing us to inspect variables and run commands to mess around with the GUI at runtime. However, this is really bad idea for something user facing since commands being executed can crash the GUI (pretty much the same issue MATLAB has (had?) where typing in a while loop with no exit condition crashes it). Also the user can create objects and arrays that aren't garbage collected that can inflate the GUI's memory usage, requiring a restart to clear. Having the REPL talk to a Python interpreter in another process through pipes will allow users to restart a misbehaving interactive session without it affecting the GUI. I've written a REPL before for wx but it wasn't a great implementation. Now that I think about it, we'll probably need to write a GUI-less wxPython app that runs as a subprocess which wraps as system terminal, sending keypresses over to it using TCP or something. 


Discussion:

Resolving patch:
diff --git a/psychopy/app/coder/coder.py b/psychopy/app/coder/coder.py
--- a/psychopy/app/coder/coder.py
+++ b/psychopy/app/coder/coder.py
@@ -39,6 +39,7 @@
 from ..utils import FileDropTarget, PsychopyToolbar, FrameSwitcher
 from psychopy.projects import pavlovia
 import psychopy.app.pavlovia_ui.menu
+from psychopy.app.errorDlg import exceptionCallback
 from psychopy.app.coder.codeEditorBase import BaseCodeEditor
 from psychopy.app.coder.fileBrowser import FileBrowserPanel
 from psychopy.app.coder.sourceTree import SourceTreePanel
@@ -103,17 +104,39 @@ def fromPickle(filename):
 
 
 class PsychopyPyShell(wx.py.shell.Shell, ThemeMixin):
-    '''Simple class wrapper for Pyshell which uses the Psychopy ThemeMixin'''
+    """Simple class wrapper for Pyshell which uses the Psychopy ThemeMixin."""
     def __init__(self, coder):
         msg = _translate('PyShell in PsychoPy - type some commands!')
-        wx.py.shell.Shell.__init__(self, coder.shelf, -1, introText=msg + '\n\n', style=wx.BORDER_NONE)
+        wx.py.shell.Shell.__init__(
+            self, coder.shelf, -1, introText=msg + '\n\n', style=wx.BORDER_NONE)
         self.prefs = coder.prefs
         self.paths = coder.paths
         self.app = coder.app
 
+        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
+        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
+
         # Set theme to match code editor
         self._applyAppTheme()
 
+    def OnSetFocus(self, evt=None):
+        """Called when the shell gets focus."""
+        # Switch to the default callback when in console, prevents the PsychoPy
+        # error dialog from opening.
+        sys.excepthook = sys.__excepthook__
+
+        if evt:
+            evt.Skip()
+
+    def OnKillFocus(self, evt=None):
+        """Called when the shell loses focus."""
+        # Set the callback to use the dialog when errors occur outside the
+        # shell.
+        sys.excepthook = exceptionCallback
+
+        if evt:
+            evt.Skip()
+
     def GetContextMenu(self):
         """Override original method (wx.py.shell.Shell.GetContextMenu)
         to localize context menu.  Simply added _translate() to


Test patch: