##// END OF EJS Templates
Backport PR #14027 on branch 8.12.x (Resume hinting on right arrow press by default) (#14042)...
Matthias Bussonnier -
r28253:887da037 merge
parent child Browse files
Show More
@@ -1,630 +1,627 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 os
9 import os
10 import signal
10 import signal
11 import sys
11 import sys
12 import warnings
12 import warnings
13 from dataclasses import dataclass
13 from dataclasses import dataclass
14 from typing import Callable, Any, Optional, List
14 from typing import Callable, Any, Optional, List
15
15
16 from prompt_toolkit.application.current import get_app
16 from prompt_toolkit.application.current import get_app
17 from prompt_toolkit.key_binding import KeyBindings
17 from prompt_toolkit.key_binding import KeyBindings
18 from prompt_toolkit.key_binding.key_processor import KeyPressEvent
18 from prompt_toolkit.key_binding.key_processor import KeyPressEvent
19 from prompt_toolkit.key_binding.bindings import named_commands as nc
19 from prompt_toolkit.key_binding.bindings import named_commands as nc
20 from prompt_toolkit.key_binding.bindings.completion import (
20 from prompt_toolkit.key_binding.bindings.completion import (
21 display_completions_like_readline,
21 display_completions_like_readline,
22 )
22 )
23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 from prompt_toolkit.filters import Condition
24 from prompt_toolkit.filters import Condition
25
25
26 from IPython.core.getipython import get_ipython
26 from IPython.core.getipython import get_ipython
27 from IPython.terminal.shortcuts import auto_match as match
27 from IPython.terminal.shortcuts import auto_match as match
28 from IPython.terminal.shortcuts import auto_suggest
28 from IPython.terminal.shortcuts import auto_suggest
29 from IPython.terminal.shortcuts.filters import filter_from_string
29 from IPython.terminal.shortcuts.filters import filter_from_string
30 from IPython.utils.decorators import undoc
30 from IPython.utils.decorators import undoc
31
31
32 from prompt_toolkit.enums import DEFAULT_BUFFER
32 from prompt_toolkit.enums import DEFAULT_BUFFER
33
33
34 __all__ = ["create_ipython_shortcuts"]
34 __all__ = ["create_ipython_shortcuts"]
35
35
36
36
37 @dataclass
37 @dataclass
38 class BaseBinding:
38 class BaseBinding:
39 command: Callable[[KeyPressEvent], Any]
39 command: Callable[[KeyPressEvent], Any]
40 keys: List[str]
40 keys: List[str]
41
41
42
42
43 @dataclass
43 @dataclass
44 class RuntimeBinding(BaseBinding):
44 class RuntimeBinding(BaseBinding):
45 filter: Condition
45 filter: Condition
46
46
47
47
48 @dataclass
48 @dataclass
49 class Binding(BaseBinding):
49 class Binding(BaseBinding):
50 # while filter could be created by referencing variables directly (rather
50 # while filter could be created by referencing variables directly (rather
51 # than created from strings), by using strings we ensure that users will
51 # than created from strings), by using strings we ensure that users will
52 # be able to create filters in configuration (e.g. JSON) files too, which
52 # be able to create filters in configuration (e.g. JSON) files too, which
53 # also benefits the documentation by enforcing human-readable filter names.
53 # also benefits the documentation by enforcing human-readable filter names.
54 condition: Optional[str] = None
54 condition: Optional[str] = None
55
55
56 def __post_init__(self):
56 def __post_init__(self):
57 if self.condition:
57 if self.condition:
58 self.filter = filter_from_string(self.condition)
58 self.filter = filter_from_string(self.condition)
59 else:
59 else:
60 self.filter = None
60 self.filter = None
61
61
62
62
63 def create_identifier(handler: Callable):
63 def create_identifier(handler: Callable):
64 parts = handler.__module__.split(".")
64 parts = handler.__module__.split(".")
65 name = handler.__name__
65 name = handler.__name__
66 package = parts[0]
66 package = parts[0]
67 if len(parts) > 1:
67 if len(parts) > 1:
68 final_module = parts[-1]
68 final_module = parts[-1]
69 return f"{package}:{final_module}.{name}"
69 return f"{package}:{final_module}.{name}"
70 else:
70 else:
71 return f"{package}:{name}"
71 return f"{package}:{name}"
72
72
73
73
74 AUTO_MATCH_BINDINGS = [
74 AUTO_MATCH_BINDINGS = [
75 *[
75 *[
76 Binding(
76 Binding(
77 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end"
77 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end"
78 )
78 )
79 for key, cmd in match.auto_match_parens.items()
79 for key, cmd in match.auto_match_parens.items()
80 ],
80 ],
81 *[
81 *[
82 # raw string
82 # raw string
83 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix")
83 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix")
84 for key, cmd in match.auto_match_parens_raw_string.items()
84 for key, cmd in match.auto_match_parens_raw_string.items()
85 ],
85 ],
86 Binding(
86 Binding(
87 match.double_quote,
87 match.double_quote,
88 ['"'],
88 ['"'],
89 "focused_insert"
89 "focused_insert"
90 " & auto_match"
90 " & auto_match"
91 " & not_inside_unclosed_string"
91 " & not_inside_unclosed_string"
92 " & preceded_by_paired_double_quotes"
92 " & preceded_by_paired_double_quotes"
93 " & followed_by_closing_paren_or_end",
93 " & followed_by_closing_paren_or_end",
94 ),
94 ),
95 Binding(
95 Binding(
96 match.single_quote,
96 match.single_quote,
97 ["'"],
97 ["'"],
98 "focused_insert"
98 "focused_insert"
99 " & auto_match"
99 " & auto_match"
100 " & not_inside_unclosed_string"
100 " & not_inside_unclosed_string"
101 " & preceded_by_paired_single_quotes"
101 " & preceded_by_paired_single_quotes"
102 " & followed_by_closing_paren_or_end",
102 " & followed_by_closing_paren_or_end",
103 ),
103 ),
104 Binding(
104 Binding(
105 match.docstring_double_quotes,
105 match.docstring_double_quotes,
106 ['"'],
106 ['"'],
107 "focused_insert"
107 "focused_insert"
108 " & auto_match"
108 " & auto_match"
109 " & not_inside_unclosed_string"
109 " & not_inside_unclosed_string"
110 " & preceded_by_two_double_quotes",
110 " & preceded_by_two_double_quotes",
111 ),
111 ),
112 Binding(
112 Binding(
113 match.docstring_single_quotes,
113 match.docstring_single_quotes,
114 ["'"],
114 ["'"],
115 "focused_insert"
115 "focused_insert"
116 " & auto_match"
116 " & auto_match"
117 " & not_inside_unclosed_string"
117 " & not_inside_unclosed_string"
118 " & preceded_by_two_single_quotes",
118 " & preceded_by_two_single_quotes",
119 ),
119 ),
120 Binding(
120 Binding(
121 match.skip_over,
121 match.skip_over,
122 [")"],
122 [")"],
123 "focused_insert & auto_match & followed_by_closing_round_paren",
123 "focused_insert & auto_match & followed_by_closing_round_paren",
124 ),
124 ),
125 Binding(
125 Binding(
126 match.skip_over,
126 match.skip_over,
127 ["]"],
127 ["]"],
128 "focused_insert & auto_match & followed_by_closing_bracket",
128 "focused_insert & auto_match & followed_by_closing_bracket",
129 ),
129 ),
130 Binding(
130 Binding(
131 match.skip_over,
131 match.skip_over,
132 ["}"],
132 ["}"],
133 "focused_insert & auto_match & followed_by_closing_brace",
133 "focused_insert & auto_match & followed_by_closing_brace",
134 ),
134 ),
135 Binding(
135 Binding(
136 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote"
136 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote"
137 ),
137 ),
138 Binding(
138 Binding(
139 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote"
139 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote"
140 ),
140 ),
141 Binding(
141 Binding(
142 match.delete_pair,
142 match.delete_pair,
143 ["backspace"],
143 ["backspace"],
144 "focused_insert"
144 "focused_insert"
145 " & preceded_by_opening_round_paren"
145 " & preceded_by_opening_round_paren"
146 " & auto_match"
146 " & auto_match"
147 " & followed_by_closing_round_paren",
147 " & followed_by_closing_round_paren",
148 ),
148 ),
149 Binding(
149 Binding(
150 match.delete_pair,
150 match.delete_pair,
151 ["backspace"],
151 ["backspace"],
152 "focused_insert"
152 "focused_insert"
153 " & preceded_by_opening_bracket"
153 " & preceded_by_opening_bracket"
154 " & auto_match"
154 " & auto_match"
155 " & followed_by_closing_bracket",
155 " & followed_by_closing_bracket",
156 ),
156 ),
157 Binding(
157 Binding(
158 match.delete_pair,
158 match.delete_pair,
159 ["backspace"],
159 ["backspace"],
160 "focused_insert"
160 "focused_insert"
161 " & preceded_by_opening_brace"
161 " & preceded_by_opening_brace"
162 " & auto_match"
162 " & auto_match"
163 " & followed_by_closing_brace",
163 " & followed_by_closing_brace",
164 ),
164 ),
165 Binding(
165 Binding(
166 match.delete_pair,
166 match.delete_pair,
167 ["backspace"],
167 ["backspace"],
168 "focused_insert"
168 "focused_insert"
169 " & preceded_by_double_quote"
169 " & preceded_by_double_quote"
170 " & auto_match"
170 " & auto_match"
171 " & followed_by_double_quote",
171 " & followed_by_double_quote",
172 ),
172 ),
173 Binding(
173 Binding(
174 match.delete_pair,
174 match.delete_pair,
175 ["backspace"],
175 ["backspace"],
176 "focused_insert"
176 "focused_insert"
177 " & preceded_by_single_quote"
177 " & preceded_by_single_quote"
178 " & auto_match"
178 " & auto_match"
179 " & followed_by_single_quote",
179 " & followed_by_single_quote",
180 ),
180 ),
181 ]
181 ]
182
182
183 AUTO_SUGGEST_BINDINGS = [
183 AUTO_SUGGEST_BINDINGS = [
184 # there are two reasons for re-defining bindings defined upstream:
184 # there are two reasons for re-defining bindings defined upstream:
185 # 1) prompt-toolkit does not execute autosuggestion bindings in vi mode,
185 # 1) prompt-toolkit does not execute autosuggestion bindings in vi mode,
186 # 2) prompt-toolkit checks if we are at the end of text, not end of line
186 # 2) prompt-toolkit checks if we are at the end of text, not end of line
187 # hence it does not work in multi-line mode of navigable provider
187 # hence it does not work in multi-line mode of navigable provider
188 Binding(
188 Binding(
189 auto_suggest.accept_or_jump_to_end,
189 auto_suggest.accept_or_jump_to_end,
190 ["end"],
190 ["end"],
191 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
191 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
192 ),
192 ),
193 Binding(
193 Binding(
194 auto_suggest.accept_or_jump_to_end,
194 auto_suggest.accept_or_jump_to_end,
195 ["c-e"],
195 ["c-e"],
196 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
196 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
197 ),
197 ),
198 Binding(
198 Binding(
199 auto_suggest.accept,
199 auto_suggest.accept,
200 ["c-f"],
200 ["c-f"],
201 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
201 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
202 ),
202 ),
203 Binding(
203 Binding(
204 auto_suggest.accept,
204 auto_suggest.accept,
205 ["right"],
205 ["right"],
206 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
206 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
207 ),
207 ),
208 Binding(
208 Binding(
209 auto_suggest.accept_word,
209 auto_suggest.accept_word,
210 ["escape", "f"],
210 ["escape", "f"],
211 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
211 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
212 ),
212 ),
213 Binding(
213 Binding(
214 auto_suggest.accept_token,
214 auto_suggest.accept_token,
215 ["c-right"],
215 ["c-right"],
216 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
216 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
217 ),
217 ),
218 Binding(
218 Binding(
219 auto_suggest.discard,
219 auto_suggest.discard,
220 ["escape"],
220 ["escape"],
221 # note this one is using `emacs_insert_mode`, not `emacs_like_insert_mode`
221 # note this one is using `emacs_insert_mode`, not `emacs_like_insert_mode`
222 # as in `vi_insert_mode` we do not want `escape` to be shadowed (ever).
222 # as in `vi_insert_mode` we do not want `escape` to be shadowed (ever).
223 "has_suggestion & default_buffer_focused & emacs_insert_mode",
223 "has_suggestion & default_buffer_focused & emacs_insert_mode",
224 ),
224 ),
225 Binding(
225 Binding(
226 auto_suggest.discard,
226 auto_suggest.discard,
227 ["delete"],
227 ["delete"],
228 "has_suggestion & default_buffer_focused & emacs_insert_mode",
228 "has_suggestion & default_buffer_focused & emacs_insert_mode",
229 ),
229 ),
230 Binding(
230 Binding(
231 auto_suggest.swap_autosuggestion_up,
231 auto_suggest.swap_autosuggestion_up,
232 ["up"],
232 ["up"],
233 "navigable_suggestions"
233 "navigable_suggestions"
234 " & ~has_line_above"
234 " & ~has_line_above"
235 " & has_suggestion"
235 " & has_suggestion"
236 " & default_buffer_focused",
236 " & default_buffer_focused",
237 ),
237 ),
238 Binding(
238 Binding(
239 auto_suggest.swap_autosuggestion_down,
239 auto_suggest.swap_autosuggestion_down,
240 ["down"],
240 ["down"],
241 "navigable_suggestions"
241 "navigable_suggestions"
242 " & ~has_line_below"
242 " & ~has_line_below"
243 " & has_suggestion"
243 " & has_suggestion"
244 " & default_buffer_focused",
244 " & default_buffer_focused",
245 ),
245 ),
246 Binding(
246 Binding(
247 auto_suggest.up_and_update_hint,
247 auto_suggest.up_and_update_hint,
248 ["up"],
248 ["up"],
249 "has_line_above & navigable_suggestions & default_buffer_focused",
249 "has_line_above & navigable_suggestions & default_buffer_focused",
250 ),
250 ),
251 Binding(
251 Binding(
252 auto_suggest.down_and_update_hint,
252 auto_suggest.down_and_update_hint,
253 ["down"],
253 ["down"],
254 "has_line_below & navigable_suggestions & default_buffer_focused",
254 "has_line_below & navigable_suggestions & default_buffer_focused",
255 ),
255 ),
256 Binding(
256 Binding(
257 auto_suggest.accept_character,
257 auto_suggest.accept_character,
258 ["escape", "right"],
258 ["escape", "right"],
259 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
259 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
260 ),
260 ),
261 Binding(
261 Binding(
262 auto_suggest.accept_and_move_cursor_left,
262 auto_suggest.accept_and_move_cursor_left,
263 ["c-left"],
263 ["c-left"],
264 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
264 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
265 ),
265 ),
266 Binding(
266 Binding(
267 auto_suggest.accept_and_keep_cursor,
267 auto_suggest.accept_and_keep_cursor,
268 ["c-down"],
268 ["c-down"],
269 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
269 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
270 ),
270 ),
271 Binding(
271 Binding(
272 auto_suggest.backspace_and_resume_hint,
272 auto_suggest.backspace_and_resume_hint,
273 ["backspace"],
273 ["backspace"],
274 # no `has_suggestion` here to allow resuming if no suggestion
274 # no `has_suggestion` here to allow resuming if no suggestion
275 "default_buffer_focused & emacs_like_insert_mode",
275 "default_buffer_focused & emacs_like_insert_mode",
276 ),
276 ),
277 Binding(
277 Binding(
278 auto_suggest.resume_hinting,
278 auto_suggest.resume_hinting,
279 ["right"],
279 ["right"],
280 # For now this binding is inactive (the filter includes `never`).
280 "default_buffer_focused & emacs_like_insert_mode",
281 # TODO: remove `never` if we reach a consensus in #13991
282 # TODO: use `emacs_like_insert_mode` once #13991 is in
283 "never & default_buffer_focused & ((vi_insert_mode & ebivim) | emacs_insert_mode)",
284 ),
281 ),
285 ]
282 ]
286
283
287
284
288 SIMPLE_CONTROL_BINDINGS = [
285 SIMPLE_CONTROL_BINDINGS = [
289 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
286 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
290 for key, cmd in {
287 for key, cmd in {
291 "c-a": nc.beginning_of_line,
288 "c-a": nc.beginning_of_line,
292 "c-b": nc.backward_char,
289 "c-b": nc.backward_char,
293 "c-k": nc.kill_line,
290 "c-k": nc.kill_line,
294 "c-w": nc.backward_kill_word,
291 "c-w": nc.backward_kill_word,
295 "c-y": nc.yank,
292 "c-y": nc.yank,
296 "c-_": nc.undo,
293 "c-_": nc.undo,
297 }.items()
294 }.items()
298 ]
295 ]
299
296
300
297
301 ALT_AND_COMOBO_CONTROL_BINDINGS = [
298 ALT_AND_COMOBO_CONTROL_BINDINGS = [
302 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
299 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
303 for keys, cmd in {
300 for keys, cmd in {
304 # Control Combos
301 # Control Combos
305 ("c-x", "c-e"): nc.edit_and_execute,
302 ("c-x", "c-e"): nc.edit_and_execute,
306 ("c-x", "e"): nc.edit_and_execute,
303 ("c-x", "e"): nc.edit_and_execute,
307 # Alt
304 # Alt
308 ("escape", "b"): nc.backward_word,
305 ("escape", "b"): nc.backward_word,
309 ("escape", "c"): nc.capitalize_word,
306 ("escape", "c"): nc.capitalize_word,
310 ("escape", "d"): nc.kill_word,
307 ("escape", "d"): nc.kill_word,
311 ("escape", "h"): nc.backward_kill_word,
308 ("escape", "h"): nc.backward_kill_word,
312 ("escape", "l"): nc.downcase_word,
309 ("escape", "l"): nc.downcase_word,
313 ("escape", "u"): nc.uppercase_word,
310 ("escape", "u"): nc.uppercase_word,
314 ("escape", "y"): nc.yank_pop,
311 ("escape", "y"): nc.yank_pop,
315 ("escape", "."): nc.yank_last_arg,
312 ("escape", "."): nc.yank_last_arg,
316 }.items()
313 }.items()
317 ]
314 ]
318
315
319
316
320 def add_binding(bindings: KeyBindings, binding: Binding):
317 def add_binding(bindings: KeyBindings, binding: Binding):
321 bindings.add(
318 bindings.add(
322 *binding.keys,
319 *binding.keys,
323 **({"filter": binding.filter} if binding.filter is not None else {}),
320 **({"filter": binding.filter} if binding.filter is not None else {}),
324 )(binding.command)
321 )(binding.command)
325
322
326
323
327 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
324 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
328 """Set up the prompt_toolkit keyboard shortcuts for IPython.
325 """Set up the prompt_toolkit keyboard shortcuts for IPython.
329
326
330 Parameters
327 Parameters
331 ----------
328 ----------
332 shell: InteractiveShell
329 shell: InteractiveShell
333 The current IPython shell Instance
330 The current IPython shell Instance
334 skip: List[Binding]
331 skip: List[Binding]
335 Bindings to skip.
332 Bindings to skip.
336
333
337 Returns
334 Returns
338 -------
335 -------
339 KeyBindings
336 KeyBindings
340 the keybinding instance for prompt toolkit.
337 the keybinding instance for prompt toolkit.
341
338
342 """
339 """
343 kb = KeyBindings()
340 kb = KeyBindings()
344 skip = skip or []
341 skip = skip or []
345 for binding in KEY_BINDINGS:
342 for binding in KEY_BINDINGS:
346 skip_this_one = False
343 skip_this_one = False
347 for to_skip in skip:
344 for to_skip in skip:
348 if (
345 if (
349 to_skip.command == binding.command
346 to_skip.command == binding.command
350 and to_skip.filter == binding.filter
347 and to_skip.filter == binding.filter
351 and to_skip.keys == binding.keys
348 and to_skip.keys == binding.keys
352 ):
349 ):
353 skip_this_one = True
350 skip_this_one = True
354 break
351 break
355 if skip_this_one:
352 if skip_this_one:
356 continue
353 continue
357 add_binding(kb, binding)
354 add_binding(kb, binding)
358
355
359 def get_input_mode(self):
356 def get_input_mode(self):
360 app = get_app()
357 app = get_app()
361 app.ttimeoutlen = shell.ttimeoutlen
358 app.ttimeoutlen = shell.ttimeoutlen
362 app.timeoutlen = shell.timeoutlen
359 app.timeoutlen = shell.timeoutlen
363
360
364 return self._input_mode
361 return self._input_mode
365
362
366 def set_input_mode(self, mode):
363 def set_input_mode(self, mode):
367 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
364 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
368 cursor = "\x1b[{} q".format(shape)
365 cursor = "\x1b[{} q".format(shape)
369
366
370 sys.stdout.write(cursor)
367 sys.stdout.write(cursor)
371 sys.stdout.flush()
368 sys.stdout.flush()
372
369
373 self._input_mode = mode
370 self._input_mode = mode
374
371
375 if shell.editing_mode == "vi" and shell.modal_cursor:
372 if shell.editing_mode == "vi" and shell.modal_cursor:
376 ViState._input_mode = InputMode.INSERT # type: ignore
373 ViState._input_mode = InputMode.INSERT # type: ignore
377 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
374 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
378
375
379 return kb
376 return kb
380
377
381
378
382 def reformat_and_execute(event):
379 def reformat_and_execute(event):
383 """Reformat code and execute it"""
380 """Reformat code and execute it"""
384 shell = get_ipython()
381 shell = get_ipython()
385 reformat_text_before_cursor(
382 reformat_text_before_cursor(
386 event.current_buffer, event.current_buffer.document, shell
383 event.current_buffer, event.current_buffer.document, shell
387 )
384 )
388 event.current_buffer.validate_and_handle()
385 event.current_buffer.validate_and_handle()
389
386
390
387
391 def reformat_text_before_cursor(buffer, document, shell):
388 def reformat_text_before_cursor(buffer, document, shell):
392 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
389 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
393 try:
390 try:
394 formatted_text = shell.reformat_handler(text)
391 formatted_text = shell.reformat_handler(text)
395 buffer.insert_text(formatted_text)
392 buffer.insert_text(formatted_text)
396 except Exception as e:
393 except Exception as e:
397 buffer.insert_text(text)
394 buffer.insert_text(text)
398
395
399
396
400 def handle_return_or_newline_or_execute(event):
397 def handle_return_or_newline_or_execute(event):
401 shell = get_ipython()
398 shell = get_ipython()
402 if getattr(shell, "handle_return", None):
399 if getattr(shell, "handle_return", None):
403 return shell.handle_return(shell)(event)
400 return shell.handle_return(shell)(event)
404 else:
401 else:
405 return newline_or_execute_outer(shell)(event)
402 return newline_or_execute_outer(shell)(event)
406
403
407
404
408 def newline_or_execute_outer(shell):
405 def newline_or_execute_outer(shell):
409 def newline_or_execute(event):
406 def newline_or_execute(event):
410 """When the user presses return, insert a newline or execute the code."""
407 """When the user presses return, insert a newline or execute the code."""
411 b = event.current_buffer
408 b = event.current_buffer
412 d = b.document
409 d = b.document
413
410
414 if b.complete_state:
411 if b.complete_state:
415 cc = b.complete_state.current_completion
412 cc = b.complete_state.current_completion
416 if cc:
413 if cc:
417 b.apply_completion(cc)
414 b.apply_completion(cc)
418 else:
415 else:
419 b.cancel_completion()
416 b.cancel_completion()
420 return
417 return
421
418
422 # If there's only one line, treat it as if the cursor is at the end.
419 # If there's only one line, treat it as if the cursor is at the end.
423 # See https://github.com/ipython/ipython/issues/10425
420 # See https://github.com/ipython/ipython/issues/10425
424 if d.line_count == 1:
421 if d.line_count == 1:
425 check_text = d.text
422 check_text = d.text
426 else:
423 else:
427 check_text = d.text[: d.cursor_position]
424 check_text = d.text[: d.cursor_position]
428 status, indent = shell.check_complete(check_text)
425 status, indent = shell.check_complete(check_text)
429
426
430 # if all we have after the cursor is whitespace: reformat current text
427 # if all we have after the cursor is whitespace: reformat current text
431 # before cursor
428 # before cursor
432 after_cursor = d.text[d.cursor_position :]
429 after_cursor = d.text[d.cursor_position :]
433 reformatted = False
430 reformatted = False
434 if not after_cursor.strip():
431 if not after_cursor.strip():
435 reformat_text_before_cursor(b, d, shell)
432 reformat_text_before_cursor(b, d, shell)
436 reformatted = True
433 reformatted = True
437 if not (
434 if not (
438 d.on_last_line
435 d.on_last_line
439 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
436 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
440 ):
437 ):
441 if shell.autoindent:
438 if shell.autoindent:
442 b.insert_text("\n" + indent)
439 b.insert_text("\n" + indent)
443 else:
440 else:
444 b.insert_text("\n")
441 b.insert_text("\n")
445 return
442 return
446
443
447 if (status != "incomplete") and b.accept_handler:
444 if (status != "incomplete") and b.accept_handler:
448 if not reformatted:
445 if not reformatted:
449 reformat_text_before_cursor(b, d, shell)
446 reformat_text_before_cursor(b, d, shell)
450 b.validate_and_handle()
447 b.validate_and_handle()
451 else:
448 else:
452 if shell.autoindent:
449 if shell.autoindent:
453 b.insert_text("\n" + indent)
450 b.insert_text("\n" + indent)
454 else:
451 else:
455 b.insert_text("\n")
452 b.insert_text("\n")
456
453
457 return newline_or_execute
454 return newline_or_execute
458
455
459
456
460 def previous_history_or_previous_completion(event):
457 def previous_history_or_previous_completion(event):
461 """
458 """
462 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
459 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
463
460
464 If completer is open this still select previous completion.
461 If completer is open this still select previous completion.
465 """
462 """
466 event.current_buffer.auto_up()
463 event.current_buffer.auto_up()
467
464
468
465
469 def next_history_or_next_completion(event):
466 def next_history_or_next_completion(event):
470 """
467 """
471 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
468 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
472
469
473 If completer is open this still select next completion.
470 If completer is open this still select next completion.
474 """
471 """
475 event.current_buffer.auto_down()
472 event.current_buffer.auto_down()
476
473
477
474
478 def dismiss_completion(event):
475 def dismiss_completion(event):
479 """Dismiss completion"""
476 """Dismiss completion"""
480 b = event.current_buffer
477 b = event.current_buffer
481 if b.complete_state:
478 if b.complete_state:
482 b.cancel_completion()
479 b.cancel_completion()
483
480
484
481
485 def reset_buffer(event):
482 def reset_buffer(event):
486 """Reset buffer"""
483 """Reset buffer"""
487 b = event.current_buffer
484 b = event.current_buffer
488 if b.complete_state:
485 if b.complete_state:
489 b.cancel_completion()
486 b.cancel_completion()
490 else:
487 else:
491 b.reset()
488 b.reset()
492
489
493
490
494 def reset_search_buffer(event):
491 def reset_search_buffer(event):
495 """Reset search buffer"""
492 """Reset search buffer"""
496 if event.current_buffer.document.text:
493 if event.current_buffer.document.text:
497 event.current_buffer.reset()
494 event.current_buffer.reset()
498 else:
495 else:
499 event.app.layout.focus(DEFAULT_BUFFER)
496 event.app.layout.focus(DEFAULT_BUFFER)
500
497
501
498
502 def suspend_to_bg(event):
499 def suspend_to_bg(event):
503 """Suspend to background"""
500 """Suspend to background"""
504 event.app.suspend_to_background()
501 event.app.suspend_to_background()
505
502
506
503
507 def quit(event):
504 def quit(event):
508 """
505 """
509 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
506 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
510
507
511 On platforms that support SIGQUIT, send SIGQUIT to the current process.
508 On platforms that support SIGQUIT, send SIGQUIT to the current process.
512 On other platforms, just exit the process with a message.
509 On other platforms, just exit the process with a message.
513 """
510 """
514 sigquit = getattr(signal, "SIGQUIT", None)
511 sigquit = getattr(signal, "SIGQUIT", None)
515 if sigquit is not None:
512 if sigquit is not None:
516 os.kill(0, signal.SIGQUIT)
513 os.kill(0, signal.SIGQUIT)
517 else:
514 else:
518 sys.exit("Quit")
515 sys.exit("Quit")
519
516
520
517
521 def indent_buffer(event):
518 def indent_buffer(event):
522 """Indent buffer"""
519 """Indent buffer"""
523 event.current_buffer.insert_text(" " * 4)
520 event.current_buffer.insert_text(" " * 4)
524
521
525
522
526 def newline_autoindent(event):
523 def newline_autoindent(event):
527 """Insert a newline after the cursor indented appropriately.
524 """Insert a newline after the cursor indented appropriately.
528
525
529 Fancier version of former ``newline_with_copy_margin`` which should
526 Fancier version of former ``newline_with_copy_margin`` which should
530 compute the correct indentation of the inserted line. That is to say, indent
527 compute the correct indentation of the inserted line. That is to say, indent
531 by 4 extra space after a function definition, class definition, context
528 by 4 extra space after a function definition, class definition, context
532 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
529 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
533 """
530 """
534 shell = get_ipython()
531 shell = get_ipython()
535 inputsplitter = shell.input_transformer_manager
532 inputsplitter = shell.input_transformer_manager
536 b = event.current_buffer
533 b = event.current_buffer
537 d = b.document
534 d = b.document
538
535
539 if b.complete_state:
536 if b.complete_state:
540 b.cancel_completion()
537 b.cancel_completion()
541 text = d.text[: d.cursor_position] + "\n"
538 text = d.text[: d.cursor_position] + "\n"
542 _, indent = inputsplitter.check_complete(text)
539 _, indent = inputsplitter.check_complete(text)
543 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
540 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
544
541
545
542
546 def open_input_in_editor(event):
543 def open_input_in_editor(event):
547 """Open code from input in external editor"""
544 """Open code from input in external editor"""
548 event.app.current_buffer.open_in_editor()
545 event.app.current_buffer.open_in_editor()
549
546
550
547
551 if sys.platform == "win32":
548 if sys.platform == "win32":
552 from IPython.core.error import TryNext
549 from IPython.core.error import TryNext
553 from IPython.lib.clipboard import (
550 from IPython.lib.clipboard import (
554 ClipboardEmpty,
551 ClipboardEmpty,
555 tkinter_clipboard_get,
552 tkinter_clipboard_get,
556 win32_clipboard_get,
553 win32_clipboard_get,
557 )
554 )
558
555
559 @undoc
556 @undoc
560 def win_paste(event):
557 def win_paste(event):
561 try:
558 try:
562 text = win32_clipboard_get()
559 text = win32_clipboard_get()
563 except TryNext:
560 except TryNext:
564 try:
561 try:
565 text = tkinter_clipboard_get()
562 text = tkinter_clipboard_get()
566 except (TryNext, ClipboardEmpty):
563 except (TryNext, ClipboardEmpty):
567 return
564 return
568 except ClipboardEmpty:
565 except ClipboardEmpty:
569 return
566 return
570 event.current_buffer.insert_text(text.replace("\t", " " * 4))
567 event.current_buffer.insert_text(text.replace("\t", " " * 4))
571
568
572 else:
569 else:
573
570
574 @undoc
571 @undoc
575 def win_paste(event):
572 def win_paste(event):
576 """Stub used on other platforms"""
573 """Stub used on other platforms"""
577 pass
574 pass
578
575
579
576
580 KEY_BINDINGS = [
577 KEY_BINDINGS = [
581 Binding(
578 Binding(
582 handle_return_or_newline_or_execute,
579 handle_return_or_newline_or_execute,
583 ["enter"],
580 ["enter"],
584 "default_buffer_focused & ~has_selection & insert_mode",
581 "default_buffer_focused & ~has_selection & insert_mode",
585 ),
582 ),
586 Binding(
583 Binding(
587 reformat_and_execute,
584 reformat_and_execute,
588 ["escape", "enter"],
585 ["escape", "enter"],
589 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
586 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
590 ),
587 ),
591 Binding(quit, ["c-\\"]),
588 Binding(quit, ["c-\\"]),
592 Binding(
589 Binding(
593 previous_history_or_previous_completion,
590 previous_history_or_previous_completion,
594 ["c-p"],
591 ["c-p"],
595 "vi_insert_mode & default_buffer_focused",
592 "vi_insert_mode & default_buffer_focused",
596 ),
593 ),
597 Binding(
594 Binding(
598 next_history_or_next_completion,
595 next_history_or_next_completion,
599 ["c-n"],
596 ["c-n"],
600 "vi_insert_mode & default_buffer_focused",
597 "vi_insert_mode & default_buffer_focused",
601 ),
598 ),
602 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
599 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
603 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
600 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
604 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
601 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
605 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
602 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
606 Binding(
603 Binding(
607 indent_buffer,
604 indent_buffer,
608 ["tab"], # Ctrl+I == Tab
605 ["tab"], # Ctrl+I == Tab
609 "default_buffer_focused"
606 "default_buffer_focused"
610 " & ~has_selection"
607 " & ~has_selection"
611 " & insert_mode"
608 " & insert_mode"
612 " & cursor_in_leading_ws",
609 " & cursor_in_leading_ws",
613 ),
610 ),
614 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
611 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
615 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
612 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
616 *AUTO_MATCH_BINDINGS,
613 *AUTO_MATCH_BINDINGS,
617 *AUTO_SUGGEST_BINDINGS,
614 *AUTO_SUGGEST_BINDINGS,
618 Binding(
615 Binding(
619 display_completions_like_readline,
616 display_completions_like_readline,
620 ["c-i"],
617 ["c-i"],
621 "readline_like_completions"
618 "readline_like_completions"
622 " & default_buffer_focused"
619 " & default_buffer_focused"
623 " & ~has_selection"
620 " & ~has_selection"
624 " & insert_mode"
621 " & insert_mode"
625 " & ~cursor_in_leading_ws",
622 " & ~cursor_in_leading_ws",
626 ),
623 ),
627 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
624 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
628 *SIMPLE_CONTROL_BINDINGS,
625 *SIMPLE_CONTROL_BINDINGS,
629 *ALT_AND_COMOBO_CONTROL_BINDINGS,
626 *ALT_AND_COMOBO_CONTROL_BINDINGS,
630 ]
627 ]
General Comments 0
You need to be logged in to leave comments. Login now