shortcuts.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. registry.add_binding(Keys.F2,
  51. filter=HasFocus(DEFAULT_BUFFER)
  52. )(open_input_in_editor)
  53. if shell.display_completions == 'readlinelike':
  54. registry.add_binding(Keys.ControlI,
  55. filter=(HasFocus(DEFAULT_BUFFER)
  56. & ~HasSelection()
  57. & insert_mode
  58. & ~cursor_in_leading_ws
  59. ))(display_completions_like_readline)
  60. if sys.platform == 'win32':
  61. registry.add_binding(Keys.ControlV,
  62. filter=(
  63. HasFocus(
  64. DEFAULT_BUFFER) & ~ViMode()
  65. ))(win_paste)
  66. def newline_or_execute_outer(shell):
  67. def newline_or_execute(event):
  68. """When the user presses return, insert a newline or execute the code."""
  69. b = event.current_buffer
  70. d = b.document
  71. if b.complete_state:
  72. cc = b.complete_state.current_completion
  73. if cc:
  74. b.apply_completion(cc)
  75. else:
  76. b.cancel_completion()
  77. return
  78. # If there's only one line, treat it as if the cursor is at the end.
  79. # See https://github.com/ipython/ipython/issues/10425
  80. if d.line_count == 1:
  81. check_text = d.text
  82. else:
  83. check_text = d.text[:d.cursor_position]
  84. status, indent = shell.input_splitter.check_complete(check_text + '\n')
  85. if not (d.on_last_line or
  86. d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
  87. ):
  88. b.insert_text('\n' + (' ' * (indent or 0)))
  89. return
  90. if (status != 'incomplete') and b.accept_action.is_returnable:
  91. b.accept_action.validate_and_handle(event.cli, b)
  92. else:
  93. b.insert_text('\n' + (' ' * (indent or 0)))
  94. return newline_or_execute
  95. def previous_history_or_previous_completion(event):
  96. """
  97. Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
  98. If completer is open this still select previous completion.
  99. """
  100. event.current_buffer.auto_up()
  101. def next_history_or_next_completion(event):
  102. """
  103. Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
  104. If completer is open this still select next completion.
  105. """
  106. event.current_buffer.auto_down()
  107. def dismiss_completion(event):
  108. b = event.current_buffer
  109. if b.complete_state:
  110. b.cancel_completion()
  111. def reset_buffer(event):
  112. b = event.current_buffer
  113. if b.complete_state:
  114. b.cancel_completion()
  115. else:
  116. b.reset()
  117. def reset_search_buffer(event):
  118. if event.current_buffer.document.text:
  119. event.current_buffer.reset()
  120. else:
  121. event.cli.push_focus(DEFAULT_BUFFER)
  122. def suspend_to_bg(event):
  123. event.cli.suspend_to_background()
  124. def force_exit(event):
  125. """
  126. Force exit (with a non-zero return value)
  127. """
  128. sys.exit("Quit")
  129. def indent_buffer(event):
  130. event.current_buffer.insert_text(' ' * 4)
  131. def newline_with_copy_margin(event):
  132. """
  133. Preserve margin and cursor position when using
  134. Control-O to insert a newline in EMACS mode
  135. """
  136. b = event.current_buffer
  137. cursor_start_pos = b.document.cursor_position_col
  138. b.newline(copy_margin=True)
  139. b.cursor_up(count=1)
  140. cursor_end_pos = b.document.cursor_position_col
  141. if cursor_start_pos != cursor_end_pos:
  142. pos_diff = cursor_start_pos - cursor_end_pos
  143. b.cursor_right(count=pos_diff)
  144. def open_input_in_editor(event):
  145. event.cli.current_buffer.tempfile_suffix = ".py"
  146. event.cli.current_buffer.open_in_editor(event.cli)
  147. if sys.platform == 'win32':
  148. from IPython.core.error import TryNext
  149. from IPython.lib.clipboard import (ClipboardEmpty,
  150. win32_clipboard_get,
  151. tkinter_clipboard_get)
  152. @undoc
  153. def win_paste(event):
  154. try:
  155. text = win32_clipboard_get()
  156. except TryNext:
  157. try:
  158. text = tkinter_clipboard_get()
  159. except (TryNext, ClipboardEmpty):
  160. return
  161. except ClipboardEmpty:
  162. return
  163. event.current_buffer.insert_text(text.replace('\t', ' ' * 4))