##// END OF EJS Templates
Switch default shortcuts for cycling auto-suggestions...
krassowski -
Show More
@@ -1,630 +1,630 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 ["c-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 ["c-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 ["c-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 ["c-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 ["escape", "down"],
269 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
269 "has_suggestion & default_buffer_focused & emacs_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 # For now this binding is inactive (the filter includes `never`).
281 # TODO: remove `never` if we reach a consensus in #13991
281 # TODO: remove `never` if we reach a consensus in #13991
282 # TODO: use `emacs_like_insert_mode` once #13991 is in
282 # TODO: use `emacs_like_insert_mode` once #13991 is in
283 "never & default_buffer_focused & ((vi_insert_mode & ebivim) | emacs_insert_mode)",
283 "never & default_buffer_focused & ((vi_insert_mode & ebivim) | emacs_insert_mode)",
284 ),
284 ),
285 ]
285 ]
286
286
287
287
288 SIMPLE_CONTROL_BINDINGS = [
288 SIMPLE_CONTROL_BINDINGS = [
289 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
289 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
290 for key, cmd in {
290 for key, cmd in {
291 "c-a": nc.beginning_of_line,
291 "c-a": nc.beginning_of_line,
292 "c-b": nc.backward_char,
292 "c-b": nc.backward_char,
293 "c-k": nc.kill_line,
293 "c-k": nc.kill_line,
294 "c-w": nc.backward_kill_word,
294 "c-w": nc.backward_kill_word,
295 "c-y": nc.yank,
295 "c-y": nc.yank,
296 "c-_": nc.undo,
296 "c-_": nc.undo,
297 }.items()
297 }.items()
298 ]
298 ]
299
299
300
300
301 ALT_AND_COMOBO_CONTROL_BINDINGS = [
301 ALT_AND_COMOBO_CONTROL_BINDINGS = [
302 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
302 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
303 for keys, cmd in {
303 for keys, cmd in {
304 # Control Combos
304 # Control Combos
305 ("c-x", "c-e"): nc.edit_and_execute,
305 ("c-x", "c-e"): nc.edit_and_execute,
306 ("c-x", "e"): nc.edit_and_execute,
306 ("c-x", "e"): nc.edit_and_execute,
307 # Alt
307 # Alt
308 ("escape", "b"): nc.backward_word,
308 ("escape", "b"): nc.backward_word,
309 ("escape", "c"): nc.capitalize_word,
309 ("escape", "c"): nc.capitalize_word,
310 ("escape", "d"): nc.kill_word,
310 ("escape", "d"): nc.kill_word,
311 ("escape", "h"): nc.backward_kill_word,
311 ("escape", "h"): nc.backward_kill_word,
312 ("escape", "l"): nc.downcase_word,
312 ("escape", "l"): nc.downcase_word,
313 ("escape", "u"): nc.uppercase_word,
313 ("escape", "u"): nc.uppercase_word,
314 ("escape", "y"): nc.yank_pop,
314 ("escape", "y"): nc.yank_pop,
315 ("escape", "."): nc.yank_last_arg,
315 ("escape", "."): nc.yank_last_arg,
316 }.items()
316 }.items()
317 ]
317 ]
318
318
319
319
320 def add_binding(bindings: KeyBindings, binding: Binding):
320 def add_binding(bindings: KeyBindings, binding: Binding):
321 bindings.add(
321 bindings.add(
322 *binding.keys,
322 *binding.keys,
323 **({"filter": binding.filter} if binding.filter is not None else {}),
323 **({"filter": binding.filter} if binding.filter is not None else {}),
324 )(binding.command)
324 )(binding.command)
325
325
326
326
327 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
327 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
328 """Set up the prompt_toolkit keyboard shortcuts for IPython.
328 """Set up the prompt_toolkit keyboard shortcuts for IPython.
329
329
330 Parameters
330 Parameters
331 ----------
331 ----------
332 shell: InteractiveShell
332 shell: InteractiveShell
333 The current IPython shell Instance
333 The current IPython shell Instance
334 skip: List[Binding]
334 skip: List[Binding]
335 Bindings to skip.
335 Bindings to skip.
336
336
337 Returns
337 Returns
338 -------
338 -------
339 KeyBindings
339 KeyBindings
340 the keybinding instance for prompt toolkit.
340 the keybinding instance for prompt toolkit.
341
341
342 """
342 """
343 kb = KeyBindings()
343 kb = KeyBindings()
344 skip = skip or []
344 skip = skip or []
345 for binding in KEY_BINDINGS:
345 for binding in KEY_BINDINGS:
346 skip_this_one = False
346 skip_this_one = False
347 for to_skip in skip:
347 for to_skip in skip:
348 if (
348 if (
349 to_skip.command == binding.command
349 to_skip.command == binding.command
350 and to_skip.filter == binding.filter
350 and to_skip.filter == binding.filter
351 and to_skip.keys == binding.keys
351 and to_skip.keys == binding.keys
352 ):
352 ):
353 skip_this_one = True
353 skip_this_one = True
354 break
354 break
355 if skip_this_one:
355 if skip_this_one:
356 continue
356 continue
357 add_binding(kb, binding)
357 add_binding(kb, binding)
358
358
359 def get_input_mode(self):
359 def get_input_mode(self):
360 app = get_app()
360 app = get_app()
361 app.ttimeoutlen = shell.ttimeoutlen
361 app.ttimeoutlen = shell.ttimeoutlen
362 app.timeoutlen = shell.timeoutlen
362 app.timeoutlen = shell.timeoutlen
363
363
364 return self._input_mode
364 return self._input_mode
365
365
366 def set_input_mode(self, mode):
366 def set_input_mode(self, mode):
367 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
367 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
368 cursor = "\x1b[{} q".format(shape)
368 cursor = "\x1b[{} q".format(shape)
369
369
370 sys.stdout.write(cursor)
370 sys.stdout.write(cursor)
371 sys.stdout.flush()
371 sys.stdout.flush()
372
372
373 self._input_mode = mode
373 self._input_mode = mode
374
374
375 if shell.editing_mode == "vi" and shell.modal_cursor:
375 if shell.editing_mode == "vi" and shell.modal_cursor:
376 ViState._input_mode = InputMode.INSERT # type: ignore
376 ViState._input_mode = InputMode.INSERT # type: ignore
377 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
377 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
378
378
379 return kb
379 return kb
380
380
381
381
382 def reformat_and_execute(event):
382 def reformat_and_execute(event):
383 """Reformat code and execute it"""
383 """Reformat code and execute it"""
384 shell = get_ipython()
384 shell = get_ipython()
385 reformat_text_before_cursor(
385 reformat_text_before_cursor(
386 event.current_buffer, event.current_buffer.document, shell
386 event.current_buffer, event.current_buffer.document, shell
387 )
387 )
388 event.current_buffer.validate_and_handle()
388 event.current_buffer.validate_and_handle()
389
389
390
390
391 def reformat_text_before_cursor(buffer, document, shell):
391 def reformat_text_before_cursor(buffer, document, shell):
392 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
392 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
393 try:
393 try:
394 formatted_text = shell.reformat_handler(text)
394 formatted_text = shell.reformat_handler(text)
395 buffer.insert_text(formatted_text)
395 buffer.insert_text(formatted_text)
396 except Exception as e:
396 except Exception as e:
397 buffer.insert_text(text)
397 buffer.insert_text(text)
398
398
399
399
400 def handle_return_or_newline_or_execute(event):
400 def handle_return_or_newline_or_execute(event):
401 shell = get_ipython()
401 shell = get_ipython()
402 if getattr(shell, "handle_return", None):
402 if getattr(shell, "handle_return", None):
403 return shell.handle_return(shell)(event)
403 return shell.handle_return(shell)(event)
404 else:
404 else:
405 return newline_or_execute_outer(shell)(event)
405 return newline_or_execute_outer(shell)(event)
406
406
407
407
408 def newline_or_execute_outer(shell):
408 def newline_or_execute_outer(shell):
409 def newline_or_execute(event):
409 def newline_or_execute(event):
410 """When the user presses return, insert a newline or execute the code."""
410 """When the user presses return, insert a newline or execute the code."""
411 b = event.current_buffer
411 b = event.current_buffer
412 d = b.document
412 d = b.document
413
413
414 if b.complete_state:
414 if b.complete_state:
415 cc = b.complete_state.current_completion
415 cc = b.complete_state.current_completion
416 if cc:
416 if cc:
417 b.apply_completion(cc)
417 b.apply_completion(cc)
418 else:
418 else:
419 b.cancel_completion()
419 b.cancel_completion()
420 return
420 return
421
421
422 # If there's only one line, treat it as if the cursor is at the end.
422 # 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
423 # See https://github.com/ipython/ipython/issues/10425
424 if d.line_count == 1:
424 if d.line_count == 1:
425 check_text = d.text
425 check_text = d.text
426 else:
426 else:
427 check_text = d.text[: d.cursor_position]
427 check_text = d.text[: d.cursor_position]
428 status, indent = shell.check_complete(check_text)
428 status, indent = shell.check_complete(check_text)
429
429
430 # if all we have after the cursor is whitespace: reformat current text
430 # if all we have after the cursor is whitespace: reformat current text
431 # before cursor
431 # before cursor
432 after_cursor = d.text[d.cursor_position :]
432 after_cursor = d.text[d.cursor_position :]
433 reformatted = False
433 reformatted = False
434 if not after_cursor.strip():
434 if not after_cursor.strip():
435 reformat_text_before_cursor(b, d, shell)
435 reformat_text_before_cursor(b, d, shell)
436 reformatted = True
436 reformatted = True
437 if not (
437 if not (
438 d.on_last_line
438 d.on_last_line
439 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
439 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
440 ):
440 ):
441 if shell.autoindent:
441 if shell.autoindent:
442 b.insert_text("\n" + indent)
442 b.insert_text("\n" + indent)
443 else:
443 else:
444 b.insert_text("\n")
444 b.insert_text("\n")
445 return
445 return
446
446
447 if (status != "incomplete") and b.accept_handler:
447 if (status != "incomplete") and b.accept_handler:
448 if not reformatted:
448 if not reformatted:
449 reformat_text_before_cursor(b, d, shell)
449 reformat_text_before_cursor(b, d, shell)
450 b.validate_and_handle()
450 b.validate_and_handle()
451 else:
451 else:
452 if shell.autoindent:
452 if shell.autoindent:
453 b.insert_text("\n" + indent)
453 b.insert_text("\n" + indent)
454 else:
454 else:
455 b.insert_text("\n")
455 b.insert_text("\n")
456
456
457 return newline_or_execute
457 return newline_or_execute
458
458
459
459
460 def previous_history_or_previous_completion(event):
460 def previous_history_or_previous_completion(event):
461 """
461 """
462 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
462 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
463
463
464 If completer is open this still select previous completion.
464 If completer is open this still select previous completion.
465 """
465 """
466 event.current_buffer.auto_up()
466 event.current_buffer.auto_up()
467
467
468
468
469 def next_history_or_next_completion(event):
469 def next_history_or_next_completion(event):
470 """
470 """
471 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
471 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
472
472
473 If completer is open this still select next completion.
473 If completer is open this still select next completion.
474 """
474 """
475 event.current_buffer.auto_down()
475 event.current_buffer.auto_down()
476
476
477
477
478 def dismiss_completion(event):
478 def dismiss_completion(event):
479 """Dismiss completion"""
479 """Dismiss completion"""
480 b = event.current_buffer
480 b = event.current_buffer
481 if b.complete_state:
481 if b.complete_state:
482 b.cancel_completion()
482 b.cancel_completion()
483
483
484
484
485 def reset_buffer(event):
485 def reset_buffer(event):
486 """Reset buffer"""
486 """Reset buffer"""
487 b = event.current_buffer
487 b = event.current_buffer
488 if b.complete_state:
488 if b.complete_state:
489 b.cancel_completion()
489 b.cancel_completion()
490 else:
490 else:
491 b.reset()
491 b.reset()
492
492
493
493
494 def reset_search_buffer(event):
494 def reset_search_buffer(event):
495 """Reset search buffer"""
495 """Reset search buffer"""
496 if event.current_buffer.document.text:
496 if event.current_buffer.document.text:
497 event.current_buffer.reset()
497 event.current_buffer.reset()
498 else:
498 else:
499 event.app.layout.focus(DEFAULT_BUFFER)
499 event.app.layout.focus(DEFAULT_BUFFER)
500
500
501
501
502 def suspend_to_bg(event):
502 def suspend_to_bg(event):
503 """Suspend to background"""
503 """Suspend to background"""
504 event.app.suspend_to_background()
504 event.app.suspend_to_background()
505
505
506
506
507 def quit(event):
507 def quit(event):
508 """
508 """
509 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
509 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
510
510
511 On platforms that support SIGQUIT, send SIGQUIT to the current process.
511 On platforms that support SIGQUIT, send SIGQUIT to the current process.
512 On other platforms, just exit the process with a message.
512 On other platforms, just exit the process with a message.
513 """
513 """
514 sigquit = getattr(signal, "SIGQUIT", None)
514 sigquit = getattr(signal, "SIGQUIT", None)
515 if sigquit is not None:
515 if sigquit is not None:
516 os.kill(0, signal.SIGQUIT)
516 os.kill(0, signal.SIGQUIT)
517 else:
517 else:
518 sys.exit("Quit")
518 sys.exit("Quit")
519
519
520
520
521 def indent_buffer(event):
521 def indent_buffer(event):
522 """Indent buffer"""
522 """Indent buffer"""
523 event.current_buffer.insert_text(" " * 4)
523 event.current_buffer.insert_text(" " * 4)
524
524
525
525
526 def newline_autoindent(event):
526 def newline_autoindent(event):
527 """Insert a newline after the cursor indented appropriately.
527 """Insert a newline after the cursor indented appropriately.
528
528
529 Fancier version of former ``newline_with_copy_margin`` which should
529 Fancier version of former ``newline_with_copy_margin`` which should
530 compute the correct indentation of the inserted line. That is to say, indent
530 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
531 by 4 extra space after a function definition, class definition, context
532 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
532 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
533 """
533 """
534 shell = get_ipython()
534 shell = get_ipython()
535 inputsplitter = shell.input_transformer_manager
535 inputsplitter = shell.input_transformer_manager
536 b = event.current_buffer
536 b = event.current_buffer
537 d = b.document
537 d = b.document
538
538
539 if b.complete_state:
539 if b.complete_state:
540 b.cancel_completion()
540 b.cancel_completion()
541 text = d.text[: d.cursor_position] + "\n"
541 text = d.text[: d.cursor_position] + "\n"
542 _, indent = inputsplitter.check_complete(text)
542 _, indent = inputsplitter.check_complete(text)
543 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
543 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
544
544
545
545
546 def open_input_in_editor(event):
546 def open_input_in_editor(event):
547 """Open code from input in external editor"""
547 """Open code from input in external editor"""
548 event.app.current_buffer.open_in_editor()
548 event.app.current_buffer.open_in_editor()
549
549
550
550
551 if sys.platform == "win32":
551 if sys.platform == "win32":
552 from IPython.core.error import TryNext
552 from IPython.core.error import TryNext
553 from IPython.lib.clipboard import (
553 from IPython.lib.clipboard import (
554 ClipboardEmpty,
554 ClipboardEmpty,
555 tkinter_clipboard_get,
555 tkinter_clipboard_get,
556 win32_clipboard_get,
556 win32_clipboard_get,
557 )
557 )
558
558
559 @undoc
559 @undoc
560 def win_paste(event):
560 def win_paste(event):
561 try:
561 try:
562 text = win32_clipboard_get()
562 text = win32_clipboard_get()
563 except TryNext:
563 except TryNext:
564 try:
564 try:
565 text = tkinter_clipboard_get()
565 text = tkinter_clipboard_get()
566 except (TryNext, ClipboardEmpty):
566 except (TryNext, ClipboardEmpty):
567 return
567 return
568 except ClipboardEmpty:
568 except ClipboardEmpty:
569 return
569 return
570 event.current_buffer.insert_text(text.replace("\t", " " * 4))
570 event.current_buffer.insert_text(text.replace("\t", " " * 4))
571
571
572 else:
572 else:
573
573
574 @undoc
574 @undoc
575 def win_paste(event):
575 def win_paste(event):
576 """Stub used on other platforms"""
576 """Stub used on other platforms"""
577 pass
577 pass
578
578
579
579
580 KEY_BINDINGS = [
580 KEY_BINDINGS = [
581 Binding(
581 Binding(
582 handle_return_or_newline_or_execute,
582 handle_return_or_newline_or_execute,
583 ["enter"],
583 ["enter"],
584 "default_buffer_focused & ~has_selection & insert_mode",
584 "default_buffer_focused & ~has_selection & insert_mode",
585 ),
585 ),
586 Binding(
586 Binding(
587 reformat_and_execute,
587 reformat_and_execute,
588 ["escape", "enter"],
588 ["escape", "enter"],
589 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
589 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
590 ),
590 ),
591 Binding(quit, ["c-\\"]),
591 Binding(quit, ["c-\\"]),
592 Binding(
592 Binding(
593 previous_history_or_previous_completion,
593 previous_history_or_previous_completion,
594 ["c-p"],
594 ["c-p"],
595 "vi_insert_mode & default_buffer_focused",
595 "vi_insert_mode & default_buffer_focused",
596 ),
596 ),
597 Binding(
597 Binding(
598 next_history_or_next_completion,
598 next_history_or_next_completion,
599 ["c-n"],
599 ["c-n"],
600 "vi_insert_mode & default_buffer_focused",
600 "vi_insert_mode & default_buffer_focused",
601 ),
601 ),
602 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
602 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
603 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
603 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
604 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
604 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
605 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
605 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
606 Binding(
606 Binding(
607 indent_buffer,
607 indent_buffer,
608 ["tab"], # Ctrl+I == Tab
608 ["tab"], # Ctrl+I == Tab
609 "default_buffer_focused"
609 "default_buffer_focused"
610 " & ~has_selection"
610 " & ~has_selection"
611 " & insert_mode"
611 " & insert_mode"
612 " & cursor_in_leading_ws",
612 " & cursor_in_leading_ws",
613 ),
613 ),
614 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
614 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
615 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
615 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
616 *AUTO_MATCH_BINDINGS,
616 *AUTO_MATCH_BINDINGS,
617 *AUTO_SUGGEST_BINDINGS,
617 *AUTO_SUGGEST_BINDINGS,
618 Binding(
618 Binding(
619 display_completions_like_readline,
619 display_completions_like_readline,
620 ["c-i"],
620 ["c-i"],
621 "readline_like_completions"
621 "readline_like_completions"
622 " & default_buffer_focused"
622 " & default_buffer_focused"
623 " & ~has_selection"
623 " & ~has_selection"
624 " & insert_mode"
624 " & insert_mode"
625 " & ~cursor_in_leading_ws",
625 " & ~cursor_in_leading_ws",
626 ),
626 ),
627 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
627 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
628 *SIMPLE_CONTROL_BINDINGS,
628 *SIMPLE_CONTROL_BINDINGS,
629 *ALT_AND_COMOBO_CONTROL_BINDINGS,
629 *ALT_AND_COMOBO_CONTROL_BINDINGS,
630 ]
630 ]
General Comments 0
You need to be logged in to leave comments. Login now