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