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