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: Use '!' for Pdb commands and add other options to control the debugger (IPython console) #12134

Merged
merged 22 commits into from
Oct 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8754119
Add numbered prompt and single letter mode
Oct 1, 2020
96353ad
Add ! prefix for pdb and create a debug preferences section
Oct 1, 2020
8d24729
Update the tests for the PR
Oct 1, 2020
71408b9
git subrepo clone --branch=separate_cmd_input --force https://github.…
Oct 1, 2020
d4f953a
remove one parenthesis IPdb
Oct 1, 2020
ad123f4
Apply suggestions from code review
impact27 Oct 2, 2020
3edbe0f
Merge remote-tracking branch 'upstream/4.x' into separate_cmd_input
impact27 Oct 12, 2020
cdd1508
remove single letter explore mode
impact27 Oct 12, 2020
0b72859
git subrepo clone --branch=separate_cmd_input --force https://github.…
Oct 13, 2020
324ca89
remove explore mode test
Oct 13, 2020
6ad8dd4
check falls back on pdb commands
Oct 13, 2020
8910d71
remove unused shortcut
impact27 Oct 13, 2020
ba0bed2
Fix history filtering
impact27 Oct 13, 2020
4e2db17
Fix silent_execute
impact27 Oct 13, 2020
2411082
pep8: Limit line length
Oct 13, 2020
aa73a47
Merge remote-tracking branch 'upstream/4.x' into separate_cmd_input
Oct 23, 2020
c165780
bump CONF_VERSION
Oct 23, 2020
7aef47e
git subrepo clone (merge) --branch=separate_cmd_input --force https:/…
Oct 23, 2020
3abf6c6
change default value for pdb_use_exclamation_mark
Oct 24, 2020
4070f46
git subrepo clone --branch=separate_cmd_input --force https://github.…
Oct 24, 2020
3cbb3d7
git subrepo clone (merge) --branch=1.x --force https://github.com/spy…
Oct 24, 2020
f3bc395
Merge branch '4.x' into separate_cmd_input
ccordoba12 Oct 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions external-deps/spyder-kernels/.gitrepo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/spyder-ide/spyder-kernels.git
branch = 1.x
commit = 6b467eba7a38a8e6796b751c55f344b4168e5fa3
parent = 0a5af377ab7c42a564f0fd5737eee03c1047b637
commit = 5e430c34b26314b17423d10ee4d7366115e73ccb
parent = 4070f46d3c4744d7a9f8e128fb4da05be60f0ca9
method = merge
cmdver = 0.4.1
9 changes: 9 additions & 0 deletions external-deps/spyder-kernels/spyder_kernels/console/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, *args, **kwargs):
'set_breakpoints': self.set_spyder_breakpoints,
'set_pdb_ignore_lib': self.set_pdb_ignore_lib,
'set_pdb_execute_events': self.set_pdb_execute_events,
'set_pdb_use_exclamation_mark': self.set_pdb_use_exclamation_mark,
'get_value': self.get_value,
'load_data': self.load_data,
'save_namespace': self.save_namespace,
Expand Down Expand Up @@ -293,6 +294,14 @@ def set_pdb_execute_events(self, state):
if self._pdb_obj:
self._pdb_obj.pdb_execute_events = state

def set_pdb_use_exclamation_mark(self, state):
"""
Set an option on the current debugging session to decide wether
the Pdb commands needs to be prefixed by '!'
"""
if self._pdb_obj:
self._pdb_obj.pdb_use_exclamation_mark = state

def pdb_input_reply(self, line, echo_stack_entry=True):
"""Get a pdb command from the frontend."""
if self._pdb_obj:
Expand Down
238 changes: 217 additions & 21 deletions external-deps/spyder-kernels/spyder_kernels/customize/spyderpdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,44 @@

if not PY2:
from IPython.core.inputtransformer2 import TransformerManager
import builtins
basestring = (str,)
else:
import __builtin__ as builtins
from IPython.core.inputsplitter import IPythonInputSplitter as TransformerManager


logger = logging.getLogger(__name__)


class DebugWrapper(object):
"""
Notifies the frontend when debuggging starts/stops
"""
def __init__(self, pdb_obj):
self.pdb_obj = pdb_obj

def __enter__(self):
"""
Debugging starts.
"""
self.pdb_obj._frontend_notified = True
try:
frontend_request(blocking=True).set_debug_state(True)
except (CommError, TimeoutError):
logger.debug("Could not send debugging state to the frontend.")

