|
import asyncio |
|
import os |
|
import sys |
|
|
|
from IPython.core.debugger import Pdb |
|
from IPython.core.completer import IPCompleter |
|
from .ptutils import IPythonPTCompleter |
|
from .shortcuts import create_ipython_shortcuts |
|
from . import embed |
|
|
|
from pathlib import Path |
|
from pygments.token import Token |
|
from prompt_toolkit.application import create_app_session |
|
from prompt_toolkit.shortcuts.prompt import PromptSession |
|
from prompt_toolkit.enums import EditingMode |
|
from prompt_toolkit.formatted_text import PygmentsTokens |
|
from prompt_toolkit.history import InMemoryHistory, FileHistory |
|
from concurrent.futures import ThreadPoolExecutor |
|
|
|
from prompt_toolkit import __version__ as ptk_version |
|
PTK3 = ptk_version.startswith('3.') |
|
|
|
|
|
|
|
|
|
_use_simple_prompt = "IPY_TEST_SIMPLE_PROMPT" in os.environ |
|
|
|
|
|
class TerminalPdb(Pdb): |
|
"""Standalone IPython debugger.""" |
|
|
|
def __init__(self, *args, pt_session_options=None, **kwargs): |
|
Pdb.__init__(self, *args, **kwargs) |
|
self._ptcomp = None |
|
self.pt_init(pt_session_options) |
|
self.thread_executor = ThreadPoolExecutor(1) |
|
|
|
def pt_init(self, pt_session_options=None): |
|
"""Initialize the prompt session and the prompt loop |
|
and store them in self.pt_app and self.pt_loop. |
|
|
|
Additional keyword arguments for the PromptSession class |
|
can be specified in pt_session_options. |
|
""" |
|
if pt_session_options is None: |
|
pt_session_options = {} |
|
|
|
def get_prompt_tokens(): |
|
return [(Token.Prompt, self.prompt)] |
|
|
|
if self._ptcomp is None: |
|
compl = IPCompleter( |
|
shell=self.shell, namespace={}, global_namespace={}, parent=self.shell |
|
) |
|
|
|
methods_names = [m[3:] for m in dir(self) if m.startswith("do_")] |
|
|
|
def gen_comp(self, text): |
|
return [m for m in methods_names if m.startswith(text)] |
|
import types |
|
newcomp = types.MethodType(gen_comp, compl) |
|
compl.custom_matchers.insert(0, newcomp) |
|
|
|
|
|
self._ptcomp = IPythonPTCompleter(compl) |
|
|
|
|
|
if self.shell.debugger_history is None: |
|
if self.shell.debugger_history_file is not None: |
|
p = Path(self.shell.debugger_history_file).expanduser() |
|
if not p.exists(): |
|
p.touch() |
|
self.debugger_history = FileHistory(os.path.expanduser(str(p))) |
|
else: |
|
self.debugger_history = InMemoryHistory() |
|
else: |
|
self.debugger_history = self.shell.debugger_history |
|
|
|
options = dict( |
|
message=(lambda: PygmentsTokens(get_prompt_tokens())), |
|
editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), |
|
key_bindings=create_ipython_shortcuts(self.shell), |
|
history=self.debugger_history, |
|
completer=self._ptcomp, |
|
enable_history_search=True, |
|
mouse_support=self.shell.mouse_support, |
|
complete_style=self.shell.pt_complete_style, |
|
style=getattr(self.shell, "style", None), |
|
color_depth=self.shell.color_depth, |
|
) |
|
|
|
if not PTK3: |
|
options['inputhook'] = self.shell.inputhook |
|
options.update(pt_session_options) |
|
if not _use_simple_prompt: |
|
self.pt_loop = asyncio.new_event_loop() |
|
self.pt_app = PromptSession(**options) |
|
|
|
def _prompt(self): |
|
""" |
|
In case other prompt_toolkit apps have to run in parallel to this one (e.g. in madbg), |
|
create_app_session must be used to prevent mixing up between them. According to the prompt_toolkit docs: |
|
|
|
> If you need multiple applications running at the same time, you have to create a separate |
|
> `AppSession` using a `with create_app_session():` block. |
|
""" |
|
with create_app_session(): |
|
return self.pt_app.prompt() |
|
|
|
def cmdloop(self, intro=None): |
|
"""Repeatedly issue a prompt, accept input, parse an initial prefix |
|
off the received input, and dispatch to action methods, passing them |
|
the remainder of the line as argument. |
|
|
|
override the same methods from cmd.Cmd to provide prompt toolkit replacement. |
|
""" |
|
if not self.use_rawinput: |
|
raise ValueError('Sorry ipdb does not support use_rawinput=False') |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.preloop() |
|
|
|
try: |
|
if intro is not None: |
|
self.intro = intro |
|
if self.intro: |
|
print(self.intro, file=self.stdout) |
|
stop = None |
|
while not stop: |
|
if self.cmdqueue: |
|
line = self.cmdqueue.pop(0) |
|
else: |
|
self._ptcomp.ipy_completer.namespace = self.curframe_locals |
|
self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals |
|
|
|
|
|
if not _use_simple_prompt: |
|
try: |
|
line = self.thread_executor.submit(self._prompt).result() |
|
except EOFError: |
|
line = "EOF" |
|
else: |
|
line = input("ipdb> ") |
|
|
|
line = self.precmd(line) |
|
stop = self.onecmd(line) |
|
stop = self.postcmd(stop, line) |
|
self.postloop() |
|
except Exception: |
|
raise |
|
|
|
def do_interact(self, arg): |
|
ipshell = embed.InteractiveShellEmbed( |
|
config=self.shell.config, |
|
banner1="*interactive*", |
|
exit_msg="*exiting interactive console...*", |
|
) |
|
global_ns = self.curframe.f_globals |
|
ipshell( |
|
module=sys.modules.get(global_ns["__name__"], None), |
|
local_ns=self.curframe_locals, |
|
) |
|
|
|
|
|
def set_trace(frame=None): |
|
""" |
|
Start debugging from `frame`. |
|
|
|
If frame is not specified, debugging starts from caller's frame. |
|
""" |
|
TerminalPdb().set_trace(frame or sys._getframe().f_back) |
|
|
|
|
|
if __name__ == '__main__': |
|
import pdb |
|
|
|
|
|
|
|
|
|
old_trace_dispatch = pdb.Pdb.trace_dispatch |
|
pdb.Pdb = TerminalPdb |
|
pdb.Pdb.trace_dispatch = old_trace_dispatch |
|
pdb.main() |
|
|