##// END OF EJS Templates
Merge pull request #10489 from takluyver/i10425...
meeseeksdev[bot] -
r23582:bbb0bc29 merge
parent child Browse files
Show More
@@ -1,251 +1,256 b''
1 """
1 """
2 Module to define and register Terminal IPython shortcuts with
2 Module to define and register Terminal IPython shortcuts with
3 :mod:`prompt_toolkit`
3 :mod:`prompt_toolkit`
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import warnings
9 import warnings
10 import signal
10 import signal
11 import sys
11 import sys
12 from typing import Callable
12 from typing import Callable
13
13
14
14
15 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
15 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
16 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
16 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
17 ViInsertMode, EmacsInsertMode, HasCompletions)
17 ViInsertMode, EmacsInsertMode, HasCompletions)
18 from prompt_toolkit.filters.cli import ViMode, ViNavigationMode
18 from prompt_toolkit.filters.cli import ViMode, ViNavigationMode
19 from prompt_toolkit.keys import Keys
19 from prompt_toolkit.keys import Keys
20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
21
21
22 from IPython.utils.decorators import undoc
22 from IPython.utils.decorators import undoc
23
23
24 @undoc
24 @undoc
25 @Condition
25 @Condition
26 def cursor_in_leading_ws(cli):
26 def cursor_in_leading_ws(cli):
27 before = cli.application.buffer.document.current_line_before_cursor
27 before = cli.application.buffer.document.current_line_before_cursor
28 return (not before) or before.isspace()
28 return (not before) or before.isspace()
29
29
30 def register_ipython_shortcuts(registry, shell):
30 def register_ipython_shortcuts(registry, shell):
31 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
31 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
32 insert_mode = ViInsertMode() | EmacsInsertMode()
32 insert_mode = ViInsertMode() | EmacsInsertMode()
33
33
34 if getattr(shell, 'handle_return', None):
34 if getattr(shell, 'handle_return', None):
35 return_handler = shell.handle_return(shell)
35 return_handler = shell.handle_return(shell)
36 else:
36 else:
37 return_handler = newline_or_execute_outer(shell)
37 return_handler = newline_or_execute_outer(shell)
38
38
39 # Ctrl+J == Enter, seemingly
39 # Ctrl+J == Enter, seemingly
40 registry.add_binding(Keys.ControlJ,
40 registry.add_binding(Keys.ControlJ,
41 filter=(HasFocus(DEFAULT_BUFFER)
41 filter=(HasFocus(DEFAULT_BUFFER)
42 & ~HasSelection()
42 & ~HasSelection()
43 & insert_mode
43 & insert_mode
44 ))(return_handler)
44 ))(return_handler)
45
45
46 registry.add_binding(Keys.ControlBackslash)(force_exit)
46 registry.add_binding(Keys.ControlBackslash)(force_exit)
47
47
48 registry.add_binding(Keys.ControlP,
48 registry.add_binding(Keys.ControlP,
49 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
49 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
50 ))(previous_history_or_previous_completion)
50 ))(previous_history_or_previous_completion)
51
51
52 registry.add_binding(Keys.ControlN,
52 registry.add_binding(Keys.ControlN,
53 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
53 filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER)
54 ))(next_history_or_next_completion)
54 ))(next_history_or_next_completion)
55
55
56 registry.add_binding(Keys.ControlG,
56 registry.add_binding(Keys.ControlG,
57 filter=(HasFocus(DEFAULT_BUFFER) & HasCompletions()
57 filter=(HasFocus(DEFAULT_BUFFER) & HasCompletions()
58 ))(dismiss_completion)
58 ))(dismiss_completion)
59
59
60 registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)
60 registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)
61 )(reset_buffer)
61 )(reset_buffer)
62
62
63 registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER)
63 registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER)
64 )(reset_search_buffer)
64 )(reset_search_buffer)
65
65
66 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
66 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
67 registry.add_binding(Keys.ControlZ, filter=supports_suspend
67 registry.add_binding(Keys.ControlZ, filter=supports_suspend
68 )(suspend_to_bg)
68 )(suspend_to_bg)
69
69
70 # Ctrl+I == Tab
70 # Ctrl+I == Tab
71 registry.add_binding(Keys.ControlI,
71 registry.add_binding(Keys.ControlI,
72 filter=(HasFocus(DEFAULT_BUFFER)
72 filter=(HasFocus(DEFAULT_BUFFER)
73 & ~HasSelection()
73 & ~HasSelection()
74 & insert_mode
74 & insert_mode
75 & cursor_in_leading_ws
75 & cursor_in_leading_ws
76 ))(indent_buffer)
76 ))(indent_buffer)
77
77
78 registry.add_binding(Keys.ControlO,
78 registry.add_binding(Keys.ControlO,
79 filter=(HasFocus(DEFAULT_BUFFER)
79 filter=(HasFocus(DEFAULT_BUFFER)
80 & EmacsInsertMode()))(newline_autoindent_outer(shell.input_splitter))
80 & EmacsInsertMode()))(newline_autoindent_outer(shell.input_splitter))
81
81
82 registry.add_binding(Keys.F2,
82 registry.add_binding(Keys.F2,
83 filter=HasFocus(DEFAULT_BUFFER)
83 filter=HasFocus(DEFAULT_BUFFER)
84 )(open_input_in_editor)
84 )(open_input_in_editor)
85
85
86 if shell.display_completions == 'readlinelike':
86 if shell.display_completions == 'readlinelike':
87 registry.add_binding(Keys.ControlI,
87 registry.add_binding(Keys.ControlI,
88 filter=(HasFocus(DEFAULT_BUFFER)
88 filter=(HasFocus(DEFAULT_BUFFER)
89 & ~HasSelection()
89 & ~HasSelection()
90 & insert_mode
90 & insert_mode
91 & ~cursor_in_leading_ws
91 & ~cursor_in_leading_ws
92 ))(display_completions_like_readline)
92 ))(display_completions_like_readline)
93
93
94 if sys.platform == 'win32':
94 if sys.platform == 'win32':
95 registry.add_binding(Keys.ControlV,
95 registry.add_binding(Keys.ControlV,
96 filter=(
96 filter=(
97 HasFocus(
97 HasFocus(
98 DEFAULT_BUFFER) & ~ViMode()
98 DEFAULT_BUFFER) & ~ViMode()
99 ))(win_paste)
99 ))(win_paste)
100
100
101
101
102 def newline_or_execute_outer(shell):
102 def newline_or_execute_outer(shell):
103 def newline_or_execute(event):
103 def newline_or_execute(event):
104 """When the user presses return, insert a newline or execute the code."""
104 """When the user presses return, insert a newline or execute the code."""
105 b = event.current_buffer
105 b = event.current_buffer
106 d = b.document
106 d = b.document
107
107
108 if b.complete_state:
108 if b.complete_state:
109 cc = b.complete_state.current_completion
109 cc = b.complete_state.current_completion
110 if cc:
110 if cc:
111 b.apply_completion(cc)
111 b.apply_completion(cc)
112 else:
112 else:
113 b.cancel_completion()
113 b.cancel_completion()
114 return
114 return
115
115
116 before_text = d.text[:d.cursor_position]
116 # If there's only one line, treat it as if the cursor is at the end.
117 status, indent = shell.input_splitter.check_complete(before_text + '\n')
117 # See https://github.com/ipython/ipython/issues/10425
118 if d.line_count == 1:
119 check_text = d.text
120 else:
121 check_text = d.text[:d.cursor_position]
122 status, indent = shell.input_splitter.check_complete(check_text + '\n')
118
123
119 if not (d.on_last_line or
124 if not (d.on_last_line or
120 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
125 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
121 ):
126 ):
122 b.insert_text('\n' + (' ' * (indent or 0)))
127 b.insert_text('\n' + (' ' * (indent or 0)))
123 return
128 return
124
129
125 if (status != 'incomplete') and b.accept_action.is_returnable:
130 if (status != 'incomplete') and b.accept_action.is_returnable:
126 b.accept_action.validate_and_handle(event.cli, b)
131 b.accept_action.validate_and_handle(event.cli, b)
127 else:
132 else:
128 b.insert_text('\n' + (' ' * (indent or 0)))
133 b.insert_text('\n' + (' ' * (indent or 0)))
129 return newline_or_execute
134 return newline_or_execute
130
135
131
136
132 def previous_history_or_previous_completion(event):
137 def previous_history_or_previous_completion(event):
133 """
138 """
134 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
139 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
135
140
136 If completer is open this still select previous completion.
141 If completer is open this still select previous completion.
137 """
142 """
138 event.current_buffer.auto_up()
143 event.current_buffer.auto_up()
139
144
140
145
141 def next_history_or_next_completion(event):
146 def next_history_or_next_completion(event):
142 """
147 """
143 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
148 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
144
149
145 If completer is open this still select next completion.
150 If completer is open this still select next completion.
146 """
151 """
147 event.current_buffer.auto_down()
152 event.current_buffer.auto_down()
148
153
149
154
150 def dismiss_completion(event):
155 def dismiss_completion(event):
151 b = event.current_buffer
156 b = event.current_buffer
152 if b.complete_state:
157 if b.complete_state:
153 b.cancel_completion()
158 b.cancel_completion()
154
159
155
160
156 def reset_buffer(event):
161 def reset_buffer(event):
157 b = event.current_buffer
162 b = event.current_buffer
158 if b.complete_state:
163 if b.complete_state:
159 b.cancel_completion()
164 b.cancel_completion()
160 else:
165 else:
161 b.reset()
166 b.reset()
162
167
163
168
164 def reset_search_buffer(event):
169 def reset_search_buffer(event):
165 if event.current_buffer.document.text:
170 if event.current_buffer.document.text:
166 event.current_buffer.reset()
171 event.current_buffer.reset()
167 else:
172 else:
168 event.cli.push_focus(DEFAULT_BUFFER)
173 event.cli.push_focus(DEFAULT_BUFFER)
169
174
170 def suspend_to_bg(event):
175 def suspend_to_bg(event):
171 event.cli.suspend_to_background()
176 event.cli.suspend_to_background()
172
177
173 def force_exit(event):
178 def force_exit(event):
174 """
179 """
175 Force exit (with a non-zero return value)
180 Force exit (with a non-zero return value)
176 """
181 """
177 sys.exit("Quit")
182 sys.exit("Quit")
178
183
179 def indent_buffer(event):
184 def indent_buffer(event):
180 event.current_buffer.insert_text(' ' * 4)
185 event.current_buffer.insert_text(' ' * 4)
181
186
182 @undoc
187 @undoc
183 def newline_with_copy_margin(event):
188 def newline_with_copy_margin(event):
184 """
189 """
185 DEPRECATED since IPython 6.0
190 DEPRECATED since IPython 6.0
186
191
187 See :any:`newline_autoindent_outer` for a replacement.
192 See :any:`newline_autoindent_outer` for a replacement.
188
193
189 Preserve margin and cursor position when using
194 Preserve margin and cursor position when using
190 Control-O to insert a newline in EMACS mode
195 Control-O to insert a newline in EMACS mode
191 """
196 """
192 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
197 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
193 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
198 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
194 DeprecationWarning, stacklevel=2)
199 DeprecationWarning, stacklevel=2)
195
200
196 b = event.current_buffer
201 b = event.current_buffer
197 cursor_start_pos = b.document.cursor_position_col
202 cursor_start_pos = b.document.cursor_position_col
198 b.newline(copy_margin=True)
203 b.newline(copy_margin=True)
199 b.cursor_up(count=1)
204 b.cursor_up(count=1)
200 cursor_end_pos = b.document.cursor_position_col
205 cursor_end_pos = b.document.cursor_position_col
201 if cursor_start_pos != cursor_end_pos:
206 if cursor_start_pos != cursor_end_pos:
202 pos_diff = cursor_start_pos - cursor_end_pos
207 pos_diff = cursor_start_pos - cursor_end_pos
203 b.cursor_right(count=pos_diff)
208 b.cursor_right(count=pos_diff)
204
209
205 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
210 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
206 """
211 """
207 Return a function suitable for inserting a indented newline after the cursor.
212 Return a function suitable for inserting a indented newline after the cursor.
208
213
209 Fancier version of deprecated ``newline_with_copy_margin`` which should
214 Fancier version of deprecated ``newline_with_copy_margin`` which should
210 compute the correct indentation of the inserted line. That is to say, indent
215 compute the correct indentation of the inserted line. That is to say, indent
211 by 4 extra space after a function definition, class definition, context
216 by 4 extra space after a function definition, class definition, context
212 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
217 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
213 """
218 """
214
219
215 def newline_autoindent(event):
220 def newline_autoindent(event):
216 """insert a newline after the cursor indented appropriately."""
221 """insert a newline after the cursor indented appropriately."""
217 b = event.current_buffer
222 b = event.current_buffer
218 d = b.document
223 d = b.document
219
224
220 if b.complete_state:
225 if b.complete_state:
221 b.cancel_completion()
226 b.cancel_completion()
222 text = d.text[:d.cursor_position] + '\n'
227 text = d.text[:d.cursor_position] + '\n'
223 _, indent = inputsplitter.check_complete(text)
228 _, indent = inputsplitter.check_complete(text)
224 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
229 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
225
230
226 return newline_autoindent
231 return newline_autoindent
227
232
228
233
229 def open_input_in_editor(event):
234 def open_input_in_editor(event):
230 event.cli.current_buffer.tempfile_suffix = ".py"
235 event.cli.current_buffer.tempfile_suffix = ".py"
231 event.cli.current_buffer.open_in_editor(event.cli)
236 event.cli.current_buffer.open_in_editor(event.cli)
232
237
233
238
234 if sys.platform == 'win32':
239 if sys.platform == 'win32':
235 from IPython.core.error import TryNext
240 from IPython.core.error import TryNext
236 from IPython.lib.clipboard import (ClipboardEmpty,
241 from IPython.lib.clipboard import (ClipboardEmpty,
237 win32_clipboard_get,
242 win32_clipboard_get,
238 tkinter_clipboard_get)
243 tkinter_clipboard_get)
239
244
240 @undoc
245 @undoc
241 def win_paste(event):
246 def win_paste(event):
242 try:
247 try:
243 text = win32_clipboard_get()
248 text = win32_clipboard_get()
244 except TryNext:
249 except TryNext:
245 try:
250 try:
246 text = tkinter_clipboard_get()
251 text = tkinter_clipboard_get()
247 except (TryNext, ClipboardEmpty):
252 except (TryNext, ClipboardEmpty):
248 return
253 return
249 except ClipboardEmpty:
254 except ClipboardEmpty:
250 return
255 return
251 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
256 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
General Comments 0
You need to be logged in to leave comments. Login now