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