##// END OF EJS Templates
Run ipdb in separate thread (required when using within asyncio applications).
Jonathan Slenders -
Show More
@@ -1,147 +1,152 b''
1 import asyncio
1 import asyncio
2 import signal
2 import signal
3 import sys
3 import sys
4 import threading
4
5
5 from IPython.core.debugger import Pdb
6 from IPython.core.debugger import Pdb
6
7
7 from IPython.core.completer import IPCompleter
8 from IPython.core.completer import IPCompleter
8 from .ptutils import IPythonPTCompleter
9 from .ptutils import IPythonPTCompleter
9 from .shortcuts import suspend_to_bg, cursor_in_leading_ws
10 from .shortcuts import suspend_to_bg, cursor_in_leading_ws
10
11
11 from prompt_toolkit.enums import DEFAULT_BUFFER
12 from prompt_toolkit.enums import DEFAULT_BUFFER
12 from prompt_toolkit.filters import (Condition, has_focus, has_selection,
13 from prompt_toolkit.filters import (Condition, has_focus, has_selection,
13 vi_insert_mode, emacs_insert_mode)
14 vi_insert_mode, emacs_insert_mode)
14 from prompt_toolkit.key_binding import KeyBindings
15 from prompt_toolkit.key_binding import KeyBindings
15 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
16 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
16 from pygments.token import Token
17 from pygments.token import Token
17 from prompt_toolkit.shortcuts.prompt import PromptSession
18 from prompt_toolkit.shortcuts.prompt import PromptSession
18 from prompt_toolkit.enums import EditingMode
19 from prompt_toolkit.enums import EditingMode
19 from prompt_toolkit.formatted_text import PygmentsTokens
20 from prompt_toolkit.formatted_text import PygmentsTokens
20
21
21 from prompt_toolkit import __version__ as ptk_version
22 from prompt_toolkit import __version__ as ptk_version
22 PTK3 = ptk_version.startswith('3.')
23 PTK3 = ptk_version.startswith('3.')
23
24
24
25
25 class TerminalPdb(Pdb):
26 class TerminalPdb(Pdb):
26 """Standalone IPython debugger."""
27 """Standalone IPython debugger."""
27
28
28 def __init__(self, *args, **kwargs):
29 def __init__(self, *args, **kwargs):
29 Pdb.__init__(self, *args, **kwargs)
30 Pdb.__init__(self, *args, **kwargs)
30 self._ptcomp = None
31 self._ptcomp = None
31 self.pt_init()
32 self.pt_init()
32
33
33 def pt_init(self):
34 def pt_init(self):
34 def get_prompt_tokens():
35 def get_prompt_tokens():
35 return [(Token.Prompt, self.prompt)]
36 return [(Token.Prompt, self.prompt)]
36
37
37 if self._ptcomp is None:
38 if self._ptcomp is None:
38 compl = IPCompleter(shell=self.shell,
39 compl = IPCompleter(shell=self.shell,
39 namespace={},
40 namespace={},
40 global_namespace={},
41 global_namespace={},
41 parent=self.shell,
42 parent=self.shell,
42 )
43 )
43 self._ptcomp = IPythonPTCompleter(compl)
44 self._ptcomp = IPythonPTCompleter(compl)
44
45
45 kb = KeyBindings()
46 kb = KeyBindings()
46 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
47 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
47 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
48 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
48
49
49 if self.shell.display_completions == 'readlinelike':
50 if self.shell.display_completions == 'readlinelike':
50 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
51 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
51 & ~has_selection
52 & ~has_selection
52 & vi_insert_mode | emacs_insert_mode
53 & vi_insert_mode | emacs_insert_mode
53 & ~cursor_in_leading_ws
54 & ~cursor_in_leading_ws
54 ))(display_completions_like_readline)
55 ))(display_completions_like_readline)
55
56
56 options = dict(
57 options = dict(
57 message=(lambda: PygmentsTokens(get_prompt_tokens())),
58 message=(lambda: PygmentsTokens(get_prompt_tokens())),
58 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
59 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
59 key_bindings=kb,
60 key_bindings=kb,
60 history=self.shell.debugger_history,
61 history=self.shell.debugger_history,
61 completer=self._ptcomp,
62 completer=self._ptcomp,
62 enable_history_search=True,
63 enable_history_search=True,
63 mouse_support=self.shell.mouse_support,
64 mouse_support=self.shell.mouse_support,
64 complete_style=self.shell.pt_complete_style,
65 complete_style=self.shell.pt_complete_style,
65 style=self.shell.style,
66 style=self.shell.style,
66 color_depth=self.shell.color_depth,
67 color_depth=self.shell.color_depth,
67 )
68 )
68
69
69 if not PTK3:
70 if not PTK3:
70 options['inputhook'] = self.shell.inputhook
71 options['inputhook'] = self.shell.inputhook
71 self.pt_loop = asyncio.new_event_loop()
72 self.pt_loop = asyncio.new_event_loop()
72 self.pt_app = PromptSession(**options)
73 self.pt_app = PromptSession(**options)
73
74
74 def cmdloop(self, intro=None):
75 def cmdloop(self, intro=None):
75 """Repeatedly issue a prompt, accept input, parse an initial prefix
76 """Repeatedly issue a prompt, accept input, parse an initial prefix
76 off the received input, and dispatch to action methods, passing them
77 off the received input, and dispatch to action methods, passing them
77 the remainder of the line as argument.
78 the remainder of the line as argument.
78
79
79 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
80 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
80 """
81 """
81 if not self.use_rawinput:
82 if not self.use_rawinput:
82 raise ValueError('Sorry ipdb does not support use_rawinput=False')
83 raise ValueError('Sorry ipdb does not support use_rawinput=False')
83
84
84 # In order to make sure that asyncio code written in the
85 # In order to make sure that prompt, which uses asyncio doesn't
85 # interactive shell doesn't interfere with the prompt, we run the
86 # interfere with applications in which it's used, we always run the
86 # prompt in a different event loop.
87 # prompt itself in a different thread (we can't start an event loop
87 # If we don't do this, people could spawn coroutine with a
88 # within an event loop). This new thread won't have any event loop
88 # while/true inside which will freeze the prompt.
89 # running, and here we run our prompt-loop.
89
90 try:
91 old_loop = asyncio.get_event_loop()
92 except RuntimeError:
93 # This happens when the user used `asyncio.run()`.
94 old_loop = None
95
96
90
97 self.preloop()
91 self.preloop()
98
92
99 try:
93 try:
100 if intro is not None:
94 if intro is not None:
101 self.intro = intro
95 self.intro = intro
102 if self.intro:
96 if self.intro:
103 self.stdout.write(str(self.intro)+"\n")
97 self.stdout.write(str(self.intro)+"\n")
104 stop = None
98 stop = None
105 while not stop:
99 while not stop:
106 if self.cmdqueue:
100 if self.cmdqueue:
107 line = self.cmdqueue.pop(0)
101 line = self.cmdqueue.pop(0)
108 else:
102 else:
109 self._ptcomp.ipy_completer.namespace = self.curframe_locals
103 self._ptcomp.ipy_completer.namespace = self.curframe_locals
110 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
104 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
111
105
112 asyncio.set_event_loop(self.pt_loop)
106 # Run the prompt in a different thread.
113 try:
107 line = ''
114 line = self.pt_app.prompt()
108 keyboard_interrupt = False
115 except EOFError:
109
116 line = 'EOF'
110 def in_thread():
117 finally:
111 nonlocal line, keyboard_interrupt
118 # Restore the original event loop.
112 try:
119 asyncio.set_event_loop(old_loop)
113 line = self.pt_app.prompt()
114 except EOFError:
115 line = 'EOF'
116 except KeyboardInterrupt:
117 keyboard_interrupt = True
118
119 th = threading.Thread(target=in_thread)
120 th.start()
121 th.join()
122
123 if keyboard_interrupt:
124 raise KeyboardInterrupt
120
125
121 line = self.precmd(line)
126 line = self.precmd(line)
122 stop = self.onecmd(line)
127 stop = self.onecmd(line)
123 stop = self.postcmd(stop, line)
128 stop = self.postcmd(stop, line)
124 self.postloop()
129 self.postloop()
125 except Exception:
130 except Exception:
126 raise
131 raise
127
132
128
133
129 def set_trace(frame=None):
134 def set_trace(frame=None):
130 """
135 """
131 Start debugging from `frame`.
136 Start debugging from `frame`.
132
137
133 If frame is not specified, debugging starts from caller's frame.
138 If frame is not specified, debugging starts from caller's frame.
134 """
139 """
135 TerminalPdb().set_trace(frame or sys._getframe().f_back)
140 TerminalPdb().set_trace(frame or sys._getframe().f_back)
136
141
137
142
138 if __name__ == '__main__':
143 if __name__ == '__main__':
139 import pdb
144 import pdb
140 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
145 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
141 # bdb.BdbQuit. When started through __main__ and an exception
146 # bdb.BdbQuit. When started through __main__ and an exception
142 # happened after hitting "c", this is needed in order to
147 # happened after hitting "c", this is needed in order to
143 # be able to quit the debugging session (see #9950).
148 # be able to quit the debugging session (see #9950).
144 old_trace_dispatch = pdb.Pdb.trace_dispatch
149 old_trace_dispatch = pdb.Pdb.trace_dispatch
145 pdb.Pdb = TerminalPdb
150 pdb.Pdb = TerminalPdb
146 pdb.Pdb.trace_dispatch = old_trace_dispatch
151 pdb.Pdb.trace_dispatch = old_trace_dispatch
147 pdb.main()
152 pdb.main()
General Comments 0
You need to be logged in to leave comments. Login now