##// END OF EJS Templates
Add config option for vi/emacs style open-editor shortcuts...
Thomas Kluyver -
Show More
@@ -1,513 +1,520 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import os
4 4 import sys
5 5 import warnings
6 6 from warnings import warn
7 7
8 8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 9 from IPython.utils import io
10 10 from IPython.utils.py3compat import cast_unicode_py2, input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
14 14
15 15 from prompt_toolkit.document import Document
16 16 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 17 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 18 from prompt_toolkit.history import InMemoryHistory
19 19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 20 from prompt_toolkit.interface import CommandLineInterface
21 21 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 23 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24 24
25 25 from pygments.styles import get_style_by_name, get_all_styles
26 26 from pygments.style import Style
27 27 from pygments.token import Token
28 28
29 29 from .debugger import TerminalPdb, Pdb
30 30 from .magics import TerminalMagics
31 31 from .pt_inputhooks import get_inputhook_name_and_func
32 32 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
33 33 from .ptutils import IPythonPTCompleter, IPythonPTLexer
34 34 from .shortcuts import register_ipython_shortcuts
35 35
36 36 DISPLAY_BANNER_DEPRECATED = object()
37 37
38 38
39 39 from pygments.style import Style
40 40
41 41 class _NoStyle(Style): pass
42 42
43 43
44 44
45 45 _style_overrides_light_bg = {
46 46 Token.Prompt: '#0000ff',
47 47 Token.PromptNum: '#0000ee bold',
48 48 Token.OutPrompt: '#cc0000',
49 49 Token.OutPromptNum: '#bb0000 bold',
50 50 }
51 51
52 52 _style_overrides_linux = {
53 53 Token.Prompt: '#00cc00',
54 54 Token.PromptNum: '#00bb00 bold',
55 55 Token.OutPrompt: '#cc0000',
56 56 Token.OutPromptNum: '#bb0000 bold',
57 57 }
58 58
59 59
60 60
61 61 def get_default_editor():
62 62 try:
63 63 return os.environ['EDITOR']
64 64 except KeyError:
65 65 pass
66 66 except UnicodeError:
67 67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 68 "default editor.")
69 69
70 70 if os.name == 'posix':
71 71 return 'vi' # the only one guaranteed to be there!
72 72 else:
73 73 return 'notepad' # same in Windows!
74 74
75 75 # conservatively check for tty
76 76 # overridden streams can result in things like:
77 77 # - sys.stdin = None
78 78 # - no isatty method
79 79 for _name in ('stdin', 'stdout', 'stderr'):
80 80 _stream = getattr(sys, _name)
81 81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 82 _is_tty = False
83 83 break
84 84 else:
85 85 _is_tty = True
86 86
87 87
88 88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
89 89
90 90 class TerminalInteractiveShell(InteractiveShell):
91 91 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
92 92 'to reserve for the completion menu'
93 93 ).tag(config=True)
94 94
95 95 def _space_for_menu_changed(self, old, new):
96 96 self._update_layout()
97 97
98 98 pt_cli = None
99 99 debugger_history = None
100 100 _pt_app = None
101 101
102 102 simple_prompt = Bool(_use_simple_prompt,
103 103 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
104 104
105 105 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
106 106 IPython own testing machinery, and emacs inferior-shell integration through elpy.
107 107
108 108 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
109 109 environment variable is set, or the current terminal is not a tty.
110 110
111 111 """
112 112 ).tag(config=True)
113 113
114 114 @property
115 115 def debugger_cls(self):
116 116 return Pdb if self.simple_prompt else TerminalPdb
117 117
118 118 confirm_exit = Bool(True,
119 119 help="""
120 120 Set to confirm when you try to exit IPython with an EOF (Control-D
121 121 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
122 122 you can force a direct exit without any confirmation.""",
123 123 ).tag(config=True)
124 124
125 125 editing_mode = Unicode('emacs',
126 126 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
127 127 ).tag(config=True)
128 128
129 129 mouse_support = Bool(False,
130 130 help="Enable mouse support in the prompt"
131 131 ).tag(config=True)
132 132
133 133 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
134 134 help="""The name or class of a Pygments style to use for syntax
135 135 highlighting: \n %s""" % ', '.join(get_all_styles())
136 136 ).tag(config=True)
137 137
138 138
139 139 @observe('highlighting_style')
140 140 @observe('colors')
141 141 def _highlighting_style_changed(self, change):
142 142 self.refresh_style()
143 143
144 144 def refresh_style(self):
145 145 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
146 146
147 147
148 148 highlighting_style_overrides = Dict(
149 149 help="Override highlighting format for specific tokens"
150 150 ).tag(config=True)
151 151
152 152 true_color = Bool(False,
153 153 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
154 154 "If your terminal supports true color, the following command "
155 155 "should print 'TRUECOLOR' in orange: "
156 156 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
157 157 ).tag(config=True)
158 158
159 159 editor = Unicode(get_default_editor(),
160 160 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
161 161 ).tag(config=True)
162 162
163 163 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
164 164
165 165 prompts = Instance(Prompts)
166 166
167 167 @default('prompts')
168 168 def _prompts_default(self):
169 169 return self.prompts_class(self)
170 170
171 171 @observe('prompts')
172 172 def _(self, change):
173 173 self._update_layout()
174 174
175 175 @default('displayhook_class')
176 176 def _displayhook_class_default(self):
177 177 return RichPromptDisplayHook
178 178
179 179 term_title = Bool(True,
180 180 help="Automatically set the terminal title"
181 181 ).tag(config=True)
182 182
183 183 display_completions = Enum(('column', 'multicolumn','readlinelike'),
184 184 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
185 185 "'readlinelike'. These options are for `prompt_toolkit`, see "
186 186 "`prompt_toolkit` documentation for more information."
187 187 ),
188 188 default_value='multicolumn').tag(config=True)
189 189
190 190 highlight_matching_brackets = Bool(True,
191 help="Highlight matching brackets .",
191 help="Highlight matching brackets.",
192 ).tag(config=True)
193
194 extra_open_editor_shortcuts = Bool(False,
195 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
196 "This is in addition to the F2 binding, which is always enabled."
192 197 ).tag(config=True)
193 198
194 199 @observe('term_title')
195 200 def init_term_title(self, change=None):
196 201 # Enable or disable the terminal title.
197 202 if self.term_title:
198 203 toggle_set_term_title(True)
199 204 set_term_title('IPython: ' + abbrev_cwd())
200 205 else:
201 206 toggle_set_term_title(False)
202 207
203 208 def init_display_formatter(self):
204 209 super(TerminalInteractiveShell, self).init_display_formatter()
205 210 # terminal only supports plain text
206 211 self.display_formatter.active_types = ['text/plain']
207 212 # disable `_ipython_display_`
208 213 self.display_formatter.ipython_display_formatter.enabled = False
209 214
210 215 def init_prompt_toolkit_cli(self):
211 216 if self.simple_prompt:
212 217 # Fall back to plain non-interactive output for tests.
213 218 # This is very limited, and only accepts a single line.
214 219 def prompt():
215 220 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
216 221 self.prompt_for_code = prompt
217 222 return
218 223
219 224 # Set up keyboard shortcuts
220 kbmanager = KeyBindingManager.for_prompt()
225 kbmanager = KeyBindingManager.for_prompt(
226 enable_open_in_editor=self.extra_open_editor_shortcuts,
227 )
221 228 register_ipython_shortcuts(kbmanager.registry, self)
222 229
223 230 # Pre-populate history from IPython's history database
224 231 history = InMemoryHistory()
225 232 last_cell = u""
226 233 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
227 234 include_latest=True):
228 235 # Ignore blank lines and consecutive duplicates
229 236 cell = cell.rstrip()
230 237 if cell and (cell != last_cell):
231 238 history.append(cell)
232 239 last_cell = cell
233 240
234 241 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
235 242 style = DynamicStyle(lambda: self._style)
236 243
237 244 editing_mode = getattr(EditingMode, self.editing_mode.upper())
238 245
239 246 def patch_stdout(**kwargs):
240 247 return self.pt_cli.patch_stdout_context(**kwargs)
241 248
242 249 self._pt_app = create_prompt_application(
243 250 editing_mode=editing_mode,
244 251 key_bindings_registry=kbmanager.registry,
245 252 history=history,
246 253 completer=IPythonPTCompleter(shell=self,
247 254 patch_stdout=patch_stdout),
248 255 enable_history_search=True,
249 256 style=style,
250 257 mouse_support=self.mouse_support,
251 258 **self._layout_options()
252 259 )
253 260 self._eventloop = create_eventloop(self.inputhook)
254 261 self.pt_cli = CommandLineInterface(
255 262 self._pt_app, eventloop=self._eventloop,
256 263 output=create_output(true_color=self.true_color))
257 264
258 265 def _make_style_from_name_or_cls(self, name_or_cls):
259 266 """
260 267 Small wrapper that make an IPython compatible style from a style name
261 268
262 269 We need that to add style for prompt ... etc.
263 270 """
264 271 style_overrides = {}
265 272 if name_or_cls == 'legacy':
266 273 legacy = self.colors.lower()
267 274 if legacy == 'linux':
268 275 style_cls = get_style_by_name('monokai')
269 276 style_overrides = _style_overrides_linux
270 277 elif legacy == 'lightbg':
271 278 style_overrides = _style_overrides_light_bg
272 279 style_cls = get_style_by_name('pastie')
273 280 elif legacy == 'neutral':
274 281 # The default theme needs to be visible on both a dark background
275 282 # and a light background, because we can't tell what the terminal
276 283 # looks like. These tweaks to the default theme help with that.
277 284 style_cls = get_style_by_name('default')
278 285 style_overrides.update({
279 286 Token.Number: '#007700',
280 287 Token.Operator: 'noinherit',
281 288 Token.String: '#BB6622',
282 289 Token.Name.Function: '#2080D0',
283 290 Token.Name.Class: 'bold #2080D0',
284 291 Token.Name.Namespace: 'bold #2080D0',
285 292 Token.Prompt: '#009900',
286 293 Token.PromptNum: '#00ff00 bold',
287 294 Token.OutPrompt: '#990000',
288 295 Token.OutPromptNum: '#ff0000 bold',
289 296 })
290 297
291 298 # Hack: Due to limited color support on the Windows console
292 299 # the prompt colors will be wrong without this
293 300 if os.name == 'nt':
294 301 style_overrides.update({
295 302 Token.Prompt: '#ansidarkgreen',
296 303 Token.PromptNum: '#ansigreen bold',
297 304 Token.OutPrompt: '#ansidarkred',
298 305 Token.OutPromptNum: '#ansired bold',
299 306 })
300 307 elif legacy =='nocolor':
301 308 style_cls=_NoStyle
302 309 style_overrides = {}
303 310 else :
304 311 raise ValueError('Got unknown colors: ', legacy)
305 312 else :
306 313 if isinstance(name_or_cls, str):
307 314 style_cls = get_style_by_name(name_or_cls)
308 315 else:
309 316 style_cls = name_or_cls
310 317 style_overrides = {
311 318 Token.Prompt: '#009900',
312 319 Token.PromptNum: '#00ff00 bold',
313 320 Token.OutPrompt: '#990000',
314 321 Token.OutPromptNum: '#ff0000 bold',
315 322 }
316 323 style_overrides.update(self.highlighting_style_overrides)
317 324 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
318 325 style_dict=style_overrides)
319 326
320 327 return style
321 328
322 329 def _layout_options(self):
323 330 """
324 331 Return the current layout option for the current Terminal InteractiveShell
325 332 """
326 333 return {
327 334 'lexer':IPythonPTLexer(),
328 335 'reserve_space_for_menu':self.space_for_menu,
329 336 'get_prompt_tokens':self.prompts.in_prompt_tokens,
330 337 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
331 338 'multiline':True,
332 339 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
333 340
334 341 # Highlight matching brackets, but only when this setting is
335 342 # enabled, and only when the DEFAULT_BUFFER has the focus.
336 343 'extra_input_processors': [ConditionalProcessor(
337 344 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
338 345 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
339 346 Condition(lambda cli: self.highlight_matching_brackets))],
340 347 }
341 348
342 349 def _update_layout(self):
343 350 """
344 351 Ask for a re computation of the application layout, if for example ,
345 352 some configuration options have changed.
346 353 """
347 354 if self._pt_app:
348 355 self._pt_app.layout = create_prompt_layout(**self._layout_options())
349 356
350 357 def prompt_for_code(self):
351 358 document = self.pt_cli.run(
352 359 pre_run=self.pre_prompt, reset_current_buffer=True)
353 360 return document.text
354 361
355 362 def enable_win_unicode_console(self):
356 363 if sys.version_info >= (3, 6):
357 364 # Since PEP 528, Python uses the unicode APIs for the Windows
358 365 # console by default, so WUC shouldn't be needed.
359 366 return
360 367
361 368 import win_unicode_console
362 369 win_unicode_console.enable()
363 370
364 371 def init_io(self):
365 372 if sys.platform not in {'win32', 'cli'}:
366 373 return
367 374
368 375 self.enable_win_unicode_console()
369 376
370 377 import colorama
371 378 colorama.init()
372 379
373 380 # For some reason we make these wrappers around stdout/stderr.
374 381 # For now, we need to reset them so all output gets coloured.
375 382 # https://github.com/ipython/ipython/issues/8669
376 383 # io.std* are deprecated, but don't show our own deprecation warnings
377 384 # during initialization of the deprecated API.
378 385 with warnings.catch_warnings():
379 386 warnings.simplefilter('ignore', DeprecationWarning)
380 387 io.stdout = io.IOStream(sys.stdout)
381 388 io.stderr = io.IOStream(sys.stderr)
382 389
383 390 def init_magics(self):
384 391 super(TerminalInteractiveShell, self).init_magics()
385 392 self.register_magics(TerminalMagics)
386 393
387 394 def init_alias(self):
388 395 # The parent class defines aliases that can be safely used with any
389 396 # frontend.
390 397 super(TerminalInteractiveShell, self).init_alias()
391 398
392 399 # Now define aliases that only make sense on the terminal, because they
393 400 # need direct access to the console in a way that we can't emulate in
394 401 # GUI or web frontend
395 402 if os.name == 'posix':
396 403 for cmd in ['clear', 'more', 'less', 'man']:
397 404 self.alias_manager.soft_define_alias(cmd, cmd)
398 405
399 406
400 407 def __init__(self, *args, **kwargs):
401 408 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
402 409 self.init_prompt_toolkit_cli()
403 410 self.init_term_title()
404 411 self.keep_running = True
405 412
406 413 self.debugger_history = InMemoryHistory()
407 414
408 415 def ask_exit(self):
409 416 self.keep_running = False
410 417
411 418 rl_next_input = None
412 419
413 420 def pre_prompt(self):
414 421 if self.rl_next_input:
415 422 # We can't set the buffer here, because it will be reset just after
416 423 # this. Adding a callable to pre_run_callables does what we need
417 424 # after the buffer is reset.
418 425 s = cast_unicode_py2(self.rl_next_input)
419 426 def set_doc():
420 427 self.pt_cli.application.buffer.document = Document(s)
421 428 if hasattr(self.pt_cli, 'pre_run_callables'):
422 429 self.pt_cli.pre_run_callables.append(set_doc)
423 430 else:
424 431 # Older version of prompt_toolkit; it's OK to set the document
425 432 # directly here.
426 433 set_doc()
427 434 self.rl_next_input = None
428 435
429 436 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
430 437
431 438 if display_banner is not DISPLAY_BANNER_DEPRECATED:
432 439 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
433 440
434 441 self.keep_running = True
435 442 while self.keep_running:
436 443 print(self.separate_in, end='')
437 444
438 445 try:
439 446 code = self.prompt_for_code()
440 447 except EOFError:
441 448 if (not self.confirm_exit) \
442 449 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
443 450 self.ask_exit()
444 451
445 452 else:
446 453 if code:
447 454 self.run_cell(code, store_history=True)
448 455
449 456 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
450 457 # An extra layer of protection in case someone mashing Ctrl-C breaks
451 458 # out of our internal code.
452 459 if display_banner is not DISPLAY_BANNER_DEPRECATED:
453 460 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
454 461 while True:
455 462 try:
456 463 self.interact()
457 464 break
458 465 except KeyboardInterrupt as e:
459 466 print("\n%s escaped interact()\n" % type(e).__name__)
460 467 finally:
461 468 # An interrupt during the eventloop will mess up the
462 469 # internal state of the prompt_toolkit library.
463 470 # Stopping the eventloop fixes this, see
464 471 # https://github.com/ipython/ipython/pull/9867
465 472 if hasattr(self, '_eventloop'):
466 473 self._eventloop.stop()
467 474
468 475 _inputhook = None
469 476 def inputhook(self, context):
470 477 if self._inputhook is not None:
471 478 self._inputhook(context)
472 479
473 480 active_eventloop = None
474 481 def enable_gui(self, gui=None):
475 482 if gui:
476 483 self.active_eventloop, self._inputhook =\
477 484 get_inputhook_name_and_func(gui)
478 485 else:
479 486 self.active_eventloop = self._inputhook = None
480 487
481 488 # Run !system commands directly, not through pipes, so terminal programs
482 489 # work correctly.
483 490 system = InteractiveShell.system_raw
484 491
485 492 def auto_rewrite_input(self, cmd):
486 493 """Overridden from the parent class to use fancy rewriting prompt"""
487 494 if not self.show_rewritten_input:
488 495 return
489 496
490 497 tokens = self.prompts.rewrite_prompt_tokens()
491 498 if self.pt_cli:
492 499 self.pt_cli.print_tokens(tokens)
493 500 print(cmd)
494 501 else:
495 502 prompt = ''.join(s for t, s in tokens)
496 503 print(prompt, cmd, sep='')
497 504
498 505 _prompts_before = None
499 506 def switch_doctest_mode(self, mode):
500 507 """Switch prompts to classic for %doctest_mode"""
501 508 if mode:
502 509 self._prompts_before = self.prompts
503 510 self.prompts = ClassicPrompts(self)
504 511 elif self._prompts_before:
505 512 self.prompts = self._prompts_before
506 513 self._prompts_before = None
507 514 self._update_layout()
508 515
509 516
510 517 InteractiveShellABC.register(TerminalInteractiveShell)
511 518
512 519 if __name__ == '__main__':
513 520 TerminalInteractiveShell.instance().interact()
@@ -1,249 +1,245 b''
1 1 """
2 2 Module to define and register Terminal IPython shortcuts with
3 3 :any:`prompt_toolkit`
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import warnings
10 10 import signal
11 11 import sys
12 12 from typing import Callable
13 13
14 14
15 15 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
16 16 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
17 17 ViInsertMode, EmacsInsertMode, HasCompletions)
18 18 from prompt_toolkit.filters.cli import ViMode, ViNavigationMode
19 19 from prompt_toolkit.keys import Keys
20 20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
21 21
22 22 from IPython.utils.decorators import undoc
23 23
24 24 @Condition
25 25 def cursor_in_leading_ws(cli):
26 26 before = cli.application.buffer.document.current_line_before_cursor
27 27 return (not before) or before.isspace()
28 28
29 29 def register_ipython_shortcuts(registry, shell):
30 30 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
31 31 insert_mode = ViInsertMode() | EmacsInsertMode()
32 32
33 33 # Ctrl+J == Enter, seemingly
34 34 registry.add_binding(Keys.ControlJ,
35 35 filter=(HasFocus(DEFAULT_BUFFER)
36 36 & ~HasSelection()
37 37 & insert_mode
38 38 ))(newline_or_execute_outer(shell))
39 39
40 40 registry.add_binding(Keys.ControlBackslash)(force_exit)
41 41
42 42 registry.add_binding(Keys.ControlP,
43 43 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
44 44 ))(previous_history_or_previous_completion)
45 45
46 46 registry.add_binding(Keys.ControlN,
47 47 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
48 48 ))(next_history_or_next_completion)
49 49
50 50 registry.add_binding(Keys.ControlG,
51 51 filter=(HasFocus(DEFAULT_BUFFER) & HasCompletions()
52 52 ))(dismiss_completion)
53 53
54 54 registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)
55 55 )(reset_buffer)
56 56
57 57 registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER)
58 58 )(reset_search_buffer)
59 59
60 60 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
61 61 registry.add_binding(Keys.ControlZ, filter=supports_suspend
62 62 )(suspend_to_bg)
63 63
64 64 # Ctrl+I == Tab
65 65 registry.add_binding(Keys.ControlI,
66 66 filter=(HasFocus(DEFAULT_BUFFER)
67 67 & ~HasSelection()
68 68 & insert_mode
69 69 & cursor_in_leading_ws
70 70 ))(indent_buffer)
71 71
72 72 registry.add_binding(Keys.ControlO,
73 73 filter=(HasFocus(DEFAULT_BUFFER)
74 74 & EmacsInsertMode()))(newline_autoindent_outer(shell.input_splitter))
75 75
76 76 registry.add_binding(Keys.F2,
77 77 filter=HasFocus(DEFAULT_BUFFER)
78 78 )(open_input_in_editor)
79 79
80 registry.add_binding('v',
81 filter=HasFocus(DEFAULT_BUFFER) & ViNavigationMode()
82 )(open_input_in_editor)
83
84 80 if shell.display_completions == 'readlinelike':
85 81 registry.add_binding(Keys.ControlI,
86 82 filter=(HasFocus(DEFAULT_BUFFER)
87 83 & ~HasSelection()
88 84 & insert_mode
89 85 & ~cursor_in_leading_ws
90 86 ))(display_completions_like_readline)
91 87
92 88 if sys.platform == 'win32':
93 89 registry.add_binding(Keys.ControlV,
94 90 filter=(
95 91 HasFocus(
96 92 DEFAULT_BUFFER) & ~ViMode()
97 93 ))(win_paste)
98 94
99 95
100 96 def newline_or_execute_outer(shell):
101 97 def newline_or_execute(event):
102 98 """When the user presses return, insert a newline or execute the code."""
103 99 b = event.current_buffer
104 100 d = b.document
105 101
106 102 if b.complete_state:
107 103 cc = b.complete_state.current_completion
108 104 if cc:
109 105 b.apply_completion(cc)
110 106 else:
111 107 b.cancel_completion()
112 108 return
113 109
114 110 before_text = d.text[:d.cursor_position]
115 111 status, indent = shell.input_splitter.check_complete(before_text + '\n')
116 112
117 113 if not (d.on_last_line or
118 114 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
119 115 ):
120 116 b.insert_text('\n' + (' ' * (indent or 0)))
121 117 return
122 118
123 119 if (status != 'incomplete') and b.accept_action.is_returnable:
124 120 b.accept_action.validate_and_handle(event.cli, b)
125 121 else:
126 122 b.insert_text('\n' + (' ' * (indent or 0)))
127 123 return newline_or_execute
128 124
129 125
130 126 def previous_history_or_previous_completion(event):
131 127 """
132 128 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
133 129
134 130 If completer is open this still select previous completion.
135 131 """
136 132 event.current_buffer.auto_up()
137 133
138 134
139 135 def next_history_or_next_completion(event):
140 136 """
141 137 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
142 138
143 139 If completer is open this still select next completion.
144 140 """
145 141 event.current_buffer.auto_down()
146 142
147 143
148 144 def dismiss_completion(event):
149 145 b = event.current_buffer
150 146 if b.complete_state:
151 147 b.cancel_completion()
152 148
153 149
154 150 def reset_buffer(event):
155 151 b = event.current_buffer
156 152 if b.complete_state:
157 153 b.cancel_completion()
158 154 else:
159 155 b.reset()
160 156
161 157
162 158 def reset_search_buffer(event):
163 159 if event.current_buffer.document.text:
164 160 event.current_buffer.reset()
165 161 else:
166 162 event.cli.push_focus(DEFAULT_BUFFER)
167 163
168 164 def suspend_to_bg(event):
169 165 event.cli.suspend_to_background()
170 166
171 167 def force_exit(event):
172 168 """
173 169 Force exit (with a non-zero return value)
174 170 """
175 171 sys.exit("Quit")
176 172
177 173 def indent_buffer(event):
178 174 event.current_buffer.insert_text(' ' * 4)
179 175
180 176 @undoc
181 177 def newline_with_copy_margin(event):
182 178 """
183 179 DEPRECATED since IPython 6.0
184 180
185 181 See :any:`newline_autoindent_outer` for a replacement.
186 182
187 183 Preserve margin and cursor position when using
188 184 Control-O to insert a newline in EMACS mode
189 185 """
190 186 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
191 187 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
192 188 DeprecationWarning, stacklevel=2)
193 189
194 190 b = event.current_buffer
195 191 cursor_start_pos = b.document.cursor_position_col
196 192 b.newline(copy_margin=True)
197 193 b.cursor_up(count=1)
198 194 cursor_end_pos = b.document.cursor_position_col
199 195 if cursor_start_pos != cursor_end_pos:
200 196 pos_diff = cursor_start_pos - cursor_end_pos
201 197 b.cursor_right(count=pos_diff)
202 198
203 199 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
204 200 """
205 201 Return a function suitable for inserting a indented newline after the cursor.
206 202
207 203 Fancier version of deprecated ``newline_with_copy_margin`` which should
208 204 compute the correct indentation of the inserted line. That is to say, indent
209 205 by 4 extra space after a function definition, class definition, context
210 206 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
211 207 """
212 208
213 209 def newline_autoindent(event):
214 210 """insert a newline after the cursor indented appropriately."""
215 211 b = event.current_buffer
216 212 d = b.document
217 213
218 214 if b.complete_state:
219 215 b.cancel_completion()
220 216 text = d.text[:d.cursor_position] + '\n'
221 217 _, indent = inputsplitter.check_complete(text)
222 218 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
223 219
224 220 return newline_autoindent
225 221
226 222
227 223 def open_input_in_editor(event):
228 224 event.cli.current_buffer.tempfile_suffix = ".py"
229 225 event.cli.current_buffer.open_in_editor(event.cli)
230 226
231 227
232 228 if sys.platform == 'win32':
233 229 from IPython.core.error import TryNext
234 230 from IPython.lib.clipboard import (ClipboardEmpty,
235 231 win32_clipboard_get,
236 232 tkinter_clipboard_get)
237 233
238 234 @undoc
239 235 def win_paste(event):
240 236 try:
241 237 text = win32_clipboard_get()
242 238 except TryNext:
243 239 try:
244 240 text = tkinter_clipboard_get()
245 241 except (TryNext, ClipboardEmpty):
246 242 return
247 243 except ClipboardEmpty:
248 244 return
249 245 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
General Comments 0
You need to be logged in to leave comments. Login now