##// END OF EJS Templates
Simplify picking debugger class
Thomas Kluyver -
Show More
@@ -1,449 +1,444 b''
1 1 """IPython terminal interface using prompt_toolkit in place of readline"""
2 2 from __future__ import print_function
3 3
4 4 import os
5 5 import sys
6 6 import signal
7 7 from warnings import warn
8 8
9 9 from IPython.core.error import TryNext
10 10 from IPython.core.interactiveshell import InteractiveShell
11 11 from IPython.utils.py3compat import cast_unicode_py2, input
12 12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 13 from IPython.utils.process import abbrev_cwd
14 14 from traitlets import Bool, Unicode, Dict, Integer, observe
15 15
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
17 17 from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode, IsDone
18 18 from prompt_toolkit.history import InMemoryHistory
19 19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
20 20 from prompt_toolkit.interface import CommandLineInterface
21 21 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 22 from prompt_toolkit.keys import Keys
23 23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
24 24 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
25 25
26 26 from pygments.styles import get_style_by_name, get_all_styles
27 27 from pygments.token import Token
28 28
29 29 from .debugger import TerminalPdb, Pdb
30 30 from .pt_inputhooks import get_inputhook_func
31 31 from .interactiveshell import get_default_editor, TerminalMagics
32 32 from .ptutils import IPythonPTCompleter, IPythonPTLexer
33 33
34 34 _use_simple_prompt = 'IPY_TEST_SIMPLE_PROMPT' in os.environ or not sys.stdin.isatty()
35 35
36 36 class TerminalInteractiveShell(InteractiveShell):
37 37 colors_force = True
38 38
39 39 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
40 40 'to reserve for the completion menu'
41 41 ).tag(config=True)
42 42
43 43 def _space_for_menu_changed(self, old, new):
44 44 self._update_layout()
45 45
46 46 pt_cli = None
47 47 debugger_history = None
48 debugger_cls = TerminalPdb
49 48
50 49 simple_prompt = Bool(_use_simple_prompt,
51 50 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
52 51
53 52 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
54 53 IPython own testing machinery, and emacs inferior-shell integration through elpy.
55 54
56 55 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
57 56 environment variable is set, or the current terminal is not a tty.
58 57
59 58 """
60 59 ).tag(config=True)
61 60
62 @observe('simple_prompt')
63 def _simple_prompt_changed(self, changes):
64 if changes['new'] == True:
65 self.debugger_cls = Pdb
66 else:
67 self.debugger_cls = TerminalPdb
68
61 @property
62 def debugger_cls(self):
63 return Pdb if self.simple_prompt else TerminalPdb
69 64
70 65 autoedit_syntax = Bool(False,
71 66 help="auto editing of files with syntax errors.",
72 67 ).tag(config=True)
73 68
74 69
75 70 confirm_exit = Bool(True,
76 71 help="""
77 72 Set to confirm when you try to exit IPython with an EOF (Control-D
78 73 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
79 74 you can force a direct exit without any confirmation.""",
80 75 ).tag(config=True)
81 76
82 77 editing_mode = Unicode('emacs',
83 78 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
84 79 ).tag(config=True)
85 80
86 81 mouse_support = Bool(False,
87 82 help="Enable mouse support in the prompt"
88 83 ).tag(config=True)
89 84
90 85 highlighting_style = Unicode('default',
91 86 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
92 87 ).tag(config=True)
93 88
94 89
95 90 @observe('highlighting_style')
96 91 def _highlighting_style_changed(self, change):
97 92 self._style = self._make_style_from_name(self.highlighting_style)
98 93
99 94 highlighting_style_overrides = Dict(
100 95 help="Override highlighting format for specific tokens"
101 96 ).tag(config=True)
102 97
103 98 editor = Unicode(get_default_editor(),
104 99 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
105 100 ).tag(config=True)
106 101
107 102 term_title = Bool(True,
108 103 help="Automatically set the terminal title"
109 104 ).tag(config=True)
110 105
111 106 display_completions_in_columns = Bool(False,
112 107 help="Display a multi column completion menu.",
113 108 ).tag(config=True)
114 109
115 110 highlight_matching_brackets = Bool(True,
116 111 help="Highlight matching brackets .",
117 112 ).tag(config=True)
118 113
119 114 @observe('term_title')
120 115 def init_term_title(self, change=None):
121 116 # Enable or disable the terminal title.
122 117 if self.term_title:
123 118 toggle_set_term_title(True)
124 119 set_term_title('IPython: ' + abbrev_cwd())
125 120 else:
126 121 toggle_set_term_title(False)
127 122
128 123 def get_prompt_tokens(self, cli):
129 124 return [
130 125 (Token.Prompt, 'In ['),
131 126 (Token.PromptNum, str(self.execution_count)),
132 127 (Token.Prompt, ']: '),
133 128 ]
134 129
135 130 def get_continuation_tokens(self, cli, width):
136 131 return [
137 132 (Token.Prompt, (' ' * (width - 5)) + '...: '),
138 133 ]
139 134
140 135 def init_prompt_toolkit_cli(self):
141 136 if self.simple_prompt:
142 137 # Fall back to plain non-interactive output for tests.
143 138 # This is very limited, and only accepts a single line.
144 139 def prompt():
145 140 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
146 141 self.prompt_for_code = prompt
147 142 return
148 143
149 144 kbmanager = KeyBindingManager.for_prompt()
150 145 insert_mode = ViInsertMode() | EmacsInsertMode()
151 146 # Ctrl+J == Enter, seemingly
152 147 @kbmanager.registry.add_binding(Keys.ControlJ,
153 148 filter=(HasFocus(DEFAULT_BUFFER)
154 149 & ~HasSelection()
155 150 & insert_mode
156 151 ))
157 152 def _(event):
158 153 b = event.current_buffer
159 154 d = b.document
160 155 if not (d.on_last_line or d.cursor_position_row >= d.line_count
161 156 - d.empty_line_count_at_the_end()):
162 157 b.newline()
163 158 return
164 159
165 160 status, indent = self.input_splitter.check_complete(d.text)
166 161
167 162 if (status != 'incomplete') and b.accept_action.is_returnable:
168 163 b.accept_action.validate_and_handle(event.cli, b)
169 164 else:
170 165 b.insert_text('\n' + (' ' * (indent or 0)))
171 166
172 167 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
173 168 def _reset_buffer(event):
174 169 event.current_buffer.reset()
175 170
176 171 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
177 172 def _reset_search_buffer(event):
178 173 if event.current_buffer.document.text:
179 174 event.current_buffer.reset()
180 175 else:
181 176 event.cli.push_focus(DEFAULT_BUFFER)
182 177
183 178 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
184 179
185 180 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
186 181 def _suspend_to_bg(event):
187 182 event.cli.suspend_to_background()
188 183
189 184 @Condition
190 185 def cursor_in_leading_ws(cli):
191 186 before = cli.application.buffer.document.current_line_before_cursor
192 187 return (not before) or before.isspace()
193 188
194 189 # Ctrl+I == Tab
195 190 @kbmanager.registry.add_binding(Keys.ControlI,
196 191 filter=(HasFocus(DEFAULT_BUFFER)
197 192 & ~HasSelection()
198 193 & insert_mode
199 194 & cursor_in_leading_ws
200 195 ))
201 196 def _indent_buffer(event):
202 197 event.current_buffer.insert_text(' ' * 4)
203 198
204 199 # Pre-populate history from IPython's history database
205 200 history = InMemoryHistory()
206 201 last_cell = u""
207 202 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
208 203 include_latest=True):
209 204 # Ignore blank lines and consecutive duplicates
210 205 cell = cell.rstrip()
211 206 if cell and (cell != last_cell):
212 207 history.append(cell)
213 208
214 209 self._style = self._make_style_from_name(self.highlighting_style)
215 210 style = DynamicStyle(lambda: self._style)
216 211
217 212 editing_mode = getattr(EditingMode, self.editing_mode.upper())
218 213
219 214 self._app = create_prompt_application(
220 215 editing_mode=editing_mode,
221 216 key_bindings_registry=kbmanager.registry,
222 217 history=history,
223 218 completer=IPythonPTCompleter(self.Completer),
224 219 enable_history_search=True,
225 220 style=style,
226 221 mouse_support=self.mouse_support,
227 222 **self._layout_options()
228 223 )
229 224 self._eventloop = create_eventloop(self.inputhook)
230 225 self.pt_cli = CommandLineInterface(self._app, eventloop=self._eventloop)
231 226
232 227 def _make_style_from_name(self, name):
233 228 """
234 229 Small wrapper that make an IPython compatible style from a style name
235 230
236 231 We need that to add style for prompt ... etc.
237 232 """
238 233 style_cls = get_style_by_name(name)
239 234 style_overrides = {
240 235 Token.Prompt: '#009900',
241 236 Token.PromptNum: '#00ff00 bold',
242 237 }
243 238 if name == 'default':
244 239 style_cls = get_style_by_name('default')
245 240 # The default theme needs to be visible on both a dark background
246 241 # and a light background, because we can't tell what the terminal
247 242 # looks like. These tweaks to the default theme help with that.
248 243 style_overrides.update({
249 244 Token.Number: '#007700',
250 245 Token.Operator: 'noinherit',
251 246 Token.String: '#BB6622',
252 247 Token.Name.Function: '#2080D0',
253 248 Token.Name.Class: 'bold #2080D0',
254 249 Token.Name.Namespace: 'bold #2080D0',
255 250 })
256 251 style_overrides.update(self.highlighting_style_overrides)
257 252 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
258 253 style_dict=style_overrides)
259 254
260 255 return style
261 256
262 257 def _layout_options(self):
263 258 """
264 259 Return the current layout option for the current Terminal InteractiveShell
265 260 """
266 261 return {
267 262 'lexer':IPythonPTLexer(),
268 263 'reserve_space_for_menu':self.space_for_menu,
269 264 'get_prompt_tokens':self.get_prompt_tokens,
270 265 'get_continuation_tokens':self.get_continuation_tokens,
271 266 'multiline':True,
272 267 'display_completions_in_columns': self.display_completions_in_columns,
273 268
274 269 # Highlight matching brackets, but only when this setting is
275 270 # enabled, and only when the DEFAULT_BUFFER has the focus.
276 271 'extra_input_processors': [ConditionalProcessor(
277 272 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
278 273 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
279 274 Condition(lambda cli: self.highlight_matching_brackets))],
280 275 }
281 276
282 277 def _update_layout(self):
283 278 """
284 279 Ask for a re computation of the application layout, if for example ,
285 280 some configuration options have changed.
286 281 """
287 282 self._app.layout = create_prompt_layout(**self._layout_options())
288 283
289 284 def prompt_for_code(self):
290 285 document = self.pt_cli.run(
291 286 pre_run=self.pre_prompt, reset_current_buffer=True)
292 287 return document.text
293 288
294 289 def init_io(self):
295 290 if sys.platform not in {'win32', 'cli'}:
296 291 return
297 292
298 293 import colorama
299 294 colorama.init()
300 295
301 296 # For some reason we make these wrappers around stdout/stderr.
302 297 # For now, we need to reset them so all output gets coloured.
303 298 # https://github.com/ipython/ipython/issues/8669
304 299 from IPython.utils import io
305 300 io.stdout = io.IOStream(sys.stdout)
306 301 io.stderr = io.IOStream(sys.stderr)
307 302
308 303 def init_magics(self):
309 304 super(TerminalInteractiveShell, self).init_magics()
310 305 self.register_magics(TerminalMagics)
311 306
312 307 def init_alias(self):
313 308 # The parent class defines aliases that can be safely used with any
314 309 # frontend.
315 310 super(TerminalInteractiveShell, self).init_alias()
316 311
317 312 # Now define aliases that only make sense on the terminal, because they
318 313 # need direct access to the console in a way that we can't emulate in
319 314 # GUI or web frontend
320 315 if os.name == 'posix':
321 316 for cmd in ['clear', 'more', 'less', 'man']:
322 317 self.alias_manager.soft_define_alias(cmd, cmd)
323 318
324 319
325 320 def __init__(self, *args, **kwargs):
326 321 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
327 322 self.init_prompt_toolkit_cli()
328 323 self.init_term_title()
329 324 self.keep_running = True
330 325
331 326 self.debugger_history = InMemoryHistory()
332 327
333 328 def ask_exit(self):
334 329 self.keep_running = False
335 330
336 331 rl_next_input = None
337 332
338 333 def pre_prompt(self):
339 334 if self.rl_next_input:
340 335 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
341 336 self.rl_next_input = None
342 337
343 338 def interact(self):
344 339 while self.keep_running:
345 340 print(self.separate_in, end='')
346 341
347 342 try:
348 343 code = self.prompt_for_code()
349 344 except EOFError:
350 345 if (not self.confirm_exit) \
351 346 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
352 347 self.ask_exit()
353 348
354 349 else:
355 350 if code:
356 351 self.run_cell(code, store_history=True)
357 352 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
358 353 self.edit_syntax_error()
359 354
360 355 def mainloop(self):
361 356 # An extra layer of protection in case someone mashing Ctrl-C breaks
362 357 # out of our internal code.
363 358 while True:
364 359 try:
365 360 self.interact()
366 361 break
367 362 except KeyboardInterrupt:
368 363 print("\nKeyboardInterrupt escaped interact()\n")
369 364
370 365 if hasattr(self, '_eventloop'):
371 366 self._eventloop.close()
372 367
373 368 _inputhook = None
374 369 def inputhook(self, context):
375 370 if self._inputhook is not None:
376 371 self._inputhook(context)
377 372
378 373 def enable_gui(self, gui=None):
379 374 if gui:
380 375 self._inputhook = get_inputhook_func(gui)
381 376 else:
382 377 self._inputhook = None
383 378
384 379 # Methods to support auto-editing of SyntaxErrors:
385 380
386 381 def edit_syntax_error(self):
387 382 """The bottom half of the syntax error handler called in the main loop.
388 383
389 384 Loop until syntax error is fixed or user cancels.
390 385 """
391 386
392 387 while self.SyntaxTB.last_syntax_error:
393 388 # copy and clear last_syntax_error
394 389 err = self.SyntaxTB.clear_err_state()
395 390 if not self._should_recompile(err):
396 391 return
397 392 try:
398 393 # may set last_syntax_error again if a SyntaxError is raised
399 394 self.safe_execfile(err.filename, self.user_ns)
400 395 except:
401 396 self.showtraceback()
402 397 else:
403 398 try:
404 399 with open(err.filename) as f:
405 400 # This should be inside a display_trap block and I
406 401 # think it is.
407 402 sys.displayhook(f.read())
408 403 except:
409 404 self.showtraceback()
410 405
411 406 def _should_recompile(self, e):
412 407 """Utility routine for edit_syntax_error"""
413 408
414 409 if e.filename in ('<ipython console>', '<input>', '<string>',
415 410 '<console>', '<BackgroundJob compilation>',
416 411 None):
417 412 return False
418 413 try:
419 414 if (self.autoedit_syntax and
420 415 not self.ask_yes_no(
421 416 'Return to editor to correct syntax error? '
422 417 '[Y/n] ', 'y')):
423 418 return False
424 419 except EOFError:
425 420 return False
426 421
427 422 def int0(x):
428 423 try:
429 424 return int(x)
430 425 except TypeError:
431 426 return 0
432 427
433 428 # always pass integer line and offset values to editor hook
434 429 try:
435 430 self.hooks.fix_error_editor(e.filename,
436 431 int0(e.lineno), int0(e.offset),
437 432 e.msg)
438 433 except TryNext:
439 434 warn('Could not open editor')
440 435 return False
441 436 return True
442 437
443 438 # Run !system commands directly, not through pipes, so terminal programs
444 439 # work correctly.
445 440 system = InteractiveShell.system_raw
446 441
447 442
448 443 if __name__ == '__main__':
449 444 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now