Repository: spyder-ide/spyder

Issue:
Spyder dies when running code while debugging
This happened many times while I was debugging using Spyder.
Sometimes I can type in code in the IPython Console above the ipdb> arrow, then everything seems to be frozen.
Nothing got printed out. After that, I can still type in codes. However, nothing can be executed.
I tried enter button, exit debug button, run button, etc. I could only type in codes.
At last, the only thing I could do is restart the kernel.

Spyder shouldn't allow the user to type in code above the ipdb> arrow.



Discussion:
Ideally, you shouldn't use `ipdb` to enter code, just debug commands (like `b` or `c`). That's because `ipdb` doesn't accept arbitrary Python code.

Same thing happened just now. Check the attached pictures.

![capture](https://cloud.githubusercontent.com/assets/7728287/7892747/a49923f4-061c-11e5-96ca-1b3d5f1bcd05.PNG)
![capture1](https://cloud.githubusercontent.com/assets/7728287/7892748/a499ad2e-061c-11e5-8758-e1ee918ca1d0.PNG)

Could you post an example of the code you're trying to debug that is causing this error?

I encountered the same issue... 
When writing code, eg displaying a pandas dataframe (pandas_df.tail()) in the ipdb command line, and, remarkably, evne when just copy-pasting code that was displayed by the ipdb debugger above (as part of trace).

I am running Anaconda Python 3.4 with Spyder 2.3.8


Resolving patch:
diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py
--- a/spyder/plugins/ipythonconsole/plugin.py
+++ b/spyder/plugins/ipythonconsole/plugin.py
@@ -556,26 +556,23 @@ def execute_code(self, lines, current_client=True, clear_variables=False):
         """Execute code instructions."""
         sw = self.get_current_shellwidget()
         if sw is not None:
-            if sw.is_waiting_pdb_input():
-                pass
-            else:
-                if not current_client:
-                    # Clear console and reset namespace for
-                    # dedicated clients.
-                    # See spyder-ide/spyder#5748.
-                    try:
-                        sw.sig_prompt_ready.disconnect()
-                    except TypeError:
-                        pass
-                    sw.reset_namespace(warning=False)
-                elif current_client and clear_variables:
-                    sw.reset_namespace(warning=False)
-                # Needed to handle an error when kernel_client is none.
-                # See spyder-ide/spyder#6308.
+            if not current_client:
+                # Clear console and reset namespace for
+                # dedicated clients.
+                # See spyder-ide/spyder#5748.
                 try:
-                    sw.execute(to_text_string(lines))
-                except AttributeError:
+                    sw.sig_prompt_ready.disconnect()
+                except TypeError:
                     pass
+                sw.reset_namespace(warning=False)
+            elif current_client and clear_variables:
+                sw.reset_namespace(warning=False)
+            # Needed to handle an error when kernel_client is none.
+            # See spyder-ide/spyder#6308.
+            try:
+                sw.execute(to_text_string(lines))
+            except AttributeError:
+                pass
             self.activateWindow()
             self.get_current_client().get_control().setFocus()
 
diff --git a/spyder/plugins/ipythonconsole/widgets/debugging.py b/spyder/plugins/ipythonconsole/widgets/debugging.py
--- a/spyder/plugins/ipythonconsole/widgets/debugging.py
+++ b/spyder/plugins/ipythonconsole/widgets/debugging.py
@@ -18,7 +18,11 @@
 
 from spyder.config.base import get_conf_path
 from spyder.config.manager import CONF
-from spyder.widgets.mixins import BrowseHistory
+from spyder.py3compat import PY2
+if not PY2:
+    from IPython.core.inputtransformer2 import TransformerManager
+else:
+    from IPython.core.inputsplitter import IPythonInputSplitter
 
 
 class PdbHistory(HistoryManager):
@@ -42,23 +46,28 @@ class DebuggingWidget(RichJupyterWidget):
     PDB_HIST_MAX = 400
 
     def __init__(self, *args, **kwargs):
+        self._pdb_in_loop = False
+        self._previous_prompt = None
         super(DebuggingWidget, self).__init__(*args, **kwargs)
         # Adapted from qtconsole/frontend_widget.py
         # This adds 'ipdb> ' as a prompt self._highlighter recognises
         self._highlighter._ipy_prompt_re = re.compile(
             r'^(%s)?([ \t]*ipdb> |[ \t]*In \[\d+\]: |[ \t]*\ \ \ \.\.\.+: )'
             % re.escape(self.other_output_prefix))
-        self._previous_prompt = None
+        # List of tuples containing (code, hidden)
         self._pdb_input_queue = []
         self._pdb_input_ready = False
         self._pdb_last_cmd = ''
         self._pdb_line_num = 0
         self._pdb_history_file = PdbHistory()
-        self._pdb_history = BrowseHistory()
-        self._pdb_history.history = [
+
+        self._pdb_history = [
             line[-1] for line in self._pdb_history_file.get_tail(
                 self.PDB_HIST_MAX, include_latest=True)]
-        self._pdb_in_loop = False
+        self._pdb_history_edits = {}
+        self._pdb_history_index = len(self._pdb_history)
+
+        self._tmp_reading = False
 
     def _handle_debug_state(self, in_debug_loop):
         """Update the debug state."""
@@ -67,6 +76,12 @@ def _handle_debug_state(self, in_debug_loop):
         self._pdb_input_queue = []
         self._pdb_line_num = 0
 
+        # start/stop pdb history session
+        if in_debug_loop:
+            self._pdb_history_file.new_session()
+        else:
+            self._pdb_history_file.end_session()
+
     def _pdb_update(self):
         """
         Update by sending an input to pdb.
@@ -89,26 +104,29 @@ def pdb_execute(self, line, hidden=False):
                 self._pdb_input_queue.append((line, hidden))
             return
 
-        if self._pdb_input_ready:
-            # Print the string to the console
-            if not hidden:
-                if line.strip():
-                    self._pdb_last_cmd = line
-
-                # Print the text if it is programatically added.
-                if line.strip() != self.input_buffer.strip():
-                    self._append_plain_text(line + '\n')
+        if not hidden:
+            if line.strip():
+                self._pdb_last_cmd = line
 
-                # Save history to browse it later
-                self._pdb_line_num += 1
-                self.add_to_pdb_history(self._pdb_line_num, line)
+            # Print the text if it is programatically added.
+            if line.strip() != self.input_buffer.strip():
+                self._append_plain_text(line)
+            self._append_plain_text('\n')
+            # Save history to browse it later
+            self._pdb_line_num += 1
+            self.add_to_pdb_history(self._pdb_line_num, line)
 
-                # Set executing to true and save the input buffer
-                self._input_buffer_executing = self.input_buffer
-                self._executing = True
+            # Set executing to true and save the input buffer
+            self._input_buffer_executing = self.input_buffer
+            self._executing = True
 
-                self._finalize_input_request()
+            # Disable the console
+            self._tmp_reading = False
+            self._finalize_input_request()
+            hidden = True
 
+        if self._pdb_input_ready:
+            # Print the string to the console
             self._pdb_input_ready = False
             return self.kernel_client.input(line)
 
@@ -160,6 +178,92 @@ def pdb_continue(self):
         # Fixes 2034
         self.pdb_execute('continue')
 
+    def in_debug_loop(self):
+        """Check if we are debugging."""
+        return self._pdb_in_loop
+
+    def is_waiting_pdb_input(self):
+        """Check if we are waiting a pdb input."""
+        # If the comm is not open, self._pdb_in_loop can not be set
+        return ((self.in_debug_loop() or not self.spyder_kernel_comm.is_open())
+                and self._previous_prompt is not None
+                and self._previous_prompt[0] == 'ipdb> ')
+
+    def add_to_pdb_history(self, line_num, line):
+        """Add command to history"""
+        self._pdb_history_index = len(self._pdb_history)
+        self._pdb_history_edits = {}
+        line = line.rstrip()
+        if not line:
+            return
+
+        # If repeated line
+        history = self._pdb_history
+        if len(history) > 0 and history[-1] == line:
+            return
+
+        cmd = line.split(" ")[0]
+        args = line.split(" ")[1:]
+        is_pdb_cmd = "do_" + cmd in dir(pdb.Pdb)
+        if cmd and (not is_pdb_cmd or len(args) > 0):
+            self._pdb_history.append(line)
+            self._pdb_history_index = len(self._pdb_history)
+            self._pdb_history_file.store_inputs(line_num, line)
+
+    # --- Private API --------------------------------------------------
+    def _is_pdb_complete(self, source):
+        """
+        Check if the pdb input is ready to be executed.
+        """
+        if source and source[0] == '!':
+            source = source[1:]
+        if PY2:
+            tm = IPythonInputSplitter()
+        else:
+            tm = TransformerManager()
+        complete, indent = tm.check_complete(source)
+        if indent is not None:
+            indent = indent * ' '
+        return complete != 'incomplete', indent
+
+    # ---- Public API (overrode by us) ----------------------------
+    def execute(self, source=None, hidden=False, interactive=False):
+        """ Executes source or the input buffer, possibly prompting for more
+        input.
+
+        Do not use to run pdb commands. Use pdb_execute instead.
+        This will add a '!' in front of the code.
+        """
+        if self.is_waiting_pdb_input():
+            if source is None:
+                if hidden:
+                    # Nothing to execute
+                    return
+                else:
+                    source = self.input_buffer
+            else:
+                source = '!' + source
+                if not hidden:
+                    self.input_buffer = source
+
+            if interactive:
+                # Add a continuation propt if not complete
+                complete, indent = self._is_pdb_complete(source)
+                if not complete:
+                    self.do_execute(source, complete, indent)
+                    return
+            if hidden:
+                self.pdb_execute(source, hidden)
+            else:
+                if self._reading_callback:
+                    self._reading_callback()
+
+            return
+        if not self._executing:
+            # Only execute if not executing
+            return super(DebuggingWidget, self).execute(
+                source, hidden, interactive)
+
     # ---- Private API (overrode by us) ----------------------------
     def _readline_callback(self, line):
         """Callback used when the user inputs text in stdin."""
@@ -189,31 +293,37 @@ def _handle_input_request(self, msg):
 
         prompt, password = msg['content']['prompt'], msg['content']['password']
 
-        # Check if this is a duplicate that we shouldn't reprint.
-        # This can happen when sending commands to pdb from the frontend.
-        print_prompt = (not self._reading
-                        or not (prompt, password) == self._previous_prompt)
-
         self._previous_prompt = (prompt, password)
 
+        # Check if the prompt should be printed
+        if not self.is_waiting_pdb_input():
+            print_prompt = True
+        else:
+            # This is pdb. The prompt should be printed unless:
+            # 1. The prompt is already printed (self._reading is True)
+            # 2. A hidden commad is in the queue
+            print_prompt = (not self._reading
+                            and (len(self._pdb_input_queue) == 0
+                                 or not self._pdb_input_queue[0][1]))
+
         if print_prompt:
             # Reset reading in case it was interrupted
             self._reading = False
             self._readline(prompt=prompt, callback=self._readline_callback,
                            password=password)
+            if self.is_waiting_pdb_input():
+                self._executing = False
+                self._highlighter.highlighting_on = True
 
         if self.is_waiting_pdb_input():
-            self._highlighter.highlighting_on = True
             self._pdb_input_ready = True
-            self._executing = False
 
         # While the widget thinks only one input is going on,
         # other functions can be sending messages to the kernel.
         # This must be properly processed to avoid dropping messages.
         # If the kernel was not ready, the messages are queued.
         if self.is_waiting_pdb_input() and len(self._pdb_input_queue) > 0:
-            args = self._pdb_input_queue[0]
-            del self._pdb_input_queue[0]
+            args = self._pdb_input_queue.pop(0)
             self.pdb_execute(*args)
             return
 
@@ -231,63 +341,72 @@ def _event_filter_console_keypress(self, event):
         key = event.key()
         if self.is_waiting_pdb_input():
             self._control.current_prompt_pos = self._prompt_pos
-            if key == Qt.Key_Up:
-                self._pdb_browse_history(backward=True)
-                return True
-            elif key == Qt.Key_Down:
-                self._pdb_browse_history(backward=False)
-                return True
-            elif key in (Qt.Key_Return, Qt.Key_Enter):
-                self._pdb_history.reset_search_pos()
-            else:
-                self._pdb_history.hist_wholeline = False
-            return super(DebuggingWidget,
-                         self)._event_filter_console_keypress(event)
+            # Pretend this is a regular prompt
+            self._tmp_reading = self._reading
+            self._reading = False
+            try:
+                return super(DebuggingWidget,
+                             self)._event_filter_console_keypress(event)
+            finally:
+                self._reading = self._tmp_reading
         else:
             return super(DebuggingWidget,
                          self)._event_filter_console_keypress(event)
 
-    def _pdb_browse_history(self, backward):
-        """Browse history"""
-        control = self._control
-        # Make sure the cursor is not before the prompt
-        if control.get_position('cursor') < control.current_prompt_pos:
-            control.set_cursor_position(control.current_prompt_pos)
-        old_pos = control.get_position('cursor')
-        line = self._get_input_buffer()
-        cursor_pos = self._get_input_buffer_cursor_pos()
-        text, move_cursor = self._pdb_history.browse_history(
-            line, cursor_pos, backward)
-        if text is not None:
-            control.remove_text(control.current_prompt_pos, 'eof')
-            self._insert_plain_text_into_buffer(control.textCursor(), text)
-            if not move_cursor:
-                control.set_cursor_position(old_pos)
+    def _register_is_complete_callback(self, source, callback):
+        """Call the callback with the result of is_complete."""
+        # Add a continuation propt if not complete
+        if self.is_waiting_pdb_input():
+            complete, indent = self._is_pdb_complete(source)
+            callback(complete, indent)
+        else:
+            return super(DebuggingWidget, self)._register_is_complete_callback(
+                source, callback)
 
-    def in_debug_loop(self):
-        """Check if we are debugging."""
-        return self._pdb_in_loop
+    @property
+    def _history(self):
+        """Get history."""
+        if self.is_waiting_pdb_input():
+            return self._pdb_history
+        else:
+            return self.__history
 
-    def is_waiting_pdb_input(self):
-        """Check if we are waiting a pdb input."""
-        return (self.in_debug_loop() and self._previous_prompt is not None
-                and self._previous_prompt[0] == 'ipdb> ')
+    @_history.setter
+    def _history(self, history):
+        """Set history."""
+        if self.is_waiting_pdb_input():
+            self._pdb_history = history
+        else:
+            self.__history = history
 
-    def add_to_pdb_history(self, line_num, line):
-        """Add command to history"""
-        self._pdb_history.histidx = None
-        if not line:
-            return
-        line = line.strip()
+    @property
+    def _history_edits(self):
+        """Get edited history."""
+        if self.is_waiting_pdb_input():
+            return self._pdb_history_edits
+        else:
+            return self.__history_edits
 
-        # If repeated line
-        history = self._pdb_history.history
-        if len(history) > 0 and history[-1] == line:
-            return
+    @_history_edits.setter
+    def _history_edits(self, history_edits):
+        """Set edited history."""
+        if self.is_waiting_pdb_input():
+            self._pdb_history_edits = history_edits
+        else:
+            self.__history_edits = history_edits
 
-        cmd = line.split(" ")[0]
-        args = line.split(" ")[1:]
-        is_pdb_cmd = "do_" + cmd in dir(pdb.Pdb)
-        if cmd and (not is_pdb_cmd or len(args) > 0):
-            self._pdb_history.history.append(line)
-            self._pdb_history_file.store_inputs(line_num, line)
+    @property
+    def _history_index(self):
+        """Get history index."""
+        if self.is_waiting_pdb_input():
+            return self._pdb_history_index
+        else:
+            return self.__history_index
+
+    @_history_index.setter
+    def _history_index(self, history_index):
+        """Set history index."""
+        if self.is_waiting_pdb_input():
+            self._pdb_history_index = history_index
+        else:
+            self.__history_index = history_index
diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py b/spyder/plugins/ipythonconsole/widgets/shell.py
--- a/spyder/plugins/ipythonconsole/widgets/shell.py
+++ b/spyder/plugins/ipythonconsole/widgets/shell.py
@@ -71,6 +71,10 @@ def __init__(self, ipyclient, additional_options, interpreter_versions,
         self.custom_control = ControlWidget
         self.custom_page_control = PageControlWidget
         self.custom_edit = True
+        self.spyder_kernel_comm = KernelComm(
+            interrupt_callback=self._pdb_update)
+        self.spyder_kernel_comm.sig_exception_occurred.connect(
+            self.sig_exception_occurred)
         super(ShellWidget, self).__init__(*args, **kw)
 
         self.ipyclient = ipyclient
@@ -87,10 +91,6 @@ def __init__(self, ipyclient, additional_options, interpreter_versions,
         # set in the qtconsole constructor. See spyder-ide/spyder#4806.
         self.set_bracket_matcher_color_scheme(self.syntax_style)
 
-        self.spyder_kernel_comm = KernelComm(
-            interrupt_callback=self._pdb_update)
-        self.spyder_kernel_comm.sig_exception_occurred.connect(
-            self.sig_exception_occurred)
         self.kernel_manager = None
         self.kernel_client = None
         handlers = {


Test patch:
diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
--- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
+++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
@@ -748,13 +748,14 @@ def test_save_history_dbg(ipyconsole, qtbot):
     qtbot.keyClick(control, Qt.Key_Enter)
     qtbot.wait(1000)
     # Add a multiline statment and ckeck we can browse it correctly
-    shell._pdb_history.history.append('if True:\n    print(1)')
-    shell._pdb_history.history.append('print(2)')
-    shell._pdb_history.history.append('if True:\n    print(10)')
+    shell._pdb_history.append('if True:\n    print(1)')
+    shell._pdb_history.append('print(2)')
+    shell._pdb_history.append('if True:\n    print(10)')
+    shell._pdb_history_index = len(shell._pdb_history)
     # The continuation prompt is here
     qtbot.keyClick(control, Qt.Key_Up)
     assert '...:     print(10)' in control.toPlainText()
-    shell._control.set_cursor_position(shell._control.get_position('eof') - 2)
+    shell._control.set_cursor_position(shell._control.get_position('eof') - 25)
     qtbot.keyClick(control, Qt.Key_Up)
     assert '...:     print(1)' in control.toPlainText()
 
@@ -1314,5 +1315,35 @@ def test_console_working_directory(ipyconsole, qtbot):
     assert folders[-1] == NEW_DIR
 
 
+@pytest.mark.slow
+@pytest.mark.use_startup_wdir
+def test_pdb_multiline(ipyconsole, qtbot):
+    """Test entering a multiline statment into pdb"""
+    shell = ipyconsole.get_current_shellwidget()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+                    timeout=SHELL_TIMEOUT)
+
+    # Give focus to the widget that's going to receive clicks
+    control = ipyconsole.get_focus_widget()
+    control.setFocus()
+
+    shell.execute('%debug print()')
+    qtbot.wait(1000)
+    assert '\nipdb> ' in control.toPlainText()
+
+    # Test reset magic
+    qtbot.keyClicks(control, 'if True:')
+    qtbot.keyClick(control, Qt.Key_Enter)
+    qtbot.wait(500)
+    qtbot.keyClicks(control, 'bb = 10')
+    qtbot.keyClick(control, Qt.Key_Enter)
+    qtbot.wait(500)
+    qtbot.keyClick(control, Qt.Key_Enter)
+    qtbot.wait(500)
+
+    assert shell.get_value('bb') == 10
+    assert "if True:\n   ...:     bb = 10\n" in control.toPlainText()
+
+
 if __name__ == "__main__":
     pytest.main()