shortcuts.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import signal
  2. import sys
  3. from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
  4. from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
  5. ViInsertMode, EmacsInsertMode, HasCompletions)
  6. from prompt_toolkit.filters.cli import ViMode
  7. from prompt_toolkit.keys import Keys
  8. from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
  9. from IPython.utils.decorators import undoc
  10. @Condition
  11. def cursor_in_leading_ws(cli):
  12. before = cli.application.buffer.document.current_line_before_cursor
  13. return (not before) or before.isspace()
  14. def register_ipython_shortcuts(registry, shell):
  15. """Set up the prompt_toolkit keyboard shortcuts for IPython"""
  16. insert_mode = ViInsertMode() | EmacsInsertMode()
  17. # Ctrl+J == Enter, seemingly
  18. registry.add_binding(Keys.ControlJ,
  19. filter=(HasFocus(DEFAULT_BUFFER)
  20. & ~HasSelection()
  21. & insert_mode
  22. ))(newline_or_execute_outer(shell))
  23. registry.add_binding(Keys.ControlBackslash)(force_exit)
  24. registry.add_binding(Keys.ControlP,
  25. filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
  26. ))(previous_history_or_previous_completion)
  27. registry.add_binding(Keys.ControlN,
  28. filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
  29. ))(next_history_or_next_completion)
  30. registry.add_binding(Keys.ControlG,
  31. filter=(HasFocus(DEFAULT_BUFFER) & HasCompletions()
  32. ))(dismiss_completion)
  33. registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)
  34. )(reset_buffer)
  35. registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER)
  36. )(reset_search_buffer)
  37. supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
  38. registry.add_binding(Keys.ControlZ, filter=supports_suspend
  39. )(suspend_to_bg)
  40. # Ctrl+I == Tab
  41. registry.add_binding(Keys.ControlI,
  42. filter=(HasFocus(DEFAULT_BUFFER)
  43. & ~HasSelection()
  44. & insert_mode
  45. & cursor_in_leading_ws
  46. ))(indent_buffer)
  47. registry.add_binding(Keys.ControlO,
  48. filter=(HasFocus(DEFAULT_BUFFER)
  49. & EmacsInsertMode()))(newline_with_copy_margin)
  50. if shell.display_completions == 'readlinelike':
  51. registry.add_binding(Keys.ControlI,
  52. filter=(HasFocus(DEFAULT_BUFFER)
  53. & ~HasSelection()
  54. & insert_mode
  55. & ~cursor_in_leading_ws
  56. ))(display_completions_like_readline)
  57. if sys.platform == 'win32':
  58. registry.add_binding(Keys.ControlV,
  59. filter=(
  60. HasFocus(
  61. DEFAULT_BUFFER) & ~ViMode()
  62. ))(win_paste)
  63. def newline_or_execute_outer(shell):
  64. def newline_or_execute(event):
  65. """When the user presses return, insert a newline or execute the code."""
  66. b = event.current_buffer
  67. d = b.document
  68. if b.complete_state:
  69. cc = b.complete_state.current_completion
  70. if cc:
  71. b.apply_completion(cc)
  72. else:
  73. b.cancel_completion()
  74. return
  75. if not (d.on_last_line or d.cursor_position_row >= d.line_count
  76. - d.empty_line_count_at_the_end()):
  77. b.newline()
  78. return
  79. status, indent = shell.input_splitter.check_complete(d.text + '\n')
  80. if (status != 'incomplete') and b.accept_action.is_returnable:
  81. b.accept_action.validate_and_handle(event.cli, b)
  82. else:
  83. b.insert_text('\n' + (' ' * (indent or 0)))
  84. return newline_or_execute
  85. def previous_history_or_previous_completion(event):
  86. """
  87. Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
  88. If completer is open this still select previous completion.
  89. """
  90. event.current_buffer.auto_up()
  91. def next_history_or_next_completion(event):
  92. """
  93. Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
  94. If completer is open this still select next completion.
  95. """
  96. event.current_buffer.auto_down()
  97. def dismiss_completion(event):
  98. b = event.current_buffer
  99. if b.complete_state:
  100. b.cancel_completion()
  101. def reset_buffer(event):
  102. b = event.current_buffer
  103. if b.complete_state:
  104. b.cancel_completion()
  105. else:
  106. b.reset()
  107. def reset_search_buffer(event):
  108. if event.current_buffer.document.text:
  109. event.current_buffer.reset()
  110. else:
  111. event.cli.push_focus(DEFAULT_BUFFER)
  112. def suspend_to_bg(event):
  113. event.cli.suspend_to_background()
  114. def force_exit(event):
  115. """
  116. Force exit (with a non-zero return value)
  117. """
  118. sys.exit("Quit")
  119. def indent_buffer(event):
  120. event.current_buffer.insert_text(' ' * 4)
  121. def newline_with_copy_margin(event):
  122. """
  123. Preserve margin and cursor position when using
  124. Control-O to insert a newline in EMACS mode
  125. """
  126. b = event.current_buffer
  127. cursor_start_pos = b.document.cursor_position_col
  128. b.newline(copy_margin=True)
  129. b.cursor_up(count=1)
  130. cursor_end_pos = b.document.cursor_position_col
  131. if cursor_start_pos != cursor_end_pos:
  132. pos_diff = cursor_start_pos - cursor_end_pos
  133. b.cursor_right(count=pos_diff)
  134. if sys.platform == 'win32':
  135. from IPython.core.error import TryNext
  136. from IPython.lib.clipboard import (ClipboardEmpty,
  137. win32_clipboard_get,
  138. tkinter_clipboard_get)
  139. @undoc
  140. def win_paste(event):
  141. try:
  142. text = win32_clipboard_get()
  143. except TryNext:
  144. try:
  145. text = tkinter_clipboard_get()
  146. except (TryNext, ClipboardEmpty):
  147. return
  148. except ClipboardEmpty:
  149. return
  150. event.current_buffer.insert_text(text.replace('\t', ' ' * 4))