##// END OF EJS Templates
Use prompt_toolkit.application.create_app_session for debugger prompt...
Maor Kleinberger -
Show More
@@ -1,178 +1,188 b''
1 import asyncio
1 import asyncio
2 import os
2 import os
3 import sys
3 import sys
4
4
5 from IPython.core.debugger import Pdb
5 from IPython.core.debugger import Pdb
6 from IPython.core.completer import IPCompleter
6 from IPython.core.completer import IPCompleter
7 from .ptutils import IPythonPTCompleter
7 from .ptutils import IPythonPTCompleter
8 from .shortcuts import create_ipython_shortcuts
8 from .shortcuts import create_ipython_shortcuts
9 from . import embed
9 from . import embed
10
10
11 from pathlib import Path
11 from pathlib import Path
12 from pygments.token import Token
12 from pygments.token import Token
13 from prompt_toolkit.application import create_app_session
13 from prompt_toolkit.shortcuts.prompt import PromptSession
14 from prompt_toolkit.shortcuts.prompt import PromptSession
14 from prompt_toolkit.enums import EditingMode
15 from prompt_toolkit.enums import EditingMode
15 from prompt_toolkit.formatted_text import PygmentsTokens
16 from prompt_toolkit.formatted_text import PygmentsTokens
16 from prompt_toolkit.history import InMemoryHistory, FileHistory
17 from prompt_toolkit.history import InMemoryHistory, FileHistory
17 from concurrent.futures import ThreadPoolExecutor
18 from concurrent.futures import ThreadPoolExecutor
18
19
19 from prompt_toolkit import __version__ as ptk_version
20 from prompt_toolkit import __version__ as ptk_version
20 PTK3 = ptk_version.startswith('3.')
21 PTK3 = ptk_version.startswith('3.')
21
22
22
23
23 # we want to avoid ptk as much as possible when using subprocesses
24 # we want to avoid ptk as much as possible when using subprocesses
24 # as it uses cursor positioning requests, deletes color ....
25 # as it uses cursor positioning requests, deletes color ....
25 _use_simple_prompt = "IPY_TEST_SIMPLE_PROMPT" in os.environ
26 _use_simple_prompt = "IPY_TEST_SIMPLE_PROMPT" in os.environ
26
27
27
28
28 class TerminalPdb(Pdb):
29 class TerminalPdb(Pdb):
29 """Standalone IPython debugger."""
30 """Standalone IPython debugger."""
30
31
31 def __init__(self, *args, pt_session_options=None, **kwargs):
32 def __init__(self, *args, pt_session_options=None, **kwargs):
32 Pdb.__init__(self, *args, **kwargs)
33 Pdb.__init__(self, *args, **kwargs)
33 self._ptcomp = None
34 self._ptcomp = None
34 self.pt_init(pt_session_options)
35 self.pt_init(pt_session_options)
35 self.thread_executor = ThreadPoolExecutor(1)
36 self.thread_executor = ThreadPoolExecutor(1)
36
37
37 def pt_init(self, pt_session_options=None):
38 def pt_init(self, pt_session_options=None):
38 """Initialize the prompt session and the prompt loop
39 """Initialize the prompt session and the prompt loop
39 and store them in self.pt_app and self.pt_loop.
40 and store them in self.pt_app and self.pt_loop.
40
41
41 Additional keyword arguments for the PromptSession class
42 Additional keyword arguments for the PromptSession class
42 can be specified in pt_session_options.
43 can be specified in pt_session_options.
43 """
44 """
44 if pt_session_options is None:
45 if pt_session_options is None:
45 pt_session_options = {}
46 pt_session_options = {}
46
47
47 def get_prompt_tokens():
48 def get_prompt_tokens():
48 return [(Token.Prompt, self.prompt)]
49 return [(Token.Prompt, self.prompt)]
49
50
50 if self._ptcomp is None:
51 if self._ptcomp is None:
51 compl = IPCompleter(
52 compl = IPCompleter(
52 shell=self.shell, namespace={}, global_namespace={}, parent=self.shell
53 shell=self.shell, namespace={}, global_namespace={}, parent=self.shell
53 )
54 )
54 # add a completer for all the do_ methods
55 # add a completer for all the do_ methods
55 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
56 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
56
57
57 def gen_comp(self, text):
58 def gen_comp(self, text):
58 return [m for m in methods_names if m.startswith(text)]
59 return [m for m in methods_names if m.startswith(text)]
59 import types
60 import types
60 newcomp = types.MethodType(gen_comp, compl)
61 newcomp = types.MethodType(gen_comp, compl)
61 compl.custom_matchers.insert(0, newcomp)
62 compl.custom_matchers.insert(0, newcomp)
62 # end add completer.
63 # end add completer.
63
64
64 self._ptcomp = IPythonPTCompleter(compl)
65 self._ptcomp = IPythonPTCompleter(compl)
65
66
66 # setup history only when we start pdb
67 # setup history only when we start pdb
67 if self.shell.debugger_history is None:
68 if self.shell.debugger_history is None:
68 if self.shell.debugger_history_file is not None:
69 if self.shell.debugger_history_file is not None:
69
70
70 p = Path(self.shell.debugger_history_file).expanduser()
71 p = Path(self.shell.debugger_history_file).expanduser()
71 if not p.exists():
72 if not p.exists():
72 p.touch()
73 p.touch()
73 self.debugger_history = FileHistory(os.path.expanduser(str(p)))
74 self.debugger_history = FileHistory(os.path.expanduser(str(p)))
74 else:
75 else:
75 self.debugger_history = InMemoryHistory()
76 self.debugger_history = InMemoryHistory()
76 else:
77 else:
77 self.debugger_history = self.shell.debugger_history
78 self.debugger_history = self.shell.debugger_history
78
79
79 options = dict(
80 options = dict(
80 message=(lambda: PygmentsTokens(get_prompt_tokens())),
81 message=(lambda: PygmentsTokens(get_prompt_tokens())),
81 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
82 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
82 key_bindings=create_ipython_shortcuts(self.shell),
83 key_bindings=create_ipython_shortcuts(self.shell),
83 history=self.debugger_history,
84 history=self.debugger_history,
84 completer=self._ptcomp,
85 completer=self._ptcomp,
85 enable_history_search=True,
86 enable_history_search=True,
86 mouse_support=self.shell.mouse_support,
87 mouse_support=self.shell.mouse_support,
87 complete_style=self.shell.pt_complete_style,
88 complete_style=self.shell.pt_complete_style,
88 style=getattr(self.shell, "style", None),
89 style=getattr(self.shell, "style", None),
89 color_depth=self.shell.color_depth,
90 color_depth=self.shell.color_depth,
90 )
91 )
91
92
92 if not PTK3:
93 if not PTK3:
93 options['inputhook'] = self.shell.inputhook
94 options['inputhook'] = self.shell.inputhook
94 options.update(pt_session_options)
95 options.update(pt_session_options)
95 if not _use_simple_prompt:
96 if not _use_simple_prompt:
96 self.pt_loop = asyncio.new_event_loop()
97 self.pt_loop = asyncio.new_event_loop()
97 self.pt_app = PromptSession(**options)
98 self.pt_app = PromptSession(**options)
98
99
100 def _prompt(self):
101 """
102 In case another prompt_toolkit apps have to run in parallel to this one (e.g. in madbg),
103 create_app_session must be used to prevent mixing up between them. According to the prompt_toolkit docs:
104
105 If you need multiple applications running at the same time, you have to create a separate
106 `AppSession` using a `with create_app_session():` block.
107 """
108 with create_app_session():
109 return self.pt_app.prompt()
110
99 def cmdloop(self, intro=None):
111 def cmdloop(self, intro=None):
100 """Repeatedly issue a prompt, accept input, parse an initial prefix
112 """Repeatedly issue a prompt, accept input, parse an initial prefix
101 off the received input, and dispatch to action methods, passing them
113 off the received input, and dispatch to action methods, passing them
102 the remainder of the line as argument.
114 the remainder of the line as argument.
103
115
104 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
116 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
105 """
117 """
106 if not self.use_rawinput:
118 if not self.use_rawinput:
107 raise ValueError('Sorry ipdb does not support use_rawinput=False')
119 raise ValueError('Sorry ipdb does not support use_rawinput=False')
108
120
109 # In order to make sure that prompt, which uses asyncio doesn't
121 # In order to make sure that prompt, which uses asyncio doesn't
110 # interfere with applications in which it's used, we always run the
122 # interfere with applications in which it's used, we always run the
111 # prompt itself in a different thread (we can't start an event loop
123 # prompt itself in a different thread (we can't start an event loop
112 # within an event loop). This new thread won't have any event loop
124 # within an event loop). This new thread won't have any event loop
113 # running, and here we run our prompt-loop.
125 # running, and here we run our prompt-loop.
114 self.preloop()
126 self.preloop()
115
127
116 try:
128 try:
117 if intro is not None:
129 if intro is not None:
118 self.intro = intro
130 self.intro = intro
119 if self.intro:
131 if self.intro:
120 print(self.intro, file=self.stdout)
132 print(self.intro, file=self.stdout)
121 stop = None
133 stop = None
122 while not stop:
134 while not stop:
123 if self.cmdqueue:
135 if self.cmdqueue:
124 line = self.cmdqueue.pop(0)
136 line = self.cmdqueue.pop(0)
125 else:
137 else:
126 self._ptcomp.ipy_completer.namespace = self.curframe_locals
138 self._ptcomp.ipy_completer.namespace = self.curframe_locals
127 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
139 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
128
140
129 # Run the prompt in a different thread.
141 # Run the prompt in a different thread.
130 if not _use_simple_prompt:
142 if not _use_simple_prompt:
131 try:
143 try:
132 line = self.thread_executor.submit(
144 line = self.thread_executor.submit(self._prompt).result()
133 self.pt_app.prompt
134 ).result()
135 except EOFError:
145 except EOFError:
136 line = "EOF"
146 line = "EOF"
137 else:
147 else:
138 line = input("ipdb> ")
148 line = input("ipdb> ")
139
149
140 line = self.precmd(line)
150 line = self.precmd(line)
141 stop = self.onecmd(line)
151 stop = self.onecmd(line)
142 stop = self.postcmd(stop, line)
152 stop = self.postcmd(stop, line)
143 self.postloop()
153 self.postloop()
144 except Exception:
154 except Exception:
145 raise
155 raise
146
156
147 def do_interact(self, arg):
157 def do_interact(self, arg):
148 ipshell = embed.InteractiveShellEmbed(
158 ipshell = embed.InteractiveShellEmbed(
149 config=self.shell.config,
159 config=self.shell.config,
150 banner1="*interactive*",
160 banner1="*interactive*",
151 exit_msg="*exiting interactive console...*",
161 exit_msg="*exiting interactive console...*",
152 )
162 )
153 global_ns = self.curframe.f_globals
163 global_ns = self.curframe.f_globals
154 ipshell(
164 ipshell(
155 module=sys.modules.get(global_ns["__name__"], None),
165 module=sys.modules.get(global_ns["__name__"], None),
156 local_ns=self.curframe_locals,
166 local_ns=self.curframe_locals,
157 )
167 )
158
168
159
169
160 def set_trace(frame=None):
170 def set_trace(frame=None):
161 """
171 """
162 Start debugging from `frame`.
172 Start debugging from `frame`.
163
173
164 If frame is not specified, debugging starts from caller's frame.
174 If frame is not specified, debugging starts from caller's frame.
165 """
175 """
166 TerminalPdb().set_trace(frame or sys._getframe().f_back)
176 TerminalPdb().set_trace(frame or sys._getframe().f_back)
167
177
168
178
169 if __name__ == '__main__':
179 if __name__ == '__main__':
170 import pdb
180 import pdb
171 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
181 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
172 # bdb.BdbQuit. When started through __main__ and an exception
182 # bdb.BdbQuit. When started through __main__ and an exception
173 # happened after hitting "c", this is needed in order to
183 # happened after hitting "c", this is needed in order to
174 # be able to quit the debugging session (see #9950).
184 # be able to quit the debugging session (see #9950).
175 old_trace_dispatch = pdb.Pdb.trace_dispatch
185 old_trace_dispatch = pdb.Pdb.trace_dispatch
176 pdb.Pdb = TerminalPdb # type: ignore
186 pdb.Pdb = TerminalPdb # type: ignore
177 pdb.Pdb.trace_dispatch = old_trace_dispatch # type: ignore
187 pdb.Pdb.trace_dispatch = old_trace_dispatch # type: ignore
178 pdb.main()
188 pdb.main()
General Comments 0
You need to be logged in to leave comments. Login now