##// END OF EJS Templates
forgotten import
Matthias Bussonnier -
Show More
@@ -1,371 +1,372 b''
1 1 """
2 2 Module to define and register Terminal IPython shortcuts with
3 3 :mod:`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 import re
12 13 from typing import Callable
13 14
14 15
15 16 from prompt_toolkit.application.current import get_app
16 17 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
17 18 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
18 19 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
19 20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
20 21 from prompt_toolkit.key_binding import KeyBindings
21 22 from prompt_toolkit.key_binding.bindings import named_commands as nc
22 23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
23 24
24 25 from IPython.utils.decorators import undoc
25 26
26 27 @undoc
27 28 @Condition
28 29 def cursor_in_leading_ws():
29 30 before = get_app().current_buffer.document.current_line_before_cursor
30 31 return (not before) or before.isspace()
31 32
32 33
33 34 def create_ipython_shortcuts(shell):
34 35 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
35 36
36 37 kb = KeyBindings()
37 38 insert_mode = vi_insert_mode | emacs_insert_mode
38 39
39 40 if getattr(shell, 'handle_return', None):
40 41 return_handler = shell.handle_return(shell)
41 42 else:
42 43 return_handler = newline_or_execute_outer(shell)
43 44
44 45 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
45 46 & ~has_selection
46 47 & insert_mode
47 48 ))(return_handler)
48 49
49 50 def reformat_and_execute(event):
50 51 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
51 52 event.current_buffer.validate_and_handle()
52 53
53 54 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
54 55 & ~has_selection
55 56 & insert_mode
56 57 ))(reformat_and_execute)
57 58
58 59 kb.add('c-\\')(force_exit)
59 60
60 61 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
61 62 )(previous_history_or_previous_completion)
62 63
63 64 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
64 65 )(next_history_or_next_completion)
65 66
66 67 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
67 68 )(dismiss_completion)
68 69
69 70 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
70 71
71 72 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
72 73
73 74 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
74 75 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
75 76
76 77 # Ctrl+I == Tab
77 78 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
78 79 & ~has_selection
79 80 & insert_mode
80 81 & cursor_in_leading_ws
81 82 ))(indent_buffer)
82 83 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
83 84 )(newline_autoindent_outer(shell.input_transformer_manager))
84 85
85 86 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
86 87
87 88 if shell.display_completions == 'readlinelike':
88 89 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
89 90 & ~has_selection
90 91 & insert_mode
91 92 & ~cursor_in_leading_ws
92 93 ))(display_completions_like_readline)
93 94
94 95 if sys.platform == "win32":
95 96 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
96 97
97 98 @Condition
98 99 def ebivim():
99 100 return shell.emacs_bindings_in_vi_insert_mode
100 101
101 102 focused_insert = has_focus(DEFAULT_BUFFER) & vi_insert_mode
102 103
103 104 # Needed for to accept autosuggestions in vi insert mode
104 105 @kb.add("c-e", filter=focused_insert & ebivim)
105 106 def _(event):
106 107 b = event.current_buffer
107 108 suggestion = b.suggestion
108 109 if suggestion:
109 110 b.insert_text(suggestion.text)
110 111 else:
111 112 nc.end_of_line(event)
112 113
113 114 @kb.add("c-f", filter=focused_insert & ebivim)
114 115 def _(event):
115 116 b = event.current_buffer
116 117 suggestion = b.suggestion
117 118 if suggestion:
118 119 b.insert_text(suggestion.text)
119 120 else:
120 121 nc.forward_char(event)
121 122
122 123 @kb.add("escape", "f", filter=focused_insert & ebivim)
123 124 def _(event):
124 125 b = event.current_buffer
125 126 suggestion = b.suggestion
126 127 if suggestion:
127 128 t = re.split(r"(\S+\s+)", suggestion.text)
128 129 b.insert_text(next((x for x in t if x), ""))
129 130 else:
130 131 nc.forward_word(event)
131 132
132 133 # Simple Control keybindings
133 134 key_cmd_dict = {
134 135 "c-a": nc.beginning_of_line,
135 136 "c-b": nc.backward_char,
136 137 "c-k": nc.kill_line,
137 138 "c-w": nc.backward_kill_word,
138 139 "c-y": nc.yank,
139 140 "c-_": nc.undo,
140 141 }
141 142
142 143 for key, cmd in key_cmd_dict.items():
143 144 kb.add(key, filter=focused_insert & ebivim)(cmd)
144 145
145 146 # Alt and Combo Control keybindings
146 147 keys_cmd_dict = {
147 148 # Control Combos
148 149 ("c-x", "c-e"): nc.edit_and_execute,
149 150 ("c-x", "e"): nc.edit_and_execute,
150 151 # Alt
151 152 ("escape", "b"): nc.backward_word,
152 153 ("escape", "c"): nc.capitalize_word,
153 154 ("escape", "d"): nc.kill_word,
154 155 ("escape", "h"): nc.backward_kill_word,
155 156 ("escape", "l"): nc.downcase_word,
156 157 ("escape", "u"): nc.uppercase_word,
157 158 ("escape", "y"): nc.yank_pop,
158 159 ("escape", "."): nc.yank_last_arg,
159 160 }
160 161
161 162 for keys, cmd in keys_cmd_dict.items():
162 163 kb.add(*keys, filter=focused_insert & ebivim)(cmd)
163 164
164 165 def get_input_mode(self):
165 166 if sys.version_info[0] == 3:
166 167 app = get_app()
167 168 app.ttimeoutlen = shell.ttimeoutlen
168 169 app.timeoutlen = shell.timeoutlen
169 170
170 171 return self._input_mode
171 172
172 173 def set_input_mode(self, mode):
173 174 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
174 175 cursor = "\x1b[{} q".format(shape)
175 176
176 177 if hasattr(sys.stdout, "_cli"):
177 178 write = sys.stdout._cli.output.write_raw
178 179 else:
179 180 write = sys.stdout.write
180 181
181 182 write(cursor)
182 183 sys.stdout.flush()
183 184
184 185 self._input_mode = mode
185 186
186 187 if shell.editing_mode == "vi" and shell.modal_cursor:
187 188 ViState._input_mode = InputMode.INSERT
188 189 ViState.input_mode = property(get_input_mode, set_input_mode)
189 190
190 191 return kb
191 192
192 193
193 194 def reformat_text_before_cursor(buffer, document, shell):
194 195 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
195 196 try:
196 197 formatted_text = shell.reformat_handler(text)
197 198 buffer.insert_text(formatted_text)
198 199 except Exception as e:
199 200 buffer.insert_text(text)
200 201
201 202
202 203 def newline_or_execute_outer(shell):
203 204
204 205 def newline_or_execute(event):
205 206 """When the user presses return, insert a newline or execute the code."""
206 207 b = event.current_buffer
207 208 d = b.document
208 209
209 210 if b.complete_state:
210 211 cc = b.complete_state.current_completion
211 212 if cc:
212 213 b.apply_completion(cc)
213 214 else:
214 215 b.cancel_completion()
215 216 return
216 217
217 218 # If there's only one line, treat it as if the cursor is at the end.
218 219 # See https://github.com/ipython/ipython/issues/10425
219 220 if d.line_count == 1:
220 221 check_text = d.text
221 222 else:
222 223 check_text = d.text[:d.cursor_position]
223 224 status, indent = shell.check_complete(check_text)
224 225
225 226 # if all we have after the cursor is whitespace: reformat current text
226 227 # before cursor
227 228 after_cursor = d.text[d.cursor_position:]
228 229 reformatted = False
229 230 if not after_cursor.strip():
230 231 reformat_text_before_cursor(b, d, shell)
231 232 reformatted = True
232 233 if not (d.on_last_line or
233 234 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
234 235 ):
235 236 if shell.autoindent:
236 237 b.insert_text('\n' + indent)
237 238 else:
238 239 b.insert_text('\n')
239 240 return
240 241
241 242 if (status != 'incomplete') and b.accept_handler:
242 243 if not reformatted:
243 244 reformat_text_before_cursor(b, d, shell)
244 245 b.validate_and_handle()
245 246 else:
246 247 if shell.autoindent:
247 248 b.insert_text('\n' + indent)
248 249 else:
249 250 b.insert_text('\n')
250 251 return newline_or_execute
251 252
252 253
253 254 def previous_history_or_previous_completion(event):
254 255 """
255 256 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
256 257
257 258 If completer is open this still select previous completion.
258 259 """
259 260 event.current_buffer.auto_up()
260 261
261 262
262 263 def next_history_or_next_completion(event):
263 264 """
264 265 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
265 266
266 267 If completer is open this still select next completion.
267 268 """
268 269 event.current_buffer.auto_down()
269 270
270 271
271 272 def dismiss_completion(event):
272 273 b = event.current_buffer
273 274 if b.complete_state:
274 275 b.cancel_completion()
275 276
276 277
277 278 def reset_buffer(event):
278 279 b = event.current_buffer
279 280 if b.complete_state:
280 281 b.cancel_completion()
281 282 else:
282 283 b.reset()
283 284
284 285
285 286 def reset_search_buffer(event):
286 287 if event.current_buffer.document.text:
287 288 event.current_buffer.reset()
288 289 else:
289 290 event.app.layout.focus(DEFAULT_BUFFER)
290 291
291 292 def suspend_to_bg(event):
292 293 event.app.suspend_to_background()
293 294
294 295 def force_exit(event):
295 296 """
296 297 Force exit (with a non-zero return value)
297 298 """
298 299 sys.exit("Quit")
299 300
300 301 def indent_buffer(event):
301 302 event.current_buffer.insert_text(' ' * 4)
302 303
303 304 @undoc
304 305 def newline_with_copy_margin(event):
305 306 """
306 307 DEPRECATED since IPython 6.0
307 308
308 309 See :any:`newline_autoindent_outer` for a replacement.
309 310
310 311 Preserve margin and cursor position when using
311 312 Control-O to insert a newline in EMACS mode
312 313 """
313 314 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
314 315 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
315 316 DeprecationWarning, stacklevel=2)
316 317
317 318 b = event.current_buffer
318 319 cursor_start_pos = b.document.cursor_position_col
319 320 b.newline(copy_margin=True)
320 321 b.cursor_up(count=1)
321 322 cursor_end_pos = b.document.cursor_position_col
322 323 if cursor_start_pos != cursor_end_pos:
323 324 pos_diff = cursor_start_pos - cursor_end_pos
324 325 b.cursor_right(count=pos_diff)
325 326
326 327 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
327 328 """
328 329 Return a function suitable for inserting a indented newline after the cursor.
329 330
330 331 Fancier version of deprecated ``newline_with_copy_margin`` which should
331 332 compute the correct indentation of the inserted line. That is to say, indent
332 333 by 4 extra space after a function definition, class definition, context
333 334 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
334 335 """
335 336
336 337 def newline_autoindent(event):
337 338 """insert a newline after the cursor indented appropriately."""
338 339 b = event.current_buffer
339 340 d = b.document
340 341
341 342 if b.complete_state:
342 343 b.cancel_completion()
343 344 text = d.text[:d.cursor_position] + '\n'
344 345 _, indent = inputsplitter.check_complete(text)
345 346 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
346 347
347 348 return newline_autoindent
348 349
349 350
350 351 def open_input_in_editor(event):
351 352 event.app.current_buffer.open_in_editor()
352 353
353 354
354 355 if sys.platform == 'win32':
355 356 from IPython.core.error import TryNext
356 357 from IPython.lib.clipboard import (ClipboardEmpty,
357 358 win32_clipboard_get,
358 359 tkinter_clipboard_get)
359 360
360 361 @undoc
361 362 def win_paste(event):
362 363 try:
363 364 text = win32_clipboard_get()
364 365 except TryNext:
365 366 try:
366 367 text = tkinter_clipboard_get()
367 368 except (TryNext, ClipboardEmpty):
368 369 return
369 370 except ClipboardEmpty:
370 371 return
371 372 event.current_buffer.insert_text(text.replace("\t", " " * 4))
General Comments 0
You need to be logged in to leave comments. Login now