##// END OF EJS Templates
Add history file to debugger....
Matthias Bussonnier -
Show More
@@ -1,166 +1,181 b''
1 1 import asyncio
2 import os
2 3 import sys
3 4 import threading
4 5
5 6 from IPython.core.debugger import Pdb
6 7
8
7 9 from IPython.core.completer import IPCompleter
8 10 from .ptutils import IPythonPTCompleter
9 11 from .shortcuts import create_ipython_shortcuts
10 12 from . import embed
11 13
14 from pathlib import Path
12 15 from pygments.token import Token
13 16 from prompt_toolkit.shortcuts.prompt import PromptSession
14 17 from prompt_toolkit.enums import EditingMode
15 18 from prompt_toolkit.formatted_text import PygmentsTokens
19 from prompt_toolkit.history import InMemoryHistory, FileHistory
16 20
17 21 from prompt_toolkit import __version__ as ptk_version
18 22 PTK3 = ptk_version.startswith('3.')
19 23
20 24
21 25 class TerminalPdb(Pdb):
22 26 """Standalone IPython debugger."""
23 27
24 28 def __init__(self, *args, pt_session_options=None, **kwargs):
25 29 Pdb.__init__(self, *args, **kwargs)
26 30 self._ptcomp = None
27 31 self.pt_init(pt_session_options)
28 32
29 33 def pt_init(self, pt_session_options=None):
30 34 """Initialize the prompt session and the prompt loop
31 35 and store them in self.pt_app and self.pt_loop.
32 36
33 37 Additional keyword arguments for the PromptSession class
34 38 can be specified in pt_session_options.
35 39 """
36 40 if pt_session_options is None:
37 41 pt_session_options = {}
38 42
39 43 def get_prompt_tokens():
40 44 return [(Token.Prompt, self.prompt)]
41 45
42 46 if self._ptcomp is None:
43 47 compl = IPCompleter(
44 48 shell=self.shell, namespace={}, global_namespace={}, parent=self.shell
45 49 )
46 50 # add a completer for all the do_ methods
47 51 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
48 52
49 53 def gen_comp(self, text):
50 54 return [m for m in methods_names if m.startswith(text)]
51 55 import types
52 56 newcomp = types.MethodType(gen_comp, compl)
53 57 compl.custom_matchers.insert(0, newcomp)
54 58 # end add completer.
55 59
56 60 self._ptcomp = IPythonPTCompleter(compl)
57 61
62 # setup history only when we start pdb
63 if self.shell.debugger_history is None:
64 if self.shell.debugger_history_file is not None:
65
66 p = Path(self.shell.debugger_history_file).expanduser()
67 if not p.exists():
68 p.touch()
69 self.debugger_history = FileHistory(os.path.expanduser(str(p)))
70 else:
71 self.debugger_history = InMemoryHistory()
72
58 73 options = dict(
59 74 message=(lambda: PygmentsTokens(get_prompt_tokens())),
60 75 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
61 76 key_bindings=create_ipython_shortcuts(self.shell),
62 77 history=self.shell.debugger_history,
63 78 completer=self._ptcomp,
64 79 enable_history_search=True,
65 80 mouse_support=self.shell.mouse_support,
66 81 complete_style=self.shell.pt_complete_style,
67 82 style=self.shell.style,
68 83 color_depth=self.shell.color_depth,
69 84 )
70 85
71 86 if not PTK3:
72 87 options['inputhook'] = self.shell.inputhook
73 88 options.update(pt_session_options)
74 89 self.pt_loop = asyncio.new_event_loop()
75 90 self.pt_app = PromptSession(**options)
76 91
77 92 def cmdloop(self, intro=None):
78 93 """Repeatedly issue a prompt, accept input, parse an initial prefix
79 94 off the received input, and dispatch to action methods, passing them
80 95 the remainder of the line as argument.
81 96
82 97 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
83 98 """
84 99 if not self.use_rawinput:
85 100 raise ValueError('Sorry ipdb does not support use_rawinput=False')
86 101
87 102 # In order to make sure that prompt, which uses asyncio doesn't
88 103 # interfere with applications in which it's used, we always run the
89 104 # prompt itself in a different thread (we can't start an event loop
90 105 # within an event loop). This new thread won't have any event loop
91 106 # running, and here we run our prompt-loop.
92 107
93 108 self.preloop()
94 109
95 110 try:
96 111 if intro is not None:
97 112 self.intro = intro
98 113 if self.intro:
99 114 self.stdout.write(str(self.intro)+"\n")
100 115 stop = None
101 116 while not stop:
102 117 if self.cmdqueue:
103 118 line = self.cmdqueue.pop(0)
104 119 else:
105 120 self._ptcomp.ipy_completer.namespace = self.curframe_locals
106 121 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
107 122
108 123 # Run the prompt in a different thread.
109 124 line = ''
110 125 keyboard_interrupt = False
111 126
112 127 def in_thread():
113 128 nonlocal line, keyboard_interrupt
114 129 try:
115 130 line = self.pt_app.prompt()
116 131 except EOFError:
117 132 line = 'EOF'
118 133 except KeyboardInterrupt:
119 134 keyboard_interrupt = True
120 135
121 136 th = threading.Thread(target=in_thread)
122 137 th.start()
123 138 th.join()
124 139
125 140 if keyboard_interrupt:
126 141 raise KeyboardInterrupt
127 142
128 143 line = self.precmd(line)
129 144 stop = self.onecmd(line)
130 145 stop = self.postcmd(stop, line)
131 146 self.postloop()
132 147 except Exception:
133 148 raise
134 149
135 150 def do_interact(self, arg):
136 151 ipshell = embed.InteractiveShellEmbed(
137 152 config=self.shell.config,
138 153 banner1="*interactive*",
139 154 exit_msg="*exiting interactive console...*",
140 155 )
141 156 global_ns = self.curframe.f_globals
142 157 ipshell(
143 158 module=sys.modules.get(global_ns["__name__"], None),
144 159 local_ns=self.curframe_locals,
145 160 )
146 161
147 162
148 163 def set_trace(frame=None):
149 164 """
150 165 Start debugging from `frame`.
151 166
152 167 If frame is not specified, debugging starts from caller's frame.
153 168 """
154 169 TerminalPdb().set_trace(frame or sys._getframe().f_back)
155 170
156 171
157 172 if __name__ == '__main__':
158 173 import pdb
159 174 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
160 175 # bdb.BdbQuit. When started through __main__ and an exception
161 176 # happened after hitting "c", this is needed in order to
162 177 # be able to quit the debugging session (see #9950).
163 178 old_trace_dispatch = pdb.Pdb.trace_dispatch
164 179 pdb.Pdb = TerminalPdb # type: ignore
165 180 pdb.Pdb.trace_dispatch = old_trace_dispatch # type: ignore
166 181 pdb.main()
@@ -1,692 +1,695 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import asyncio
4 4 import os
5 5 import sys
6 6 import warnings
7 7 from warnings import warn
8 8
9 9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 10 from IPython.utils import io
11 11 from IPython.utils.py3compat import input
12 12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
13 13 from IPython.utils.process import abbrev_cwd
14 14 from traitlets import (
15 15 Bool,
16 16 Unicode,
17 17 Dict,
18 18 Integer,
19 19 observe,
20 20 Instance,
21 21 Type,
22 22 default,
23 23 Enum,
24 24 Union,
25 25 Any,
26 26 validate,
27 27 Float,
28 28 )
29 29
30 30 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
31 31 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
32 32 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
33 33 from prompt_toolkit.formatted_text import PygmentsTokens
34 34 from prompt_toolkit.history import InMemoryHistory
35 35 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
36 36 from prompt_toolkit.output import ColorDepth
37 37 from prompt_toolkit.patch_stdout import patch_stdout
38 38 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
39 39 from prompt_toolkit.styles import DynamicStyle, merge_styles
40 40 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
41 41 from prompt_toolkit import __version__ as ptk_version
42 42
43 43 from pygments.styles import get_style_by_name
44 44 from pygments.style import Style
45 45 from pygments.token import Token
46 46
47 47 from .debugger import TerminalPdb, Pdb
48 48 from .magics import TerminalMagics
49 49 from .pt_inputhooks import get_inputhook_name_and_func
50 50 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
51 51 from .ptutils import IPythonPTCompleter, IPythonPTLexer
52 52 from .shortcuts import create_ipython_shortcuts
53 53
54 54 DISPLAY_BANNER_DEPRECATED = object()
55 55 PTK3 = ptk_version.startswith('3.')
56 56
57 57
58 58 class _NoStyle(Style): pass
59 59
60 60
61 61
62 62 _style_overrides_light_bg = {
63 63 Token.Prompt: '#ansibrightblue',
64 64 Token.PromptNum: '#ansiblue bold',
65 65 Token.OutPrompt: '#ansibrightred',
66 66 Token.OutPromptNum: '#ansired bold',
67 67 }
68 68
69 69 _style_overrides_linux = {
70 70 Token.Prompt: '#ansibrightgreen',
71 71 Token.PromptNum: '#ansigreen bold',
72 72 Token.OutPrompt: '#ansibrightred',
73 73 Token.OutPromptNum: '#ansired bold',
74 74 }
75 75
76 76 def get_default_editor():
77 77 try:
78 78 return os.environ['EDITOR']
79 79 except KeyError:
80 80 pass
81 81 except UnicodeError:
82 82 warn("$EDITOR environment variable is not pure ASCII. Using platform "
83 83 "default editor.")
84 84
85 85 if os.name == 'posix':
86 86 return 'vi' # the only one guaranteed to be there!
87 87 else:
88 88 return 'notepad' # same in Windows!
89 89
90 90 # conservatively check for tty
91 91 # overridden streams can result in things like:
92 92 # - sys.stdin = None
93 93 # - no isatty method
94 94 for _name in ('stdin', 'stdout', 'stderr'):
95 95 _stream = getattr(sys, _name)
96 96 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
97 97 _is_tty = False
98 98 break
99 99 else:
100 100 _is_tty = True
101 101
102 102
103 103 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
104 104
105 105 def black_reformat_handler(text_before_cursor):
106 106 import black
107 107 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
108 108 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
109 109 formatted_text = formatted_text[:-1]
110 110 return formatted_text
111 111
112 112
113 113 class TerminalInteractiveShell(InteractiveShell):
114 114 mime_renderers = Dict().tag(config=True)
115 115
116 116 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
117 117 'to reserve for the tab completion menu, '
118 118 'search history, ...etc, the height of '
119 119 'these menus will at most this value. '
120 120 'Increase it is you prefer long and skinny '
121 121 'menus, decrease for short and wide.'
122 122 ).tag(config=True)
123 123
124 124 pt_app = None
125 125 debugger_history = None
126 126
127 debugger_history_file = Unicode(
128 "~/.pdbhistory", help="File in which to store and read history"
129 ).tag(config=True)
130
127 131 simple_prompt = Bool(_use_simple_prompt,
128 132 help="""Use `raw_input` for the REPL, without completion and prompt colors.
129 133
130 134 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
131 135 IPython own testing machinery, and emacs inferior-shell integration through elpy.
132 136
133 137 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
134 138 environment variable is set, or the current terminal is not a tty."""
135 139 ).tag(config=True)
136 140
137 141 @property
138 142 def debugger_cls(self):
139 143 return Pdb if self.simple_prompt else TerminalPdb
140 144
141 145 confirm_exit = Bool(True,
142 146 help="""
143 147 Set to confirm when you try to exit IPython with an EOF (Control-D
144 148 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
145 149 you can force a direct exit without any confirmation.""",
146 150 ).tag(config=True)
147 151
148 152 editing_mode = Unicode('emacs',
149 153 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
150 154 ).tag(config=True)
151 155
152 156 emacs_bindings_in_vi_insert_mode = Bool(
153 157 True,
154 158 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
155 159 ).tag(config=True)
156 160
157 161 modal_cursor = Bool(
158 162 True,
159 163 help="""
160 164 Cursor shape changes depending on vi mode: beam in vi insert mode,
161 165 block in nav mode, underscore in replace mode.""",
162 166 ).tag(config=True)
163 167
164 168 ttimeoutlen = Float(
165 169 0.01,
166 170 help="""The time in milliseconds that is waited for a key code
167 171 to complete.""",
168 172 ).tag(config=True)
169 173
170 174 timeoutlen = Float(
171 175 0.5,
172 176 help="""The time in milliseconds that is waited for a mapped key
173 177 sequence to complete.""",
174 178 ).tag(config=True)
175 179
176 180 autoformatter = Unicode(None,
177 181 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
178 182 allow_none=True
179 183 ).tag(config=True)
180 184
181 185 mouse_support = Bool(False,
182 186 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
183 187 ).tag(config=True)
184 188
185 189 # We don't load the list of styles for the help string, because loading
186 190 # Pygments plugins takes time and can cause unexpected errors.
187 191 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
188 192 help="""The name or class of a Pygments style to use for syntax
189 193 highlighting. To see available styles, run `pygmentize -L styles`."""
190 194 ).tag(config=True)
191 195
192 196 @validate('editing_mode')
193 197 def _validate_editing_mode(self, proposal):
194 198 if proposal['value'].lower() == 'vim':
195 199 proposal['value']= 'vi'
196 200 elif proposal['value'].lower() == 'default':
197 201 proposal['value']= 'emacs'
198 202
199 203 if hasattr(EditingMode, proposal['value'].upper()):
200 204 return proposal['value'].lower()
201 205
202 206 return self.editing_mode
203 207
204 208
205 209 @observe('editing_mode')
206 210 def _editing_mode(self, change):
207 211 if self.pt_app:
208 212 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
209 213
210 214 @observe('autoformatter')
211 215 def _autoformatter_changed(self, change):
212 216 formatter = change.new
213 217 if formatter is None:
214 218 self.reformat_handler = lambda x:x
215 219 elif formatter == 'black':
216 220 self.reformat_handler = black_reformat_handler
217 221 else:
218 222 raise ValueError
219 223
220 224 @observe('highlighting_style')
221 225 @observe('colors')
222 226 def _highlighting_style_changed(self, change):
223 227 self.refresh_style()
224 228
225 229 def refresh_style(self):
226 230 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
227 231
228 232
229 233 highlighting_style_overrides = Dict(
230 234 help="Override highlighting format for specific tokens"
231 235 ).tag(config=True)
232 236
233 237 true_color = Bool(False,
234 238 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
235 239 If your terminal supports true color, the following command should
236 240 print ``TRUECOLOR`` in orange::
237 241
238 242 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
239 243 """,
240 244 ).tag(config=True)
241 245
242 246 editor = Unicode(get_default_editor(),
243 247 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
244 248 ).tag(config=True)
245 249
246 250 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
247 251
248 252 prompts = Instance(Prompts)
249 253
250 254 @default('prompts')
251 255 def _prompts_default(self):
252 256 return self.prompts_class(self)
253 257
254 258 # @observe('prompts')
255 259 # def _(self, change):
256 260 # self._update_layout()
257 261
258 262 @default('displayhook_class')
259 263 def _displayhook_class_default(self):
260 264 return RichPromptDisplayHook
261 265
262 266 term_title = Bool(True,
263 267 help="Automatically set the terminal title"
264 268 ).tag(config=True)
265 269
266 270 term_title_format = Unicode("IPython: {cwd}",
267 271 help="Customize the terminal title format. This is a python format string. " +
268 272 "Available substitutions are: {cwd}."
269 273 ).tag(config=True)
270 274
271 275 display_completions = Enum(('column', 'multicolumn','readlinelike'),
272 276 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
273 277 "'readlinelike'. These options are for `prompt_toolkit`, see "
274 278 "`prompt_toolkit` documentation for more information."
275 279 ),
276 280 default_value='multicolumn').tag(config=True)
277 281
278 282 highlight_matching_brackets = Bool(True,
279 283 help="Highlight matching brackets.",
280 284 ).tag(config=True)
281 285
282 286 extra_open_editor_shortcuts = Bool(False,
283 287 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
284 288 "This is in addition to the F2 binding, which is always enabled."
285 289 ).tag(config=True)
286 290
287 291 handle_return = Any(None,
288 292 help="Provide an alternative handler to be called when the user presses "
289 293 "Return. This is an advanced option intended for debugging, which "
290 294 "may be changed or removed in later releases."
291 295 ).tag(config=True)
292 296
293 297 enable_history_search = Bool(True,
294 298 help="Allows to enable/disable the prompt toolkit history search"
295 299 ).tag(config=True)
296 300
297 301 prompt_includes_vi_mode = Bool(True,
298 302 help="Display the current vi mode (when using vi editing mode)."
299 303 ).tag(config=True)
300 304
301 305 @observe('term_title')
302 306 def init_term_title(self, change=None):
303 307 # Enable or disable the terminal title.
304 308 if self.term_title:
305 309 toggle_set_term_title(True)
306 310 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
307 311 else:
308 312 toggle_set_term_title(False)
309 313
310 314 def restore_term_title(self):
311 315 if self.term_title:
312 316 restore_term_title()
313 317
314 318 def init_display_formatter(self):
315 319 super(TerminalInteractiveShell, self).init_display_formatter()
316 320 # terminal only supports plain text
317 321 self.display_formatter.active_types = ['text/plain']
318 322 # disable `_ipython_display_`
319 323 self.display_formatter.ipython_display_formatter.enabled = False
320 324
321 325 def init_prompt_toolkit_cli(self):
322 326 if self.simple_prompt:
323 327 # Fall back to plain non-interactive output for tests.
324 328 # This is very limited.
325 329 def prompt():
326 330 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
327 331 lines = [input(prompt_text)]
328 332 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
329 333 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
330 334 lines.append( input(prompt_continuation) )
331 335 return '\n'.join(lines)
332 336 self.prompt_for_code = prompt
333 337 return
334 338
335 339 # Set up keyboard shortcuts
336 340 key_bindings = create_ipython_shortcuts(self)
337 341
338 342 # Pre-populate history from IPython's history database
339 343 history = InMemoryHistory()
340 344 last_cell = u""
341 345 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
342 346 include_latest=True):
343 347 # Ignore blank lines and consecutive duplicates
344 348 cell = cell.rstrip()
345 349 if cell and (cell != last_cell):
346 350 history.append_string(cell)
347 351 last_cell = cell
348 352
349 353 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
350 354 self.style = DynamicStyle(lambda: self._style)
351 355
352 356 editing_mode = getattr(EditingMode, self.editing_mode.upper())
353 357
354 358 self.pt_loop = asyncio.new_event_loop()
355 359 self.pt_app = PromptSession(
356 360 auto_suggest=AutoSuggestFromHistory(),
357 361 editing_mode=editing_mode,
358 362 key_bindings=key_bindings,
359 363 history=history,
360 364 completer=IPythonPTCompleter(shell=self),
361 365 enable_history_search=self.enable_history_search,
362 366 style=self.style,
363 367 include_default_pygments_style=False,
364 368 mouse_support=self.mouse_support,
365 369 enable_open_in_editor=self.extra_open_editor_shortcuts,
366 370 color_depth=self.color_depth,
367 371 tempfile_suffix=".py",
368 372 **self._extra_prompt_options()
369 373 )
370 374
371 375 def _make_style_from_name_or_cls(self, name_or_cls):
372 376 """
373 377 Small wrapper that make an IPython compatible style from a style name
374 378
375 379 We need that to add style for prompt ... etc.
376 380 """
377 381 style_overrides = {}
378 382 if name_or_cls == 'legacy':
379 383 legacy = self.colors.lower()
380 384 if legacy == 'linux':
381 385 style_cls = get_style_by_name('monokai')
382 386 style_overrides = _style_overrides_linux
383 387 elif legacy == 'lightbg':
384 388 style_overrides = _style_overrides_light_bg
385 389 style_cls = get_style_by_name('pastie')
386 390 elif legacy == 'neutral':
387 391 # The default theme needs to be visible on both a dark background
388 392 # and a light background, because we can't tell what the terminal
389 393 # looks like. These tweaks to the default theme help with that.
390 394 style_cls = get_style_by_name('default')
391 395 style_overrides.update({
392 396 Token.Number: '#ansigreen',
393 397 Token.Operator: 'noinherit',
394 398 Token.String: '#ansiyellow',
395 399 Token.Name.Function: '#ansiblue',
396 400 Token.Name.Class: 'bold #ansiblue',
397 401 Token.Name.Namespace: 'bold #ansiblue',
398 402 Token.Name.Variable.Magic: '#ansiblue',
399 403 Token.Prompt: '#ansigreen',
400 404 Token.PromptNum: '#ansibrightgreen bold',
401 405 Token.OutPrompt: '#ansired',
402 406 Token.OutPromptNum: '#ansibrightred bold',
403 407 })
404 408
405 409 # Hack: Due to limited color support on the Windows console
406 410 # the prompt colors will be wrong without this
407 411 if os.name == 'nt':
408 412 style_overrides.update({
409 413 Token.Prompt: '#ansidarkgreen',
410 414 Token.PromptNum: '#ansigreen bold',
411 415 Token.OutPrompt: '#ansidarkred',
412 416 Token.OutPromptNum: '#ansired bold',
413 417 })
414 418 elif legacy =='nocolor':
415 419 style_cls=_NoStyle
416 420 style_overrides = {}
417 421 else :
418 422 raise ValueError('Got unknown colors: ', legacy)
419 423 else :
420 424 if isinstance(name_or_cls, str):
421 425 style_cls = get_style_by_name(name_or_cls)
422 426 else:
423 427 style_cls = name_or_cls
424 428 style_overrides = {
425 429 Token.Prompt: '#ansigreen',
426 430 Token.PromptNum: '#ansibrightgreen bold',
427 431 Token.OutPrompt: '#ansired',
428 432 Token.OutPromptNum: '#ansibrightred bold',
429 433 }
430 434 style_overrides.update(self.highlighting_style_overrides)
431 435 style = merge_styles([
432 436 style_from_pygments_cls(style_cls),
433 437 style_from_pygments_dict(style_overrides),
434 438 ])
435 439
436 440 return style
437 441
438 442 @property
439 443 def pt_complete_style(self):
440 444 return {
441 445 'multicolumn': CompleteStyle.MULTI_COLUMN,
442 446 'column': CompleteStyle.COLUMN,
443 447 'readlinelike': CompleteStyle.READLINE_LIKE,
444 448 }[self.display_completions]
445 449
446 450 @property
447 451 def color_depth(self):
448 452 return (ColorDepth.TRUE_COLOR if self.true_color else None)
449 453
450 454 def _extra_prompt_options(self):
451 455 """
452 456 Return the current layout option for the current Terminal InteractiveShell
453 457 """
454 458 def get_message():
455 459 return PygmentsTokens(self.prompts.in_prompt_tokens())
456 460
457 461 if self.editing_mode == 'emacs':
458 462 # with emacs mode the prompt is (usually) static, so we call only
459 463 # the function once. With VI mode it can toggle between [ins] and
460 464 # [nor] so we can't precompute.
461 465 # here I'm going to favor the default keybinding which almost
462 466 # everybody uses to decrease CPU usage.
463 467 # if we have issues with users with custom Prompts we can see how to
464 468 # work around this.
465 469 get_message = get_message()
466 470
467 471 options = {
468 472 'complete_in_thread': False,
469 473 'lexer':IPythonPTLexer(),
470 474 'reserve_space_for_menu':self.space_for_menu,
471 475 'message': get_message,
472 476 'prompt_continuation': (
473 477 lambda width, lineno, is_soft_wrap:
474 478 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
475 479 'multiline': True,
476 480 'complete_style': self.pt_complete_style,
477 481
478 482 # Highlight matching brackets, but only when this setting is
479 483 # enabled, and only when the DEFAULT_BUFFER has the focus.
480 484 'input_processors': [ConditionalProcessor(
481 485 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
482 486 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
483 487 Condition(lambda: self.highlight_matching_brackets))],
484 488 }
485 489 if not PTK3:
486 490 options['inputhook'] = self.inputhook
487 491
488 492 return options
489 493
490 494 def prompt_for_code(self):
491 495 if self.rl_next_input:
492 496 default = self.rl_next_input
493 497 self.rl_next_input = None
494 498 else:
495 499 default = ''
496 500
497 501 # In order to make sure that asyncio code written in the
498 502 # interactive shell doesn't interfere with the prompt, we run the
499 503 # prompt in a different event loop.
500 504 # If we don't do this, people could spawn coroutine with a
501 505 # while/true inside which will freeze the prompt.
502 506
503 507 try:
504 508 old_loop = asyncio.get_event_loop()
505 509 except RuntimeError:
506 510 # This happens when the user used `asyncio.run()`.
507 511 old_loop = None
508 512
509 513 asyncio.set_event_loop(self.pt_loop)
510 514 try:
511 515 with patch_stdout(raw=True):
512 516 text = self.pt_app.prompt(
513 517 default=default,
514 518 **self._extra_prompt_options())
515 519 finally:
516 520 # Restore the original event loop.
517 521 asyncio.set_event_loop(old_loop)
518 522
519 523 return text
520 524
521 525 def enable_win_unicode_console(self):
522 526 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
523 527 # console by default, so WUC shouldn't be needed.
524 528 from warnings import warn
525 529 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
526 530 DeprecationWarning,
527 531 stacklevel=2)
528 532
529 533 def init_io(self):
530 534 if sys.platform not in {'win32', 'cli'}:
531 535 return
532 536
533 537 import colorama
534 538 colorama.init()
535 539
536 540 # For some reason we make these wrappers around stdout/stderr.
537 541 # For now, we need to reset them so all output gets coloured.
538 542 # https://github.com/ipython/ipython/issues/8669
539 543 # io.std* are deprecated, but don't show our own deprecation warnings
540 544 # during initialization of the deprecated API.
541 545 with warnings.catch_warnings():
542 546 warnings.simplefilter('ignore', DeprecationWarning)
543 547 io.stdout = io.IOStream(sys.stdout)
544 548 io.stderr = io.IOStream(sys.stderr)
545 549
546 550 def init_magics(self):
547 551 super(TerminalInteractiveShell, self).init_magics()
548 552 self.register_magics(TerminalMagics)
549 553
550 554 def init_alias(self):
551 555 # The parent class defines aliases that can be safely used with any
552 556 # frontend.
553 557 super(TerminalInteractiveShell, self).init_alias()
554 558
555 559 # Now define aliases that only make sense on the terminal, because they
556 560 # need direct access to the console in a way that we can't emulate in
557 561 # GUI or web frontend
558 562 if os.name == 'posix':
559 563 for cmd in ('clear', 'more', 'less', 'man'):
560 564 self.alias_manager.soft_define_alias(cmd, cmd)
561 565
562 566
563 567 def __init__(self, *args, **kwargs):
564 568 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
565 569 self.init_prompt_toolkit_cli()
566 570 self.init_term_title()
567 571 self.keep_running = True
568 572
569 self.debugger_history = InMemoryHistory()
570 573
571 574 def ask_exit(self):
572 575 self.keep_running = False
573 576
574 577 rl_next_input = None
575 578
576 579 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
577 580
578 581 if display_banner is not DISPLAY_BANNER_DEPRECATED:
579 582 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
580 583
581 584 self.keep_running = True
582 585 while self.keep_running:
583 586 print(self.separate_in, end='')
584 587
585 588 try:
586 589 code = self.prompt_for_code()
587 590 except EOFError:
588 591 if (not self.confirm_exit) \
589 592 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
590 593 self.ask_exit()
591 594
592 595 else:
593 596 if code:
594 597 self.run_cell(code, store_history=True)
595 598
596 599 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
597 600 # An extra layer of protection in case someone mashing Ctrl-C breaks
598 601 # out of our internal code.
599 602 if display_banner is not DISPLAY_BANNER_DEPRECATED:
600 603 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
601 604 while True:
602 605 try:
603 606 self.interact()
604 607 break
605 608 except KeyboardInterrupt as e:
606 609 print("\n%s escaped interact()\n" % type(e).__name__)
607 610 finally:
608 611 # An interrupt during the eventloop will mess up the
609 612 # internal state of the prompt_toolkit library.
610 613 # Stopping the eventloop fixes this, see
611 614 # https://github.com/ipython/ipython/pull/9867
612 615 if hasattr(self, '_eventloop'):
613 616 self._eventloop.stop()
614 617
615 618 self.restore_term_title()
616 619
617 620 # try to call some at-exit operation optimistically as some things can't
618 621 # be done during interpreter shutdown. this is technically inaccurate as
619 622 # this make mainlool not re-callable, but that should be a rare if not
620 623 # in existent use case.
621 624
622 625 self._atexit_once()
623 626
624 627
625 628 _inputhook = None
626 629 def inputhook(self, context):
627 630 if self._inputhook is not None:
628 631 self._inputhook(context)
629 632
630 633 active_eventloop = None
631 634 def enable_gui(self, gui=None):
632 635 if gui and (gui != 'inline') :
633 636 self.active_eventloop, self._inputhook =\
634 637 get_inputhook_name_and_func(gui)
635 638 else:
636 639 self.active_eventloop = self._inputhook = None
637 640
638 641 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
639 642 # this inputhook.
640 643 if PTK3:
641 644 import asyncio
642 645 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
643 646
644 647 if gui == 'asyncio':
645 648 # When we integrate the asyncio event loop, run the UI in the
646 649 # same event loop as the rest of the code. don't use an actual
647 650 # input hook. (Asyncio is not made for nesting event loops.)
648 651 self.pt_loop = asyncio.get_event_loop()
649 652
650 653 elif self._inputhook:
651 654 # If an inputhook was set, create a new asyncio event loop with
652 655 # this inputhook for the prompt.
653 656 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
654 657 else:
655 658 # When there's no inputhook, run the prompt in a separate
656 659 # asyncio event loop.
657 660 self.pt_loop = asyncio.new_event_loop()
658 661
659 662 # Run !system commands directly, not through pipes, so terminal programs
660 663 # work correctly.
661 664 system = InteractiveShell.system_raw
662 665
663 666 def auto_rewrite_input(self, cmd):
664 667 """Overridden from the parent class to use fancy rewriting prompt"""
665 668 if not self.show_rewritten_input:
666 669 return
667 670
668 671 tokens = self.prompts.rewrite_prompt_tokens()
669 672 if self.pt_app:
670 673 print_formatted_text(PygmentsTokens(tokens), end='',
671 674 style=self.pt_app.app.style)
672 675 print(cmd)
673 676 else:
674 677 prompt = ''.join(s for t, s in tokens)
675 678 print(prompt, cmd, sep='')
676 679
677 680 _prompts_before = None
678 681 def switch_doctest_mode(self, mode):
679 682 """Switch prompts to classic for %doctest_mode"""
680 683 if mode:
681 684 self._prompts_before = self.prompts
682 685 self.prompts = ClassicPrompts(self)
683 686 elif self._prompts_before:
684 687 self.prompts = self._prompts_before
685 688 self._prompts_before = None
686 689 # self._update_layout()
687 690
688 691
689 692 InteractiveShellABC.register(TerminalInteractiveShell)
690 693
691 694 if __name__ == '__main__':
692 695 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now