##// END OF EJS Templates
Add missing line_below/line_above conditions
krassowski -
Show More
@@ -1,668 +1,671 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 re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import warnings
13 import warnings
14 from typing import Callable, Dict, Union
14 from typing import Callable, Dict, Union
15
15
16 from prompt_toolkit.application.current import get_app
16 from prompt_toolkit.application.current import get_app
17 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
17 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
18 from prompt_toolkit.filters import Condition, emacs_insert_mode, has_completions
18 from prompt_toolkit.filters import Condition, emacs_insert_mode, has_completions
19 from prompt_toolkit.filters import has_focus as has_focus_impl
19 from prompt_toolkit.filters import has_focus as has_focus_impl
20 from prompt_toolkit.filters import (
20 from prompt_toolkit.filters import (
21 has_selection,
21 has_selection,
22 has_suggestion,
22 has_suggestion,
23 vi_insert_mode,
23 vi_insert_mode,
24 vi_mode,
24 vi_mode,
25 )
25 )
26 from prompt_toolkit.key_binding import KeyBindings
26 from prompt_toolkit.key_binding import KeyBindings
27 from prompt_toolkit.key_binding.bindings import named_commands as nc
27 from prompt_toolkit.key_binding.bindings import named_commands as nc
28 from prompt_toolkit.key_binding.bindings.completion import (
28 from prompt_toolkit.key_binding.bindings.completion import (
29 display_completions_like_readline,
29 display_completions_like_readline,
30 )
30 )
31 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
31 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
32 from prompt_toolkit.layout.layout import FocusableElement
32 from prompt_toolkit.layout.layout import FocusableElement
33
33
34 from IPython.terminal.shortcuts import auto_match as match
34 from IPython.terminal.shortcuts import auto_match as match
35 from IPython.terminal.shortcuts import auto_suggest
35 from IPython.terminal.shortcuts import auto_suggest
36 from IPython.utils.decorators import undoc
36 from IPython.utils.decorators import undoc
37
37
38 __all__ = ["create_ipython_shortcuts"]
38 __all__ = ["create_ipython_shortcuts"]
39
39
40
40
41 @undoc
41 @undoc
42 @Condition
42 @Condition
43 def cursor_in_leading_ws():
43 def cursor_in_leading_ws():
44 before = get_app().current_buffer.document.current_line_before_cursor
44 before = get_app().current_buffer.document.current_line_before_cursor
45 return (not before) or before.isspace()
45 return (not before) or before.isspace()
46
46
47
47
48 def has_focus(value: FocusableElement):
48 def has_focus(value: FocusableElement):
49 """Wrapper around has_focus adding a nice `__name__` to tester function"""
49 """Wrapper around has_focus adding a nice `__name__` to tester function"""
50 tester = has_focus_impl(value).func
50 tester = has_focus_impl(value).func
51 tester.__name__ = f"is_focused({value})"
51 tester.__name__ = f"is_focused({value})"
52 return Condition(tester)
52 return Condition(tester)
53
53
54
54
55 @undoc
55 @Condition
56 @Condition
56 def has_line_below() -> bool:
57 def has_line_below() -> bool:
57 document = get_app().current_buffer.document
58 document = get_app().current_buffer.document
58 return document.cursor_position_row < len(document.lines) - 1
59 return document.cursor_position_row < len(document.lines) - 1
59
60
60
61
62 @undoc
61 @Condition
63 @Condition
62 def has_line_above() -> bool:
64 def has_line_above() -> bool:
63 document = get_app().current_buffer.document
65 document = get_app().current_buffer.document
64 return document.cursor_position_row != 0
66 return document.cursor_position_row != 0
65
67
66
68
67 def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings:
69 def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings:
68 """Set up the prompt_toolkit keyboard shortcuts for IPython.
70 """Set up the prompt_toolkit keyboard shortcuts for IPython.
69
71
70 Parameters
72 Parameters
71 ----------
73 ----------
72 shell: InteractiveShell
74 shell: InteractiveShell
73 The current IPython shell Instance
75 The current IPython shell Instance
74 for_all_platforms: bool (default false)
76 for_all_platforms: bool (default false)
75 This parameter is mostly used in generating the documentation
77 This parameter is mostly used in generating the documentation
76 to create the shortcut binding for all the platforms, and export
78 to create the shortcut binding for all the platforms, and export
77 them.
79 them.
78
80
79 Returns
81 Returns
80 -------
82 -------
81 KeyBindings
83 KeyBindings
82 the keybinding instance for prompt toolkit.
84 the keybinding instance for prompt toolkit.
83
85
84 """
86 """
85 # Warning: if possible, do NOT define handler functions in the locals
87 # Warning: if possible, do NOT define handler functions in the locals
86 # scope of this function, instead define functions in the global
88 # scope of this function, instead define functions in the global
87 # scope, or a separate module, and include a user-friendly docstring
89 # scope, or a separate module, and include a user-friendly docstring
88 # describing the action.
90 # describing the action.
89
91
90 kb = KeyBindings()
92 kb = KeyBindings()
91 insert_mode = vi_insert_mode | emacs_insert_mode
93 insert_mode = vi_insert_mode | emacs_insert_mode
92
94
93 if getattr(shell, "handle_return", None):
95 if getattr(shell, "handle_return", None):
94 return_handler = shell.handle_return(shell)
96 return_handler = shell.handle_return(shell)
95 else:
97 else:
96 return_handler = newline_or_execute_outer(shell)
98 return_handler = newline_or_execute_outer(shell)
97
99
98 kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode))(
100 kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode))(
99 return_handler
101 return_handler
100 )
102 )
101
103
102 @Condition
104 @Condition
103 def ebivim():
105 def ebivim():
104 return shell.emacs_bindings_in_vi_insert_mode
106 return shell.emacs_bindings_in_vi_insert_mode
105
107
106 @kb.add(
108 @kb.add(
107 "escape",
109 "escape",
108 "enter",
110 "enter",
109 filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode & ebivim),
111 filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode & ebivim),
110 )
112 )
111 def reformat_and_execute(event):
113 def reformat_and_execute(event):
112 """Reformat code and execute it"""
114 """Reformat code and execute it"""
113 reformat_text_before_cursor(
115 reformat_text_before_cursor(
114 event.current_buffer, event.current_buffer.document, shell
116 event.current_buffer, event.current_buffer.document, shell
115 )
117 )
116 event.current_buffer.validate_and_handle()
118 event.current_buffer.validate_and_handle()
117
119
118 kb.add("c-\\")(quit)
120 kb.add("c-\\")(quit)
119
121
120 kb.add("c-p", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
122 kb.add("c-p", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
121 previous_history_or_previous_completion
123 previous_history_or_previous_completion
122 )
124 )
123
125
124 kb.add("c-n", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
126 kb.add("c-n", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
125 next_history_or_next_completion
127 next_history_or_next_completion
126 )
128 )
127
129
128 kb.add("c-g", filter=(has_focus(DEFAULT_BUFFER) & has_completions))(
130 kb.add("c-g", filter=(has_focus(DEFAULT_BUFFER) & has_completions))(
129 dismiss_completion
131 dismiss_completion
130 )
132 )
131
133
132 kb.add("c-c", filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
134 kb.add("c-c", filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
133
135
134 kb.add("c-c", filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
136 kb.add("c-c", filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
135
137
136 supports_suspend = Condition(lambda: hasattr(signal, "SIGTSTP"))
138 supports_suspend = Condition(lambda: hasattr(signal, "SIGTSTP"))
137 kb.add("c-z", filter=supports_suspend)(suspend_to_bg)
139 kb.add("c-z", filter=supports_suspend)(suspend_to_bg)
138
140
139 # Ctrl+I == Tab
141 # Ctrl+I == Tab
140 kb.add(
142 kb.add(
141 "tab",
143 "tab",
142 filter=(
144 filter=(
143 has_focus(DEFAULT_BUFFER)
145 has_focus(DEFAULT_BUFFER)
144 & ~has_selection
146 & ~has_selection
145 & insert_mode
147 & insert_mode
146 & cursor_in_leading_ws
148 & cursor_in_leading_ws
147 ),
149 ),
148 )(indent_buffer)
150 )(indent_buffer)
149 kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode))(
151 kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode))(
150 newline_autoindent_outer(shell.input_transformer_manager)
152 newline_autoindent_outer(shell.input_transformer_manager)
151 )
153 )
152
154
153 kb.add("f2", filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
155 kb.add("f2", filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
154
156
155 @Condition
157 @Condition
156 def auto_match():
158 def auto_match():
157 return shell.auto_match
159 return shell.auto_match
158
160
159 def all_quotes_paired(quote, buf):
161 def all_quotes_paired(quote, buf):
160 paired = True
162 paired = True
161 i = 0
163 i = 0
162 while i < len(buf):
164 while i < len(buf):
163 c = buf[i]
165 c = buf[i]
164 if c == quote:
166 if c == quote:
165 paired = not paired
167 paired = not paired
166 elif c == "\\":
168 elif c == "\\":
167 i += 1
169 i += 1
168 i += 1
170 i += 1
169 return paired
171 return paired
170
172
171 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
173 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
172 _preceding_text_cache: Dict[Union[str, Callable], Condition] = {}
174 _preceding_text_cache: Dict[Union[str, Callable], Condition] = {}
173 _following_text_cache: Dict[Union[str, Callable], Condition] = {}
175 _following_text_cache: Dict[Union[str, Callable], Condition] = {}
174
176
175 def preceding_text(pattern: Union[str, Callable]):
177 def preceding_text(pattern: Union[str, Callable]):
176 if pattern in _preceding_text_cache:
178 if pattern in _preceding_text_cache:
177 return _preceding_text_cache[pattern]
179 return _preceding_text_cache[pattern]
178
180
179 if callable(pattern):
181 if callable(pattern):
180
182
181 def _preceding_text():
183 def _preceding_text():
182 app = get_app()
184 app = get_app()
183 before_cursor = app.current_buffer.document.current_line_before_cursor
185 before_cursor = app.current_buffer.document.current_line_before_cursor
184 # mypy can't infer if(callable): https://github.com/python/mypy/issues/3603
186 # mypy can't infer if(callable): https://github.com/python/mypy/issues/3603
185 return bool(pattern(before_cursor)) # type: ignore[operator]
187 return bool(pattern(before_cursor)) # type: ignore[operator]
186
188
187 else:
189 else:
188 m = re.compile(pattern)
190 m = re.compile(pattern)
189
191
190 def _preceding_text():
192 def _preceding_text():
191 app = get_app()
193 app = get_app()
192 before_cursor = app.current_buffer.document.current_line_before_cursor
194 before_cursor = app.current_buffer.document.current_line_before_cursor
193 return bool(m.match(before_cursor))
195 return bool(m.match(before_cursor))
194
196
195 _preceding_text.__name__ = f"preceding_text({pattern!r})"
197 _preceding_text.__name__ = f"preceding_text({pattern!r})"
196
198
197 condition = Condition(_preceding_text)
199 condition = Condition(_preceding_text)
198 _preceding_text_cache[pattern] = condition
200 _preceding_text_cache[pattern] = condition
199 return condition
201 return condition
200
202
201 def following_text(pattern):
203 def following_text(pattern):
202 try:
204 try:
203 return _following_text_cache[pattern]
205 return _following_text_cache[pattern]
204 except KeyError:
206 except KeyError:
205 pass
207 pass
206 m = re.compile(pattern)
208 m = re.compile(pattern)
207
209
208 def _following_text():
210 def _following_text():
209 app = get_app()
211 app = get_app()
210 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
212 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
211
213
212 _following_text.__name__ = f"following_text({pattern!r})"
214 _following_text.__name__ = f"following_text({pattern!r})"
213
215
214 condition = Condition(_following_text)
216 condition = Condition(_following_text)
215 _following_text_cache[pattern] = condition
217 _following_text_cache[pattern] = condition
216 return condition
218 return condition
217
219
218 @Condition
220 @Condition
219 def not_inside_unclosed_string():
221 def not_inside_unclosed_string():
220 app = get_app()
222 app = get_app()
221 s = app.current_buffer.document.text_before_cursor
223 s = app.current_buffer.document.text_before_cursor
222 # remove escaped quotes
224 # remove escaped quotes
223 s = s.replace('\\"', "").replace("\\'", "")
225 s = s.replace('\\"', "").replace("\\'", "")
224 # remove triple-quoted string literals
226 # remove triple-quoted string literals
225 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
227 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
226 # remove single-quoted string literals
228 # remove single-quoted string literals
227 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
229 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
228 return not ('"' in s or "'" in s)
230 return not ('"' in s or "'" in s)
229
231
230 # auto match
232 # auto match
231 for key, cmd in match.auto_match_parens.items():
233 for key, cmd in match.auto_match_parens.items():
232 kb.add(key, filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))(
234 kb.add(key, filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))(
233 cmd
235 cmd
234 )
236 )
235
237
236 # raw string
238 # raw string
237 for key, cmd in match.auto_match_parens_raw_string.items():
239 for key, cmd in match.auto_match_parens_raw_string.items():
238 kb.add(
240 kb.add(
239 key,
241 key,
240 filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$"),
242 filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$"),
241 )(cmd)
243 )(cmd)
242
244
243 kb.add(
245 kb.add(
244 '"',
246 '"',
245 filter=focused_insert
247 filter=focused_insert
246 & auto_match
248 & auto_match
247 & not_inside_unclosed_string
249 & not_inside_unclosed_string
248 & preceding_text(lambda line: all_quotes_paired('"', line))
250 & preceding_text(lambda line: all_quotes_paired('"', line))
249 & following_text(r"[,)}\]]|$"),
251 & following_text(r"[,)}\]]|$"),
250 )(match.double_quote)
252 )(match.double_quote)
251
253
252 kb.add(
254 kb.add(
253 "'",
255 "'",
254 filter=focused_insert
256 filter=focused_insert
255 & auto_match
257 & auto_match
256 & not_inside_unclosed_string
258 & not_inside_unclosed_string
257 & preceding_text(lambda line: all_quotes_paired("'", line))
259 & preceding_text(lambda line: all_quotes_paired("'", line))
258 & following_text(r"[,)}\]]|$"),
260 & following_text(r"[,)}\]]|$"),
259 )(match.single_quote)
261 )(match.single_quote)
260
262
261 kb.add(
263 kb.add(
262 '"',
264 '"',
263 filter=focused_insert
265 filter=focused_insert
264 & auto_match
266 & auto_match
265 & not_inside_unclosed_string
267 & not_inside_unclosed_string
266 & preceding_text(r'^.*""$'),
268 & preceding_text(r'^.*""$'),
267 )(match.docstring_double_quotes)
269 )(match.docstring_double_quotes)
268
270
269 kb.add(
271 kb.add(
270 "'",
272 "'",
271 filter=focused_insert
273 filter=focused_insert
272 & auto_match
274 & auto_match
273 & not_inside_unclosed_string
275 & not_inside_unclosed_string
274 & preceding_text(r"^.*''$"),
276 & preceding_text(r"^.*''$"),
275 )(match.docstring_single_quotes)
277 )(match.docstring_single_quotes)
276
278
277 # just move cursor
279 # just move cursor
278 kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))(
280 kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))(
279 match.skip_over
281 match.skip_over
280 )
282 )
281 kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))(
283 kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))(
282 match.skip_over
284 match.skip_over
283 )
285 )
284 kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))(
286 kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))(
285 match.skip_over
287 match.skip_over
286 )
288 )
287 kb.add('"', filter=focused_insert & auto_match & following_text('^"'))(
289 kb.add('"', filter=focused_insert & auto_match & following_text('^"'))(
288 match.skip_over
290 match.skip_over
289 )
291 )
290 kb.add("'", filter=focused_insert & auto_match & following_text("^'"))(
292 kb.add("'", filter=focused_insert & auto_match & following_text("^'"))(
291 match.skip_over
293 match.skip_over
292 )
294 )
293
295
294 kb.add(
296 kb.add(
295 "backspace",
297 "backspace",
296 filter=focused_insert
298 filter=focused_insert
297 & preceding_text(r".*\($")
299 & preceding_text(r".*\($")
298 & auto_match
300 & auto_match
299 & following_text(r"^\)"),
301 & following_text(r"^\)"),
300 )(match.delete_pair)
302 )(match.delete_pair)
301 kb.add(
303 kb.add(
302 "backspace",
304 "backspace",
303 filter=focused_insert
305 filter=focused_insert
304 & preceding_text(r".*\[$")
306 & preceding_text(r".*\[$")
305 & auto_match
307 & auto_match
306 & following_text(r"^\]"),
308 & following_text(r"^\]"),
307 )(match.delete_pair)
309 )(match.delete_pair)
308 kb.add(
310 kb.add(
309 "backspace",
311 "backspace",
310 filter=focused_insert
312 filter=focused_insert
311 & preceding_text(r".*\{$")
313 & preceding_text(r".*\{$")
312 & auto_match
314 & auto_match
313 & following_text(r"^\}"),
315 & following_text(r"^\}"),
314 )(match.delete_pair)
316 )(match.delete_pair)
315 kb.add(
317 kb.add(
316 "backspace",
318 "backspace",
317 filter=focused_insert
319 filter=focused_insert
318 & preceding_text('.*"$')
320 & preceding_text('.*"$')
319 & auto_match
321 & auto_match
320 & following_text('^"'),
322 & following_text('^"'),
321 )(match.delete_pair)
323 )(match.delete_pair)
322 kb.add(
324 kb.add(
323 "backspace",
325 "backspace",
324 filter=focused_insert
326 filter=focused_insert
325 & preceding_text(r".*'$")
327 & preceding_text(r".*'$")
326 & auto_match
328 & auto_match
327 & following_text(r"^'"),
329 & following_text(r"^'"),
328 )(match.delete_pair)
330 )(match.delete_pair)
329
331
330 if shell.display_completions == "readlinelike":
332 if shell.display_completions == "readlinelike":
331 kb.add(
333 kb.add(
332 "c-i",
334 "c-i",
333 filter=(
335 filter=(
334 has_focus(DEFAULT_BUFFER)
336 has_focus(DEFAULT_BUFFER)
335 & ~has_selection
337 & ~has_selection
336 & insert_mode
338 & insert_mode
337 & ~cursor_in_leading_ws
339 & ~cursor_in_leading_ws
338 ),
340 ),
339 )(display_completions_like_readline)
341 )(display_completions_like_readline)
340
342
341 if sys.platform == "win32" or for_all_platforms:
343 if sys.platform == "win32" or for_all_platforms:
342 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
344 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
343
345
344 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
346 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
345
347
346 # autosuggestions
348 # autosuggestions
347 @Condition
349 @Condition
348 def navigable_suggestions():
350 def navigable_suggestions():
349 return isinstance(
351 return isinstance(
350 shell.auto_suggest, auto_suggest.NavigableAutoSuggestFromHistory
352 shell.auto_suggest, auto_suggest.NavigableAutoSuggestFromHistory
351 )
353 )
352
354
353 kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))(
355 kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))(
354 auto_suggest.accept_in_vi_insert_mode
356 auto_suggest.accept_in_vi_insert_mode
355 )
357 )
356 kb.add("c-e", filter=focused_insert_vi & ebivim)(
358 kb.add("c-e", filter=focused_insert_vi & ebivim)(
357 auto_suggest.accept_in_vi_insert_mode
359 auto_suggest.accept_in_vi_insert_mode
358 )
360 )
359 kb.add("c-f", filter=focused_insert_vi)(auto_suggest.accept)
361 kb.add("c-f", filter=focused_insert_vi)(auto_suggest.accept)
360 kb.add("escape", "f", filter=focused_insert_vi & ebivim)(auto_suggest.accept_word)
362 kb.add("escape", "f", filter=focused_insert_vi & ebivim)(auto_suggest.accept_word)
361 kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
363 kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
362 auto_suggest.accept_token
364 auto_suggest.accept_token
363 )
365 )
364 kb.add("escape", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
366 kb.add("escape", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
365 auto_suggest.discard
367 auto_suggest.discard
366 )
368 )
367 kb.add(
369 kb.add(
368 "up",
370 "up",
369 filter=navigable_suggestions
371 filter=navigable_suggestions
370 & ~has_line_above
372 & ~has_line_above
371 & has_suggestion
373 & has_suggestion
372 & has_focus(DEFAULT_BUFFER),
374 & has_focus(DEFAULT_BUFFER),
373 )(auto_suggest.swap_autosuggestion_up(shell.auto_suggest))
375 )(auto_suggest.swap_autosuggestion_up(shell.auto_suggest))
374 kb.add(
376 kb.add(
375 "down",
377 "down",
376 filter=navigable_suggestions
378 filter=navigable_suggestions
377 & ~has_line_below
379 & ~has_line_below
378 & has_suggestion
380 & has_suggestion
379 & has_focus(DEFAULT_BUFFER),
381 & has_focus(DEFAULT_BUFFER),
380 )(auto_suggest.swap_autosuggestion_down(shell.auto_suggest))
382 )(auto_suggest.swap_autosuggestion_down(shell.auto_suggest))
381 kb.add("up", filter=navigable_suggestions & has_focus(DEFAULT_BUFFER))(
383 kb.add(
382 auto_suggest.up_and_update_hint
384 "up", filter=has_line_above & navigable_suggestions & has_focus(DEFAULT_BUFFER)
383 )
385 )(auto_suggest.up_and_update_hint)
384 kb.add("down", filter=navigable_suggestions & has_focus(DEFAULT_BUFFER))(
386 kb.add(
385 auto_suggest.down_and_update_hint
387 "down",
386 )
388 filter=has_line_below & navigable_suggestions & has_focus(DEFAULT_BUFFER),
389 )(auto_suggest.down_and_update_hint)
387 kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
390 kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
388 auto_suggest.accept_character
391 auto_suggest.accept_character
389 )
392 )
390 kb.add("c-left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
393 kb.add("c-left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
391 auto_suggest.accept_and_move_cursor_left
394 auto_suggest.accept_and_move_cursor_left
392 )
395 )
393 kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
396 kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
394 auto_suggest.accept_and_keep_cursor
397 auto_suggest.accept_and_keep_cursor
395 )
398 )
396 kb.add("backspace", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
399 kb.add("backspace", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
397 auto_suggest.backspace_and_resume_hint
400 auto_suggest.backspace_and_resume_hint
398 )
401 )
399
402
400 # Simple Control keybindings
403 # Simple Control keybindings
401 key_cmd_dict = {
404 key_cmd_dict = {
402 "c-a": nc.beginning_of_line,
405 "c-a": nc.beginning_of_line,
403 "c-b": nc.backward_char,
406 "c-b": nc.backward_char,
404 "c-k": nc.kill_line,
407 "c-k": nc.kill_line,
405 "c-w": nc.backward_kill_word,
408 "c-w": nc.backward_kill_word,
406 "c-y": nc.yank,
409 "c-y": nc.yank,
407 "c-_": nc.undo,
410 "c-_": nc.undo,
408 }
411 }
409
412
410 for key, cmd in key_cmd_dict.items():
413 for key, cmd in key_cmd_dict.items():
411 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
414 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
412
415
413 # Alt and Combo Control keybindings
416 # Alt and Combo Control keybindings
414 keys_cmd_dict = {
417 keys_cmd_dict = {
415 # Control Combos
418 # Control Combos
416 ("c-x", "c-e"): nc.edit_and_execute,
419 ("c-x", "c-e"): nc.edit_and_execute,
417 ("c-x", "e"): nc.edit_and_execute,
420 ("c-x", "e"): nc.edit_and_execute,
418 # Alt
421 # Alt
419 ("escape", "b"): nc.backward_word,
422 ("escape", "b"): nc.backward_word,
420 ("escape", "c"): nc.capitalize_word,
423 ("escape", "c"): nc.capitalize_word,
421 ("escape", "d"): nc.kill_word,
424 ("escape", "d"): nc.kill_word,
422 ("escape", "h"): nc.backward_kill_word,
425 ("escape", "h"): nc.backward_kill_word,
423 ("escape", "l"): nc.downcase_word,
426 ("escape", "l"): nc.downcase_word,
424 ("escape", "u"): nc.uppercase_word,
427 ("escape", "u"): nc.uppercase_word,
425 ("escape", "y"): nc.yank_pop,
428 ("escape", "y"): nc.yank_pop,
426 ("escape", "."): nc.yank_last_arg,
429 ("escape", "."): nc.yank_last_arg,
427 }
430 }
428
431
429 for keys, cmd in keys_cmd_dict.items():
432 for keys, cmd in keys_cmd_dict.items():
430 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
433 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
431
434
432 def get_input_mode(self):
435 def get_input_mode(self):
433 app = get_app()
436 app = get_app()
434 app.ttimeoutlen = shell.ttimeoutlen
437 app.ttimeoutlen = shell.ttimeoutlen
435 app.timeoutlen = shell.timeoutlen
438 app.timeoutlen = shell.timeoutlen
436
439
437 return self._input_mode
440 return self._input_mode
438
441
439 def set_input_mode(self, mode):
442 def set_input_mode(self, mode):
440 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
443 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
441 cursor = "\x1b[{} q".format(shape)
444 cursor = "\x1b[{} q".format(shape)
442
445
443 sys.stdout.write(cursor)
446 sys.stdout.write(cursor)
444 sys.stdout.flush()
447 sys.stdout.flush()
445
448
446 self._input_mode = mode
449 self._input_mode = mode
447
450
448 if shell.editing_mode == "vi" and shell.modal_cursor:
451 if shell.editing_mode == "vi" and shell.modal_cursor:
449 ViState._input_mode = InputMode.INSERT # type: ignore
452 ViState._input_mode = InputMode.INSERT # type: ignore
450 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
453 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
451
454
452 return kb
455 return kb
453
456
454
457
455 def reformat_text_before_cursor(buffer, document, shell):
458 def reformat_text_before_cursor(buffer, document, shell):
456 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
459 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
457 try:
460 try:
458 formatted_text = shell.reformat_handler(text)
461 formatted_text = shell.reformat_handler(text)
459 buffer.insert_text(formatted_text)
462 buffer.insert_text(formatted_text)
460 except Exception as e:
463 except Exception as e:
461 buffer.insert_text(text)
464 buffer.insert_text(text)
462
465
463
466
464 def newline_or_execute_outer(shell):
467 def newline_or_execute_outer(shell):
465 def newline_or_execute(event):
468 def newline_or_execute(event):
466 """When the user presses return, insert a newline or execute the code."""
469 """When the user presses return, insert a newline or execute the code."""
467 b = event.current_buffer
470 b = event.current_buffer
468 d = b.document
471 d = b.document
469
472
470 if b.complete_state:
473 if b.complete_state:
471 cc = b.complete_state.current_completion
474 cc = b.complete_state.current_completion
472 if cc:
475 if cc:
473 b.apply_completion(cc)
476 b.apply_completion(cc)
474 else:
477 else:
475 b.cancel_completion()
478 b.cancel_completion()
476 return
479 return
477
480
478 # If there's only one line, treat it as if the cursor is at the end.
481 # If there's only one line, treat it as if the cursor is at the end.
479 # See https://github.com/ipython/ipython/issues/10425
482 # See https://github.com/ipython/ipython/issues/10425
480 if d.line_count == 1:
483 if d.line_count == 1:
481 check_text = d.text
484 check_text = d.text
482 else:
485 else:
483 check_text = d.text[: d.cursor_position]
486 check_text = d.text[: d.cursor_position]
484 status, indent = shell.check_complete(check_text)
487 status, indent = shell.check_complete(check_text)
485
488
486 # if all we have after the cursor is whitespace: reformat current text
489 # if all we have after the cursor is whitespace: reformat current text
487 # before cursor
490 # before cursor
488 after_cursor = d.text[d.cursor_position :]
491 after_cursor = d.text[d.cursor_position :]
489 reformatted = False
492 reformatted = False
490 if not after_cursor.strip():
493 if not after_cursor.strip():
491 reformat_text_before_cursor(b, d, shell)
494 reformat_text_before_cursor(b, d, shell)
492 reformatted = True
495 reformatted = True
493 if not (
496 if not (
494 d.on_last_line
497 d.on_last_line
495 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
498 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
496 ):
499 ):
497 if shell.autoindent:
500 if shell.autoindent:
498 b.insert_text("\n" + indent)
501 b.insert_text("\n" + indent)
499 else:
502 else:
500 b.insert_text("\n")
503 b.insert_text("\n")
501 return
504 return
502
505
503 if (status != "incomplete") and b.accept_handler:
506 if (status != "incomplete") and b.accept_handler:
504 if not reformatted:
507 if not reformatted:
505 reformat_text_before_cursor(b, d, shell)
508 reformat_text_before_cursor(b, d, shell)
506 b.validate_and_handle()
509 b.validate_and_handle()
507 else:
510 else:
508 if shell.autoindent:
511 if shell.autoindent:
509 b.insert_text("\n" + indent)
512 b.insert_text("\n" + indent)
510 else:
513 else:
511 b.insert_text("\n")
514 b.insert_text("\n")
512
515
513 newline_or_execute.__qualname__ = "newline_or_execute"
516 newline_or_execute.__qualname__ = "newline_or_execute"
514
517
515 return newline_or_execute
518 return newline_or_execute
516
519
517
520
518 def previous_history_or_previous_completion(event):
521 def previous_history_or_previous_completion(event):
519 """
522 """
520 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
523 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
521
524
522 If completer is open this still select previous completion.
525 If completer is open this still select previous completion.
523 """
526 """
524 event.current_buffer.auto_up()
527 event.current_buffer.auto_up()
525
528
526
529
527 def next_history_or_next_completion(event):
530 def next_history_or_next_completion(event):
528 """
531 """
529 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
532 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
530
533
531 If completer is open this still select next completion.
534 If completer is open this still select next completion.
532 """
535 """
533 event.current_buffer.auto_down()
536 event.current_buffer.auto_down()
534
537
535
538
536 def dismiss_completion(event):
539 def dismiss_completion(event):
537 """Dismiss completion"""
540 """Dismiss completion"""
538 b = event.current_buffer
541 b = event.current_buffer
539 if b.complete_state:
542 if b.complete_state:
540 b.cancel_completion()
543 b.cancel_completion()
541
544
542
545
543 def reset_buffer(event):
546 def reset_buffer(event):
544 """Reset buffer"""
547 """Reset buffer"""
545 b = event.current_buffer
548 b = event.current_buffer
546 if b.complete_state:
549 if b.complete_state:
547 b.cancel_completion()
550 b.cancel_completion()
548 else:
551 else:
549 b.reset()
552 b.reset()
550
553
551
554
552 def reset_search_buffer(event):
555 def reset_search_buffer(event):
553 """Reset search buffer"""
556 """Reset search buffer"""
554 if event.current_buffer.document.text:
557 if event.current_buffer.document.text:
555 event.current_buffer.reset()
558 event.current_buffer.reset()
556 else:
559 else:
557 event.app.layout.focus(DEFAULT_BUFFER)
560 event.app.layout.focus(DEFAULT_BUFFER)
558
561
559
562
560 def suspend_to_bg(event):
563 def suspend_to_bg(event):
561 """Suspend to background"""
564 """Suspend to background"""
562 event.app.suspend_to_background()
565 event.app.suspend_to_background()
563
566
564
567
565 def quit(event):
568 def quit(event):
566 """
569 """
567 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
570 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
568
571
569 On platforms that support SIGQUIT, send SIGQUIT to the current process.
572 On platforms that support SIGQUIT, send SIGQUIT to the current process.
570 On other platforms, just exit the process with a message.
573 On other platforms, just exit the process with a message.
571 """
574 """
572 sigquit = getattr(signal, "SIGQUIT", None)
575 sigquit = getattr(signal, "SIGQUIT", None)
573 if sigquit is not None:
576 if sigquit is not None:
574 os.kill(0, signal.SIGQUIT)
577 os.kill(0, signal.SIGQUIT)
575 else:
578 else:
576 sys.exit("Quit")
579 sys.exit("Quit")
577
580
578
581
579 def indent_buffer(event):
582 def indent_buffer(event):
580 """Indent buffer"""
583 """Indent buffer"""
581 event.current_buffer.insert_text(" " * 4)
584 event.current_buffer.insert_text(" " * 4)
582
585
583
586
584 @undoc
587 @undoc
585 def newline_with_copy_margin(event):
588 def newline_with_copy_margin(event):
586 """
589 """
587 DEPRECATED since IPython 6.0
590 DEPRECATED since IPython 6.0
588
591
589 See :any:`newline_autoindent_outer` for a replacement.
592 See :any:`newline_autoindent_outer` for a replacement.
590
593
591 Preserve margin and cursor position when using
594 Preserve margin and cursor position when using
592 Control-O to insert a newline in EMACS mode
595 Control-O to insert a newline in EMACS mode
593 """
596 """
594 warnings.warn(
597 warnings.warn(
595 "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
598 "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
596 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
599 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
597 DeprecationWarning,
600 DeprecationWarning,
598 stacklevel=2,
601 stacklevel=2,
599 )
602 )
600
603
601 b = event.current_buffer
604 b = event.current_buffer
602 cursor_start_pos = b.document.cursor_position_col
605 cursor_start_pos = b.document.cursor_position_col
603 b.newline(copy_margin=True)
606 b.newline(copy_margin=True)
604 b.cursor_up(count=1)
607 b.cursor_up(count=1)
605 cursor_end_pos = b.document.cursor_position_col
608 cursor_end_pos = b.document.cursor_position_col
606 if cursor_start_pos != cursor_end_pos:
609 if cursor_start_pos != cursor_end_pos:
607 pos_diff = cursor_start_pos - cursor_end_pos
610 pos_diff = cursor_start_pos - cursor_end_pos
608 b.cursor_right(count=pos_diff)
611 b.cursor_right(count=pos_diff)
609
612
610
613
611 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
614 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
612 """
615 """
613 Return a function suitable for inserting a indented newline after the cursor.
616 Return a function suitable for inserting a indented newline after the cursor.
614
617
615 Fancier version of deprecated ``newline_with_copy_margin`` which should
618 Fancier version of deprecated ``newline_with_copy_margin`` which should
616 compute the correct indentation of the inserted line. That is to say, indent
619 compute the correct indentation of the inserted line. That is to say, indent
617 by 4 extra space after a function definition, class definition, context
620 by 4 extra space after a function definition, class definition, context
618 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
621 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
619 """
622 """
620
623
621 def newline_autoindent(event):
624 def newline_autoindent(event):
622 """Insert a newline after the cursor indented appropriately."""
625 """Insert a newline after the cursor indented appropriately."""
623 b = event.current_buffer
626 b = event.current_buffer
624 d = b.document
627 d = b.document
625
628
626 if b.complete_state:
629 if b.complete_state:
627 b.cancel_completion()
630 b.cancel_completion()
628 text = d.text[: d.cursor_position] + "\n"
631 text = d.text[: d.cursor_position] + "\n"
629 _, indent = inputsplitter.check_complete(text)
632 _, indent = inputsplitter.check_complete(text)
630 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
633 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
631
634
632 newline_autoindent.__qualname__ = "newline_autoindent"
635 newline_autoindent.__qualname__ = "newline_autoindent"
633
636
634 return newline_autoindent
637 return newline_autoindent
635
638
636
639
637 def open_input_in_editor(event):
640 def open_input_in_editor(event):
638 """Open code from input in external editor"""
641 """Open code from input in external editor"""
639 event.app.current_buffer.open_in_editor()
642 event.app.current_buffer.open_in_editor()
640
643
641
644
642 if sys.platform == "win32":
645 if sys.platform == "win32":
643 from IPython.core.error import TryNext
646 from IPython.core.error import TryNext
644 from IPython.lib.clipboard import (
647 from IPython.lib.clipboard import (
645 ClipboardEmpty,
648 ClipboardEmpty,
646 tkinter_clipboard_get,
649 tkinter_clipboard_get,
647 win32_clipboard_get,
650 win32_clipboard_get,
648 )
651 )
649
652
650 @undoc
653 @undoc
651 def win_paste(event):
654 def win_paste(event):
652 try:
655 try:
653 text = win32_clipboard_get()
656 text = win32_clipboard_get()
654 except TryNext:
657 except TryNext:
655 try:
658 try:
656 text = tkinter_clipboard_get()
659 text = tkinter_clipboard_get()
657 except (TryNext, ClipboardEmpty):
660 except (TryNext, ClipboardEmpty):
658 return
661 return
659 except ClipboardEmpty:
662 except ClipboardEmpty:
660 return
663 return
661 event.current_buffer.insert_text(text.replace("\t", " " * 4))
664 event.current_buffer.insert_text(text.replace("\t", " " * 4))
662
665
663 else:
666 else:
664
667
665 @undoc
668 @undoc
666 def win_paste(event):
669 def win_paste(event):
667 """Stub used when auto-generating shortcuts for documentation"""
670 """Stub used when auto-generating shortcuts for documentation"""
668 pass
671 pass
General Comments 0
You need to be logged in to leave comments. Login now