def __exit__(self, exc_type, exc_val, exc_tb):
"""
Debugging ends.
"""
self.pdb_obj._frontend_notified = False
try:
frontend_request(blocking=True).set_debug_state(False)
except (CommError, TimeoutError):
logger.debug("Could not send debugging state to the frontend.")


class SpyderPdb(ipyPdb, object): # Inherits `object` to call super() in PY2
"""
Extends Pdb to add features:
Expand All @@ -51,11 +81,22 @@ def __init__(self, completekey='tab', stdin=None, stdout=None,
self.continue_if_has_breakpoints = False
self.pdb_ignore_lib = False
self.pdb_execute_events = False
self.pdb_use_exclamation_mark = False
self._exclamation_warning_printed = False
self.pdb_stop_first_line = True
self._disable_next_stack_entry = False
super(SpyderPdb, self).__init__()
self._pdb_breaking = False
self._frontend_notified = False

# --- Methods overriden for code execution
def print_exclamation_warning(self):
"""Print pdb warning for exclamation mark."""
if not self._exclamation_warning_printed:
print("Warning: '!' option enabled."
" Use '!' as an optionnal prefix for pdb commands.")
self._exclamation_warning_printed = True

def default(self, line):
"""
Default way of running pdb statment.
Expand All @@ -67,6 +108,10 @@ def default(self, line):
execute_events = self.pdb_execute_events
if line[:1] == '!':
line = line[1:]
elif self.pdb_use_exclamation_mark:
self.print_exclamation_warning()
self.error("Unknown command '" + line.split()[0] + "'")
return
# Disallow the use of %debug magic in the debugger
if line.startswith("%debug"):
self.error("Please don't use '%debug' in the debugger.\n"
Expand All @@ -85,6 +130,18 @@ def default(self, line):
else:
ns = self.curframe.f_globals

if self.pdb_use_exclamation_mark:
# Find pdb commands executed without !
cmd, arg, line = self.parseline(line)
if cmd and cmd not in ns and cmd not in builtins.__dict__:
# Check if it is not an assignment
if not (arg and arg[0] == "="):
func = getattr(self, 'do_' + cmd, None)
if func:
self.lastcmd = line
return func(arg)
elif cmd:
self.print_exclamation_warning()
try:
line = TransformerManager().transform_cell(line)
try:
Expand Down Expand Up @@ -152,7 +209,11 @@ def interaction(self, frame, traceback):

self.setup(frame, traceback)
self.print_stack_entry(self.stack[self.curindex])
self._cmdloop()
if self._frontend_notified:
self._cmdloop()
else:
with DebugWrapper(self):
self._cmdloop()
self.forget()

def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
Expand Down Expand Up @@ -203,6 +264,15 @@ def do_complete(self, code, cursor_pos):
"""
Respond to a complete request.
"""
if self.pdb_use_exclamation_mark:
return self._complete_exclamation(code, cursor_pos)
else:
return self._complete_default(code, cursor_pos)

def _complete_default(self, code, cursor_pos):
"""
Respond to a complete request if not pdb_use_exclamation_mark.
"""
if cursor_pos is None:
cursor_pos = len(code)

Expand Down Expand Up @@ -290,14 +360,101 @@ def is_name_or_composed(text):
'metadata': {},
'status': 'ok'}

def _complete_exclamation(self, code, cursor_pos):
"""
Respond to a complete request if pdb_use_exclamation_mark.
"""
if cursor_pos is None:
cursor_pos = len(code)

# Get text to complete
text = code[:cursor_pos].split(' ')[-1]
# Choose Pdb function to complete, based on cmd.py
origline = code
line = origline.lstrip()
if not line:
# Nothing to complete
return
is_pdb_command = line[0] == '!'
is_pdb_command_name = False

stripped = len(origline) - len(line)
begidx = cursor_pos - len(text) - stripped
endidx = cursor_pos - stripped

compfunc = None

if is_pdb_command:
line = line[1:]
begidx -= 1
endidx -= 1
if begidx == -1:
is_pdb_command_name = True
text = text[1:]
begidx += 1
compfunc = self.completenames
else:
cmd, args, _ = self.parseline(line)
if cmd != '':
try:
# Function to complete Pdb command arguments
compfunc = getattr(self, 'complete_' + cmd)
except AttributeError:
# This command doesn't exist, nothing to complete
return
else:
# We don't know this command
return

if not is_pdb_command_name:
# Remove eg. leading opening parenthesis
def is_name_or_composed(text):
if not text or text[0] == '.':
return False
# We want to keep value.subvalue
return isidentifier(text.replace('.', ''))

while text and not is_name_or_composed(text):
text = text[1:]
begidx += 1

