Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add permanent history to the debugger #10010

Merged
merged 12 commits into from
Aug 23, 2019
28 changes: 26 additions & 2 deletions spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,11 @@ def test_request_syspath(ipyconsole, qtbot, tmpdir):
@pytest.mark.slow
@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It doesn't work on Windows")
def test_browse_history_dbg(ipyconsole, qtbot):
def test_save_history_dbg(ipyconsole, qtbot):
"""Test that browsing command history is working while debugging."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
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()
Expand Down Expand Up @@ -683,6 +684,29 @@ def test_browse_history_dbg(ipyconsole, qtbot):
qtbot.keyClick(control, Qt.Key_Up)
assert '!aa = 10' in control.toPlainText()

# Open new widget
ipyconsole.create_new_client()

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()

# Generate a traceback and enter debugging mode
with qtbot.waitSignal(shell.executed):
shell.execute('1/0')

shell.execute('%debug')
qtbot.wait(1000)

# Press Up arrow button and assert we get the last
# introduced command
qtbot.keyClick(control, Qt.Key_Up)
assert '!aa = 10' in control.toPlainText()


@pytest.mark.slow
@flaky(max_runs=3)
Expand Down
1 change: 0 additions & 1 deletion spyder/plugins/ipythonconsole/widgets/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
# (see spyder/__init__.py for details)

"""Control widgets used by ShellWidget"""

from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QTextEdit

Expand Down
60 changes: 53 additions & 7 deletions spyder/plugins/ipythonconsole/widgets/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,36 @@
mode and Spyder
"""

import pdb
import re
import pdb

from IPython.core.history import HistoryManager
from qtpy.QtCore import Qt
from qtconsole.rich_jupyter_widget import RichJupyterWidget

from spyder.config.base import get_conf_path
from spyder.config.manager import CONF


class PdbHistory(HistoryManager):

def _get_hist_file_name(self, profile=None):
"""
Get default pdb history file name.

The profile parameter is ignored, but must exist for compatibility with
the parent class.
"""
return get_conf_path('pdb_history.sqlite')


class DebuggingWidget(RichJupyterWidget):
"""
Widget with the necessary attributes and methods to handle
communications between a console in debugging mode and
Spyder
"""
PDB_HIST_MAX = 400

def __init__(self, *args, **kwargs):
super(DebuggingWidget, self).__init__(*args, **kwargs)
Expand All @@ -35,13 +50,22 @@ def __init__(self, *args, **kwargs):
self._previous_prompt = None
self._input_queue = []
self._input_ready = False
self._last_pdb_cmd = ''
self._pdb_line_num = 0
self.pdb_history_file = PdbHistory()
self._control.history = [
line[-1] for line in self.pdb_history_file.get_tail(
self.PDB_HIST_MAX, include_latest=True)]

def set_queued_input(self, client):
"""Change the kernel client input function queue calls."""
old_input = client.input

def queued_input(string):
"""If input is not ready, save it in a queue."""
if not string.strip():
# Must get the last genuine command
string = self._last_pdb_cmd
if self._input_ready:
self._input_ready = False
return old_input(string)
Expand All @@ -54,6 +78,7 @@ def _debugging_hook(self, debugging):
"""Catches debugging state."""
# If debugging starts or stops, clear the input queue.
self._input_queue = []
self._pdb_line_num = 0

# --- Public API --------------------------------------------------
def write_to_stdin(self, line):
Expand Down Expand Up @@ -121,13 +146,16 @@ def _handle_input_request(self, msg):
return

def callback(line):
if not self.is_debugging():
return self.kernel_client.input(line)
line = line.strip()

# Save history to browse it later
if not (len(self._control.history) > 0
and self._control.history[-1] == line):
# do not save pdb commands
cmd = line.split(" ")[0]
if cmd and "do_" + cmd not in dir(pdb.Pdb):
self._control.history.append(line)
self._pdb_line_num += 1
self.add_to_pdb_history(self._pdb_line_num, line)

if line:
self._last_pdb_cmd = line

# must match ConsoleWidget.do_execute
self._executing = True
Expand Down Expand Up @@ -191,3 +219,21 @@ def _event_filter_console_keypress(self, event):
def is_debugging(self):
"""Check if we are debugging."""
return self.spyder_kernel_comm._debugging

def add_to_pdb_history(self, line_num, line):
"""Add command to history"""
self._control.histidx = None
if not line:
return
line = line.strip()

# If repeated line
if len(self._control.history) > 0 and self._control.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._control.history.append(line)
self.pdb_history_file.store_inputs(line_num, line)
3 changes: 2 additions & 1 deletion spyder/widgets/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -1377,13 +1377,14 @@ def browse_history(self, backward):
text, self.histidx = self.find_in_history(tocursor, self.histidx,
backward)
if text is not None:
text = text.strip()
if self.hist_wholeline:
self.clear_line()
self.insert_text(text)
else:
cursor_position = self.get_position('cursor')
# Removing text from cursor to the end of the line
self.remove_text('cursor', 'eol')
self.remove_text('cursor', 'eof')
# Inserting history text
self.insert_text(text)
self.set_cursor_position(cursor_position)
Expand Down