##// END OF EJS Templates
Start embedded IPython shell for ipdb interact, #12913
Iwan Briquemont -
Show More
@@ -0,0 +1,4 b''
1 IPython shell for ipdb interact
2 -------------------------------
3
4 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
@@ -1,153 +1,166 b''
1 1 import asyncio
2 2 import sys
3 3 import threading
4 4
5 5 from IPython.core.debugger import Pdb
6 6
7 7 from IPython.core.completer import IPCompleter
8 8 from .ptutils import IPythonPTCompleter
9 9 from .shortcuts import create_ipython_shortcuts
10 from . import embed
10 11
11 12 from pygments.token import Token
12 13 from prompt_toolkit.shortcuts.prompt import PromptSession
13 14 from prompt_toolkit.enums import EditingMode
14 15 from prompt_toolkit.formatted_text import PygmentsTokens
15 16
16 17 from prompt_toolkit import __version__ as ptk_version
17 18 PTK3 = ptk_version.startswith('3.')
18 19
19 20
20 21 class TerminalPdb(Pdb):
21 22 """Standalone IPython debugger."""
22 23
23 24 def __init__(self, *args, pt_session_options=None, **kwargs):
24 25 Pdb.__init__(self, *args, **kwargs)
25 26 self._ptcomp = None
26 27 self.pt_init(pt_session_options)
27 28
28 29 def pt_init(self, pt_session_options=None):
29 30 """Initialize the prompt session and the prompt loop
30 31 and store them in self.pt_app and self.pt_loop.
31 32
32 33 Additional keyword arguments for the PromptSession class
33 34 can be specified in pt_session_options.
34 35 """
35 36 if pt_session_options is None:
36 37 pt_session_options = {}
37 38
38 39 def get_prompt_tokens():
39 40 return [(Token.Prompt, self.prompt)]
40 41
41 42 if self._ptcomp is None:
42 43 compl = IPCompleter(
43 44 shell=self.shell, namespace={}, global_namespace={}, parent=self.shell
44 45 )
45 46 # add a completer for all the do_ methods
46 47 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
47 48
48 49 def gen_comp(self, text):
49 50 return [m for m in methods_names if m.startswith(text)]
50 51 import types
51 52 newcomp = types.MethodType(gen_comp, compl)
52 53 compl.custom_matchers.insert(0, newcomp)
53 54 # end add completer.
54 55
55 56 self._ptcomp = IPythonPTCompleter(compl)
56 57
57 58 options = dict(
58 59 message=(lambda: PygmentsTokens(get_prompt_tokens())),
59 60 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
60 61 key_bindings=create_ipython_shortcuts(self.shell),
61 62 history=self.shell.debugger_history,
62 63 completer=self._ptcomp,
63 64 enable_history_search=True,
64 65 mouse_support=self.shell.mouse_support,
65 66 complete_style=self.shell.pt_complete_style,
66 67 style=self.shell.style,
67 68 color_depth=self.shell.color_depth,
68 69 )
69 70
70 71 if not PTK3:
71 72 options['inputhook'] = self.shell.inputhook
72 73 options.update(pt_session_options)
73 74 self.pt_loop = asyncio.new_event_loop()
74 75 self.pt_app = PromptSession(**options)
75 76
76 77 def cmdloop(self, intro=None):
77 78 """Repeatedly issue a prompt, accept input, parse an initial prefix
78 79 off the received input, and dispatch to action methods, passing them
79 80 the remainder of the line as argument.
80 81
81 82 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
82 83 """
83 84 if not self.use_rawinput:
84 85 raise ValueError('Sorry ipdb does not support use_rawinput=False')
85 86
86 87 # In order to make sure that prompt, which uses asyncio doesn't
87 88 # interfere with applications in which it's used, we always run the
88 89 # prompt itself in a different thread (we can't start an event loop
89 90 # within an event loop). This new thread won't have any event loop
90 91 # running, and here we run our prompt-loop.
91 92
92 93 self.preloop()
93 94
94 95 try:
95 96 if intro is not None:
96 97 self.intro = intro
97 98 if self.intro:
98 99 self.stdout.write(str(self.intro)+"\n")
99 100 stop = None
100 101 while not stop:
101 102 if self.cmdqueue:
102 103 line = self.cmdqueue.pop(0)
103 104 else:
104 105 self._ptcomp.ipy_completer.namespace = self.curframe_locals
105 106 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
106 107
107 108 # Run the prompt in a different thread.
108 109 line = ''
109 110 keyboard_interrupt = False
110 111
111 112 def in_thread():
112 113 nonlocal line, keyboard_interrupt
113 114 try:
114 115 line = self.pt_app.prompt()
115 116 except EOFError:
116 117 line = 'EOF'
117 118 except KeyboardInterrupt:
118 119 keyboard_interrupt = True
119 120
120 121 th = threading.Thread(target=in_thread)
121 122 th.start()
122 123 th.join()
123 124
124 125 if keyboard_interrupt:
125 126 raise KeyboardInterrupt
126 127
127 128 line = self.precmd(line)
128 129 stop = self.onecmd(line)
129 130 stop = self.postcmd(stop, line)
130 131 self.postloop()
131 132 except Exception:
132 133 raise
133 134
135 def do_interact(self, arg):
136 ipshell = embed.InteractiveShellEmbed(
137 config=self.shell.config,
138 banner1="*interactive*",
139 exit_msg="now exiting InteractiveConsole...",
140 )
141 global_ns = self.curframe.f_globals
142 ipshell(
143 module=sys.modules.get(global_ns["__name__"], None),
144 local_ns=self.curframe_locals,
145 )
146
134 147
135 148 def set_trace(frame=None):
136 149 """
137 150 Start debugging from `frame`.
138 151
139 152 If frame is not specified, debugging starts from caller's frame.
140 153 """
141 154 TerminalPdb().set_trace(frame or sys._getframe().f_back)
142 155
143 156
144 157 if __name__ == '__main__':
145 158 import pdb
146 159 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
147 160 # bdb.BdbQuit. When started through __main__ and an exception
148 161 # happened after hitting "c", this is needed in order to
149 162 # be able to quit the debugging session (see #9950).
150 163 old_trace_dispatch = pdb.Pdb.trace_dispatch
151 164 pdb.Pdb = TerminalPdb
152 165 pdb.Pdb.trace_dispatch = old_trace_dispatch
153 166 pdb.main()
General Comments 0
You need to be logged in to leave comments. Login now