cursor_start = cursor_pos - len(text)
matches = []
if is_pdb_command:
matches = compfunc(text, line, begidx, endidx)
return {
'matches': matches,
'cursor_end': cursor_pos,
'cursor_start': cursor_start,
'metadata': {},
'status': 'ok'
}

kernel = get_ipython().kernel
# Make complete call with current frame
if self.curframe:
if self.curframe_locals:
Frame = namedtuple("Frame", ["f_locals", "f_globals"])
frame = Frame(self.curframe_locals,
self.curframe.f_globals)
else:
frame = self.curframe
kernel.shell.set_completer_frame(frame)
result = kernel._do_complete(code, cursor_pos)
# Reset frame
kernel.shell.set_completer_frame()
return result

# --- Methods overriden by us for Spyder integration
def preloop(self):
"""Ask Spyder for breakpoints before the first prompt is created."""
try:
frontend_request(blocking=True).set_debug_state(True)
pdb_settings = frontend_request().get_pdb_settings()
self.pdb_ignore_lib = pdb_settings['pdb_ignore_lib']
self.pdb_execute_events = pdb_settings['pdb_execute_events']
self.pdb_use_exclamation_mark = pdb_settings[
'pdb_use_exclamation_mark']
self.pdb_stop_first_line = pdb_settings['pdb_stop_first_line']
if self.starting:
self.set_spyder_breakpoints(pdb_settings['breakpoints'])
if self.send_initial_notification:
Expand All @@ -306,14 +463,6 @@ def preloop(self):
logger.debug("Could not get breakpoints from the frontend.")
super(SpyderPdb, self).preloop()

def postloop(self):
"""Notifies spyder that the loop has ended."""
try:
frontend_request(blocking=True).set_debug_state(False)
except (CommError, TimeoutError):
logger.debug("Could not send debugging state to the frontend.")
super(SpyderPdb, self).postloop()

def set_continue(self):
"""
Stop only at breakpoints or when finished.
Expand Down Expand Up @@ -369,13 +518,23 @@ def _cmdloop(self):
_print("--KeyboardInterrupt--\n"
"For copying text while debugging, use Ctrl+Shift+C",
file=self.stdout)
except Exception:
try:
frontend_request(blocking=True).set_debug_state(False)
except (CommError, TimeoutError):
logger.debug(
"Could not send debugging state to the frontend.")
raise

def precmd(self, line):
"""
Hook method executed just before the command line is
interpreted, but after the input prompt is generated and issued.

Here we switch ! and non !
"""
if not self.pdb_use_exclamation_mark:
return line
if not line:
return line
if line[0] == '!':
line = line[1:]
else:
line = '!' + line
return line

def postcmd(self, stop, line):
"""
Expand Down Expand Up @@ -453,11 +612,24 @@ def set_spyder_breakpoints(self, breakpoints):
# Do 'continue' if the first breakpoint is *not* placed
# where the debugger is going to land.
# Fixes issue 4681
if (self.continue_if_has_breakpoints and
breaks and
lineno < breaks[0]):
if self.pdb_stop_first_line:
do_continue = (
self.continue_if_has_breakpoints
and breaks
and lineno < breaks[0])
else:
# The breakpoint could be in another file.
do_continue = (
self.continue_if_has_breakpoints
and not (breaks and lineno >= breaks[0]))

if do_continue:
try:
frontend_request(blocking=False).pdb_execute('continue')
if self.pdb_use_exclamation_mark:
cont_cmd = '!continue'
else:
cont_cmd = 'continue'
frontend_request(blocking=False).pdb_execute(cont_cmd)
except (CommError, TimeoutError):
logger.debug(
"Could not send a Pdb continue call to the frontend.")
Expand Down Expand Up @@ -493,3 +665,27 @@ def notify_spyder(self, frame=None):
kernel.publish_pdb_state()
except (CommError, TimeoutError):
logger.debug("Could not send Pdb state to the frontend.")

def run(self, cmd, globals=None, locals=None):
"""Debug a statement executed via the exec() function.

globals defaults to __main__.dict; locals defaults to globals.
"""
with DebugWrapper(self):
super(SpyderPdb, self).run(cmd, globals, locals)

def runeval(self, expr, globals=None, locals=None):
"""Debug an expression executed via the eval() function.

globals defaults to __main__.dict; locals defaults to globals.
"""
with DebugWrapper(self):
super(SpyderPdb, self).runeval(expr, globals, locals)

def runcall(self, *args, **kwds):
"""Debug a single function call.

Return the result of the function call.
"""
with DebugWrapper(self):
super(SpyderPdb, self).runcall(*args, **kwds)