##// END OF EJS Templates
Extend the completion layout to use a third readlinelike option....
Matthias Bussonnier -
Show More
@@ -1,569 +1,589 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, InteractiveShellABC
11 11 from IPython.utils.py3compat import PY3, 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 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
15 15
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
17 17 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
18 18 ViInsertMode, EmacsInsertMode, IsDone, HasCompletions)
19 19 from prompt_toolkit.filters.cli import ViMode
20 20 from prompt_toolkit.history import InMemoryHistory
21 21 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
22 22 from prompt_toolkit.interface import CommandLineInterface
23 23 from prompt_toolkit.key_binding.manager import KeyBindingManager
24 24 from prompt_toolkit.keys import Keys
25 25 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
26 26 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
27 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
27 28
28 29 from pygments.styles import get_style_by_name, get_all_styles
29 30 from pygments.token import Token
30 31
31 32 from .debugger import TerminalPdb, Pdb
32 33 from .magics import TerminalMagics
33 34 from .pt_inputhooks import get_inputhook_func
34 35 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
35 36 from .ptutils import IPythonPTCompleter, IPythonPTLexer
36 37
37 38
38 39 def get_default_editor():
39 40 try:
40 41 ed = os.environ['EDITOR']
41 42 if not PY3:
42 43 ed = ed.decode()
43 44 return ed
44 45 except KeyError:
45 46 pass
46 47 except UnicodeError:
47 48 warn("$EDITOR environment variable is not pure ASCII. Using platform "
48 49 "default editor.")
49 50
50 51 if os.name == 'posix':
51 52 return 'vi' # the only one guaranteed to be there!
52 53 else:
53 54 return 'notepad' # same in Windows!
54 55
55 56
56 57 if sys.stdin and sys.stdout and sys.stderr:
57 58 _is_tty = (sys.stdin.isatty()) and (sys.stdout.isatty()) and (sys.stderr.isatty())
58 59 else:
59 60 _is_tty = False
60 61
61 62
62 63 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
63 64
64 65 class TerminalInteractiveShell(InteractiveShell):
65 66 colors_force = True
66 67
67 68 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
68 69 'to reserve for the completion menu'
69 70 ).tag(config=True)
70 71
71 72 def _space_for_menu_changed(self, old, new):
72 73 self._update_layout()
73 74
74 75 pt_cli = None
75 76 debugger_history = None
76 77
77 78 simple_prompt = Bool(_use_simple_prompt,
78 79 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
79 80
80 81 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
81 82 IPython own testing machinery, and emacs inferior-shell integration through elpy.
82 83
83 84 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
84 85 environment variable is set, or the current terminal is not a tty.
85 86
86 87 """
87 88 ).tag(config=True)
88 89
89 90 @property
90 91 def debugger_cls(self):
91 92 return Pdb if self.simple_prompt else TerminalPdb
92 93
93 94 autoedit_syntax = Bool(False,
94 95 help="auto editing of files with syntax errors.",
95 96 ).tag(config=True)
96 97
97 98
98 99 confirm_exit = Bool(True,
99 100 help="""
100 101 Set to confirm when you try to exit IPython with an EOF (Control-D
101 102 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
102 103 you can force a direct exit without any confirmation.""",
103 104 ).tag(config=True)
104 105
105 106 editing_mode = Unicode('emacs',
106 107 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
107 108 ).tag(config=True)
108 109
109 110 mouse_support = Bool(False,
110 111 help="Enable mouse support in the prompt"
111 112 ).tag(config=True)
112 113
113 114 highlighting_style = Unicode('default',
114 115 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
115 116 ).tag(config=True)
116 117
117 118
118 119 @observe('highlighting_style')
119 120 def _highlighting_style_changed(self, change):
120 121 self._style = self._make_style_from_name(self.highlighting_style)
121 122
122 123 highlighting_style_overrides = Dict(
123 124 help="Override highlighting format for specific tokens"
124 125 ).tag(config=True)
125 126
126 127 editor = Unicode(get_default_editor(),
127 128 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
128 129 ).tag(config=True)
129 130
130 131 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
131 132
132 133 prompts = Instance(Prompts)
133 134
134 135 @default('prompts')
135 136 def _prompts_default(self):
136 137 return self.prompts_class(self)
137 138
138 139 @observe('prompts')
139 140 def _(self, change):
140 141 self._update_layout()
141 142
142 143 @default('displayhook_class')
143 144 def _displayhook_class_default(self):
144 145 return RichPromptDisplayHook
145 146
146 147 term_title = Bool(True,
147 148 help="Automatically set the terminal title"
148 149 ).tag(config=True)
149 150
150 display_completions_in_columns = Bool(False,
151 help="Display a multi column completion menu.",
151 display_completions_in_columns = Bool(None,
152 help="Display a multi column completion menu.", allow_none=True
152 153 ).tag(config=True)
153 154
155 @observe('display_completions_in_columns')
156 def _display_completions_in_columns_changed(self, new):
157 raise DeprecationWarning("The `display_completions_in_columns` Boolean has been replaced by the enum `display_completions`"
158 "with the following acceptable value: 'column', 'multicolumn','readlinelike'. ")
159
160 display_completions = Enum(('column', 'multicolumn','readlinelike'), default_value='multicolumn').tag(config=True)
161
154 162 highlight_matching_brackets = Bool(True,
155 163 help="Highlight matching brackets .",
156 164 ).tag(config=True)
157 165
158 166 @observe('term_title')
159 167 def init_term_title(self, change=None):
160 168 # Enable or disable the terminal title.
161 169 if self.term_title:
162 170 toggle_set_term_title(True)
163 171 set_term_title('IPython: ' + abbrev_cwd())
164 172 else:
165 173 toggle_set_term_title(False)
166 174
167 175 def init_display_formatter(self):
168 176 super(TerminalInteractiveShell, self).init_display_formatter()
169 177 # terminal only supports plain text
170 178 self.display_formatter.active_types = ['text/plain']
171 179
172 180 def init_prompt_toolkit_cli(self):
173 181 self._app = None
174 182 if self.simple_prompt:
175 183 # Fall back to plain non-interactive output for tests.
176 184 # This is very limited, and only accepts a single line.
177 185 def prompt():
178 186 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
179 187 self.prompt_for_code = prompt
180 188 return
181 189
182 190 kbmanager = KeyBindingManager.for_prompt()
183 191 insert_mode = ViInsertMode() | EmacsInsertMode()
184 192 # Ctrl+J == Enter, seemingly
185 193 @kbmanager.registry.add_binding(Keys.ControlJ,
186 194 filter=(HasFocus(DEFAULT_BUFFER)
187 195 & ~HasSelection()
188 196 & insert_mode
189 197 ))
190 198 def _(event):
191 199 b = event.current_buffer
192 200 d = b.document
193 201
194 202 if b.complete_state:
195 203 cc = b.complete_state.current_completion
196 204 if cc:
197 205 b.apply_completion(cc)
198 206 else:
199 207 b.cancel_completion()
200 208 return
201 209
202 210 if not (d.on_last_line or d.cursor_position_row >= d.line_count
203 211 - d.empty_line_count_at_the_end()):
204 212 b.newline()
205 213 return
206 214
207 215 status, indent = self.input_splitter.check_complete(d.text + '\n')
208 216
209 217 if (status != 'incomplete') and b.accept_action.is_returnable:
210 218 b.accept_action.validate_and_handle(event.cli, b)
211 219 else:
212 220 b.insert_text('\n' + (' ' * (indent or 0)))
213 221
214 222 @kbmanager.registry.add_binding(Keys.ControlP, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)))
215 223 def _previous_history_or_previous_completion(event):
216 224 """
217 225 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
218 226
219 227 If completer is open this still select previous completion.
220 228 """
221 229 event.current_buffer.auto_up()
222 230
223 231 @kbmanager.registry.add_binding(Keys.ControlN, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)))
224 232 def _next_history_or_next_completion(event):
225 233 """
226 234 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
227 235
228 236 If completer is open this still select next completion.
229 237 """
230 238 event.current_buffer.auto_down()
231 239
232 240 @kbmanager.registry.add_binding(Keys.ControlG, filter=(
233 241 HasFocus(DEFAULT_BUFFER) & HasCompletions()
234 242 ))
235 243 def _dismiss_completion(event):
236 244 b = event.current_buffer
237 245 if b.complete_state:
238 246 b.cancel_completion()
239 247
240 248 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
241 249 def _reset_buffer(event):
242 250 b = event.current_buffer
243 251 if b.complete_state:
244 252 b.cancel_completion()
245 253 else:
246 254 b.reset()
247 255
248 256 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
249 257 def _reset_search_buffer(event):
250 258 if event.current_buffer.document.text:
251 259 event.current_buffer.reset()
252 260 else:
253 261 event.cli.push_focus(DEFAULT_BUFFER)
254 262
255 263 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
256 264
257 265 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
258 266 def _suspend_to_bg(event):
259 267 event.cli.suspend_to_background()
260 268
261 269 @Condition
262 270 def cursor_in_leading_ws(cli):
263 271 before = cli.application.buffer.document.current_line_before_cursor
264 272 return (not before) or before.isspace()
265 273
266 274 # Ctrl+I == Tab
267 275 @kbmanager.registry.add_binding(Keys.ControlI,
268 276 filter=(HasFocus(DEFAULT_BUFFER)
269 277 & ~HasSelection()
270 278 & insert_mode
271 279 & cursor_in_leading_ws
272 280 ))
273 281 def _indent_buffer(event):
274 282 event.current_buffer.insert_text(' ' * 4)
275 283
284
285 if self.display_completions == 'readlinelike':
286 @kbmanager.registry.add_binding(Keys.ControlI,
287 filter=(HasFocus(DEFAULT_BUFFER)
288 & ~HasSelection()
289 & insert_mode
290 & ~cursor_in_leading_ws
291 ))
292 def _disaply_compl(ev):
293 display_completions_like_readline(ev)
294
295
276 296 if sys.platform == 'win32':
277 297 from IPython.lib.clipboard import (ClipboardEmpty,
278 298 win32_clipboard_get, tkinter_clipboard_get)
279 299 @kbmanager.registry.add_binding(Keys.ControlV,
280 300 filter=(HasFocus(DEFAULT_BUFFER) & ~ViMode()))
281 301 def _paste(event):
282 302 try:
283 303 text = win32_clipboard_get()
284 304 except TryNext:
285 305 try:
286 306 text = tkinter_clipboard_get()
287 307 except (TryNext, ClipboardEmpty):
288 308 return
289 309 except ClipboardEmpty:
290 310 return
291 311 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
292 312
293 313 # Pre-populate history from IPython's history database
294 314 history = InMemoryHistory()
295 315 last_cell = u""
296 316 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
297 317 include_latest=True):
298 318 # Ignore blank lines and consecutive duplicates
299 319 cell = cell.rstrip()
300 320 if cell and (cell != last_cell):
301 321 history.append(cell)
302 322
303 323 self._style = self._make_style_from_name(self.highlighting_style)
304 324 style = DynamicStyle(lambda: self._style)
305 325
306 326 editing_mode = getattr(EditingMode, self.editing_mode.upper())
307 327
308 328 self._app = create_prompt_application(
309 329 editing_mode=editing_mode,
310 330 key_bindings_registry=kbmanager.registry,
311 331 history=history,
312 332 completer=IPythonPTCompleter(self.Completer),
313 333 enable_history_search=True,
314 334 style=style,
315 335 mouse_support=self.mouse_support,
316 336 **self._layout_options()
317 337 )
318 338 self._eventloop = create_eventloop(self.inputhook)
319 339 self.pt_cli = CommandLineInterface(self._app, eventloop=self._eventloop)
320 340
321 341 def _make_style_from_name(self, name):
322 342 """
323 343 Small wrapper that make an IPython compatible style from a style name
324 344
325 345 We need that to add style for prompt ... etc.
326 346 """
327 347 style_cls = get_style_by_name(name)
328 348 style_overrides = {
329 349 Token.Prompt: '#009900',
330 350 Token.PromptNum: '#00ff00 bold',
331 351 Token.OutPrompt: '#990000',
332 352 Token.OutPromptNum: '#ff0000 bold',
333 353 }
334 354 if name == 'default':
335 355 style_cls = get_style_by_name('default')
336 356 # The default theme needs to be visible on both a dark background
337 357 # and a light background, because we can't tell what the terminal
338 358 # looks like. These tweaks to the default theme help with that.
339 359 style_overrides.update({
340 360 Token.Number: '#007700',
341 361 Token.Operator: 'noinherit',
342 362 Token.String: '#BB6622',
343 363 Token.Name.Function: '#2080D0',
344 364 Token.Name.Class: 'bold #2080D0',
345 365 Token.Name.Namespace: 'bold #2080D0',
346 366 })
347 367 style_overrides.update(self.highlighting_style_overrides)
348 368 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
349 369 style_dict=style_overrides)
350 370
351 371 return style
352 372
353 373 def _layout_options(self):
354 374 """
355 375 Return the current layout option for the current Terminal InteractiveShell
356 376 """
357 377 return {
358 378 'lexer':IPythonPTLexer(),
359 379 'reserve_space_for_menu':self.space_for_menu,
360 380 'get_prompt_tokens':self.prompts.in_prompt_tokens,
361 381 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
362 382 'multiline':True,
363 'display_completions_in_columns': self.display_completions_in_columns,
383 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
364 384
365 385 # Highlight matching brackets, but only when this setting is
366 386 # enabled, and only when the DEFAULT_BUFFER has the focus.
367 387 'extra_input_processors': [ConditionalProcessor(
368 388 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
369 389 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
370 390 Condition(lambda cli: self.highlight_matching_brackets))],
371 391 }
372 392
373 393 def _update_layout(self):
374 394 """
375 395 Ask for a re computation of the application layout, if for example ,
376 396 some configuration options have changed.
377 397 """
378 398 if getattr(self, '._app', None):
379 399 self._app.layout = create_prompt_layout(**self._layout_options())
380 400
381 401 def prompt_for_code(self):
382 402 document = self.pt_cli.run(
383 403 pre_run=self.pre_prompt, reset_current_buffer=True)
384 404 return document.text
385 405
386 406 def init_io(self):
387 407 if sys.platform not in {'win32', 'cli'}:
388 408 return
389 409
390 410 import win_unicode_console
391 411 import colorama
392 412
393 413 win_unicode_console.enable()
394 414 colorama.init()
395 415
396 416 # For some reason we make these wrappers around stdout/stderr.
397 417 # For now, we need to reset them so all output gets coloured.
398 418 # https://github.com/ipython/ipython/issues/8669
399 419 from IPython.utils import io
400 420 io.stdout = io.IOStream(sys.stdout)
401 421 io.stderr = io.IOStream(sys.stderr)
402 422
403 423 def init_magics(self):
404 424 super(TerminalInteractiveShell, self).init_magics()
405 425 self.register_magics(TerminalMagics)
406 426
407 427 def init_alias(self):
408 428 # The parent class defines aliases that can be safely used with any
409 429 # frontend.
410 430 super(TerminalInteractiveShell, self).init_alias()
411 431
412 432 # Now define aliases that only make sense on the terminal, because they
413 433 # need direct access to the console in a way that we can't emulate in
414 434 # GUI or web frontend
415 435 if os.name == 'posix':
416 436 for cmd in ['clear', 'more', 'less', 'man']:
417 437 self.alias_manager.soft_define_alias(cmd, cmd)
418 438
419 439
420 440 def __init__(self, *args, **kwargs):
421 441 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
422 442 self.init_prompt_toolkit_cli()
423 443 self.init_term_title()
424 444 self.keep_running = True
425 445
426 446 self.debugger_history = InMemoryHistory()
427 447
428 448 def ask_exit(self):
429 449 self.keep_running = False
430 450
431 451 rl_next_input = None
432 452
433 453 def pre_prompt(self):
434 454 if self.rl_next_input:
435 455 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
436 456 self.rl_next_input = None
437 457
438 458 def interact(self):
439 459 while self.keep_running:
440 460 print(self.separate_in, end='')
441 461
442 462 try:
443 463 code = self.prompt_for_code()
444 464 except EOFError:
445 465 if (not self.confirm_exit) \
446 466 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
447 467 self.ask_exit()
448 468
449 469 else:
450 470 if code:
451 471 self.run_cell(code, store_history=True)
452 472 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
453 473 self.edit_syntax_error()
454 474
455 475 def mainloop(self):
456 476 # An extra layer of protection in case someone mashing Ctrl-C breaks
457 477 # out of our internal code.
458 478 while True:
459 479 try:
460 480 self.interact()
461 481 break
462 482 except KeyboardInterrupt:
463 483 print("\nKeyboardInterrupt escaped interact()\n")
464 484
465 485 if hasattr(self, '_eventloop'):
466 486 self._eventloop.close()
467 487
468 488 _inputhook = None
469 489 def inputhook(self, context):
470 490 if self._inputhook is not None:
471 491 self._inputhook(context)
472 492
473 493 def enable_gui(self, gui=None):
474 494 if gui:
475 495 self._inputhook = get_inputhook_func(gui)
476 496 else:
477 497 self._inputhook = None
478 498
479 499 # Methods to support auto-editing of SyntaxErrors:
480 500
481 501 def edit_syntax_error(self):
482 502 """The bottom half of the syntax error handler called in the main loop.
483 503
484 504 Loop until syntax error is fixed or user cancels.
485 505 """
486 506
487 507 while self.SyntaxTB.last_syntax_error:
488 508 # copy and clear last_syntax_error
489 509 err = self.SyntaxTB.clear_err_state()
490 510 if not self._should_recompile(err):
491 511 return
492 512 try:
493 513 # may set last_syntax_error again if a SyntaxError is raised
494 514 self.safe_execfile(err.filename, self.user_ns)
495 515 except:
496 516 self.showtraceback()
497 517 else:
498 518 try:
499 519 with open(err.filename) as f:
500 520 # This should be inside a display_trap block and I
501 521 # think it is.
502 522 sys.displayhook(f.read())
503 523 except:
504 524 self.showtraceback()
505 525
506 526 def _should_recompile(self, e):
507 527 """Utility routine for edit_syntax_error"""
508 528
509 529 if e.filename in ('<ipython console>', '<input>', '<string>',
510 530 '<console>', '<BackgroundJob compilation>',
511 531 None):
512 532 return False
513 533 try:
514 534 if (self.autoedit_syntax and
515 535 not self.ask_yes_no(
516 536 'Return to editor to correct syntax error? '
517 537 '[Y/n] ', 'y')):
518 538 return False
519 539 except EOFError:
520 540 return False
521 541
522 542 def int0(x):
523 543 try:
524 544 return int(x)
525 545 except TypeError:
526 546 return 0
527 547
528 548 # always pass integer line and offset values to editor hook
529 549 try:
530 550 self.hooks.fix_error_editor(e.filename,
531 551 int0(e.lineno), int0(e.offset),
532 552 e.msg)
533 553 except TryNext:
534 554 warn('Could not open editor')
535 555 return False
536 556 return True
537 557
538 558 # Run !system commands directly, not through pipes, so terminal programs
539 559 # work correctly.
540 560 system = InteractiveShell.system_raw
541 561
542 562 def auto_rewrite_input(self, cmd):
543 563 """Overridden from the parent class to use fancy rewriting prompt"""
544 564 if not self.show_rewritten_input:
545 565 return
546 566
547 567 tokens = self.prompts.rewrite_prompt_tokens()
548 568 if self.pt_cli:
549 569 self.pt_cli.print_tokens(tokens)
550 570 print(cmd)
551 571 else:
552 572 prompt = ''.join(s for t, s in tokens)
553 573 print(prompt, cmd, sep='')
554 574
555 575 _prompts_before = None
556 576 def switch_doctest_mode(self, mode):
557 577 """Switch prompts to classic for %doctest_mode"""
558 578 if mode:
559 579 self._prompts_before = self.prompts
560 580 self.prompts = ClassicPrompts(self)
561 581 elif self._prompts_before:
562 582 self.prompts = self._prompts_before
563 583 self._prompts_before = None
564 584
565 585
566 586 InteractiveShellABC.register(TerminalInteractiveShell)
567 587
568 588 if __name__ == '__main__':
569 589 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now