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