##// END OF EJS Templates
Misc review cleanup....
Matthias Bussonnier -
Show More
@@ -1,638 +1,640 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 warnings
9 import os
10 import re
10 import signal
11 import signal
11 import sys
12 import sys
12 import re
13 import warnings
13 import os
14 from typing import Callable, Dict, Union
14 from typing import Callable, Dict, Union
15
15
16
17 from prompt_toolkit.application.current import get_app
16 from prompt_toolkit.application.current import get_app
18 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
19 from prompt_toolkit.filters import has_focus as has_focus_impl
19 from prompt_toolkit.filters import (
20 from prompt_toolkit.filters import (
20 has_focus as has_focus_impl,
21 has_selection,
21 has_selection,
22 Condition,
22 has_suggestion,
23 vi_insert_mode,
23 vi_insert_mode,
24 emacs_insert_mode,
25 has_completions,
26 vi_mode,
24 vi_mode,
27 )
25 )
26 from prompt_toolkit.key_binding import KeyBindings
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 import KeyBindings
32 from prompt_toolkit.key_binding.bindings import named_commands as nc
33 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
31 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
34 from prompt_toolkit.layout.layout import FocusableElement
32 from prompt_toolkit.layout.layout import FocusableElement
35
33
34 from IPython.terminal.shortcuts import auto_match as match
35 from IPython.terminal.shortcuts import auto_suggest
36 from IPython.utils.decorators import undoc
36 from IPython.utils.decorators import undoc
37 from . import auto_match as match, auto_suggest
38
39
37
40 __all__ = ["create_ipython_shortcuts"]
38 __all__ = ["create_ipython_shortcuts"]
41
39
42
40
43 try:
44 # only added in 3.0.30
45 from prompt_toolkit.filters import has_suggestion
46 except ImportError:
47
48 @undoc
49 @Condition
50 def has_suggestion():
51 buffer = get_app().current_buffer
52 return buffer.suggestion is not None and buffer.suggestion.text != ""
53
54
55 @undoc
41 @undoc
56 @Condition
42 @Condition
57 def cursor_in_leading_ws():
43 def cursor_in_leading_ws():
58 before = get_app().current_buffer.document.current_line_before_cursor
44 before = get_app().current_buffer.document.current_line_before_cursor
59 return (not before) or before.isspace()
45 return (not before) or before.isspace()
60
46
61
47
62 def has_focus(value: FocusableElement):
48 def has_focus(value: FocusableElement):
63 """Wrapper around has_focus adding a nice `__name__` to tester function"""
49 """Wrapper around has_focus adding a nice `__name__` to tester function"""
64 tester = has_focus_impl(value).func
50 tester = has_focus_impl(value).func
65 tester.__name__ = f"is_focused({value})"
51 tester.__name__ = f"is_focused({value})"
66 return Condition(tester)
52 return Condition(tester)
67
53
68
54
69 def create_ipython_shortcuts(shell, for_all_platforms: bool = False):
55 def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings:
70 """Set up the prompt_toolkit keyboard shortcuts for IPython."""
56 """Set up the prompt_toolkit keyboard shortcuts for IPython.
57
58 Parameters
59 ----------
60 shell: InteractiveShell
61 The current IPython shell Instance
62 for_all_platforms: bool (default false)
63 This parameter is mostly used in generating the documentation
64 to create the shortcut binding for all the platforms, and export
65 them.
66
67 Returns
68 -------
69 KeyBindings
70 the keybinding instance for prompt toolkit.
71
72 """
71 # Warning: if possible, do NOT define handler functions in the locals
73 # Warning: if possible, do NOT define handler functions in the locals
72 # scope of this function, instead define functions in the global
74 # scope of this function, instead define functions in the global
73 # scope, or a separate module, and include a user-friendly docstring
75 # scope, or a separate module, and include a user-friendly docstring
74 # describing the action.
76 # describing the action.
75
77
76 kb = KeyBindings()
78 kb = KeyBindings()
77 insert_mode = vi_insert_mode | emacs_insert_mode
79 insert_mode = vi_insert_mode | emacs_insert_mode
78
80
79 if getattr(shell, "handle_return", None):
81 if getattr(shell, "handle_return", None):
80 return_handler = shell.handle_return(shell)
82 return_handler = shell.handle_return(shell)
81 else:
83 else:
82 return_handler = newline_or_execute_outer(shell)
84 return_handler = newline_or_execute_outer(shell)
83
85
84 kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode))(
86 kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode))(
85 return_handler
87 return_handler
86 )
88 )
87
89
88 @Condition
90 @Condition
89 def ebivim():
91 def ebivim():
90 return shell.emacs_bindings_in_vi_insert_mode
92 return shell.emacs_bindings_in_vi_insert_mode
91
93
92 @kb.add(
94 @kb.add(
93 "escape",
95 "escape",
94 "enter",
96 "enter",
95 filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode & ebivim),
97 filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode & ebivim),
96 )
98 )
97 def reformat_and_execute(event):
99 def reformat_and_execute(event):
98 """Reformat code and execute it"""
100 """Reformat code and execute it"""
99 reformat_text_before_cursor(
101 reformat_text_before_cursor(
100 event.current_buffer, event.current_buffer.document, shell
102 event.current_buffer, event.current_buffer.document, shell
101 )
103 )
102 event.current_buffer.validate_and_handle()
104 event.current_buffer.validate_and_handle()
103
105
104 kb.add("c-\\")(quit)
106 kb.add("c-\\")(quit)
105
107
106 kb.add("c-p", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
108 kb.add("c-p", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
107 previous_history_or_previous_completion
109 previous_history_or_previous_completion
108 )
110 )
109
111
110 kb.add("c-n", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
112 kb.add("c-n", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))(
111 next_history_or_next_completion
113 next_history_or_next_completion
112 )
114 )
113
115
114 kb.add("c-g", filter=(has_focus(DEFAULT_BUFFER) & has_completions))(
116 kb.add("c-g", filter=(has_focus(DEFAULT_BUFFER) & has_completions))(
115 dismiss_completion
117 dismiss_completion
116 )
118 )
117
119
118 kb.add("c-c", filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
120 kb.add("c-c", filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
119
121
120 kb.add("c-c", filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
122 kb.add("c-c", filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
121
123
122 supports_suspend = Condition(lambda: hasattr(signal, "SIGTSTP"))
124 supports_suspend = Condition(lambda: hasattr(signal, "SIGTSTP"))
123 kb.add("c-z", filter=supports_suspend)(suspend_to_bg)
125 kb.add("c-z", filter=supports_suspend)(suspend_to_bg)
124
126
125 # Ctrl+I == Tab
127 # Ctrl+I == Tab
126 kb.add(
128 kb.add(
127 "tab",
129 "tab",
128 filter=(
130 filter=(
129 has_focus(DEFAULT_BUFFER)
131 has_focus(DEFAULT_BUFFER)
130 & ~has_selection
132 & ~has_selection
131 & insert_mode
133 & insert_mode
132 & cursor_in_leading_ws
134 & cursor_in_leading_ws
133 ),
135 ),
134 )(indent_buffer)
136 )(indent_buffer)
135 kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode))(
137 kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode))(
136 newline_autoindent_outer(shell.input_transformer_manager)
138 newline_autoindent_outer(shell.input_transformer_manager)
137 )
139 )
138
140
139 kb.add("f2", filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
141 kb.add("f2", filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
140
142
141 @Condition
143 @Condition
142 def auto_match():
144 def auto_match():
143 return shell.auto_match
145 return shell.auto_match
144
146
145 def all_quotes_paired(quote, buf):
147 def all_quotes_paired(quote, buf):
146 paired = True
148 paired = True
147 i = 0
149 i = 0
148 while i < len(buf):
150 while i < len(buf):
149 c = buf[i]
151 c = buf[i]
150 if c == quote:
152 if c == quote:
151 paired = not paired
153 paired = not paired
152 elif c == "\\":
154 elif c == "\\":
153 i += 1
155 i += 1
154 i += 1
156 i += 1
155 return paired
157 return paired
156
158
157 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
159 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
158 _preceding_text_cache: Dict[Union[str, Callable], Condition] = {}
160 _preceding_text_cache: Dict[Union[str, Callable], Condition] = {}
159 _following_text_cache: Dict[Union[str, Callable], Condition] = {}
161 _following_text_cache: Dict[Union[str, Callable], Condition] = {}
160
162
161 def preceding_text(pattern: Union[str, Callable]):
163 def preceding_text(pattern: Union[str, Callable]):
162 if pattern in _preceding_text_cache:
164 if pattern in _preceding_text_cache:
163 return _preceding_text_cache[pattern]
165 return _preceding_text_cache[pattern]
164
166
165 if callable(pattern):
167 if callable(pattern):
166 def _preceding_text():
168 def _preceding_text():
167 app = get_app()
169 app = get_app()
168 before_cursor = app.current_buffer.document.current_line_before_cursor
170 before_cursor = app.current_buffer.document.current_line_before_cursor
169 # mypy can't infer if(callable): https://github.com/python/mypy/issues/3603
171 # mypy can't infer if(callable): https://github.com/python/mypy/issues/3603
170 return bool(pattern(before_cursor)) # type: ignore[operator]
172 return bool(pattern(before_cursor)) # type: ignore[operator]
171
173
172 else:
174 else:
173 m = re.compile(pattern)
175 m = re.compile(pattern)
174
176
175 def _preceding_text():
177 def _preceding_text():
176 app = get_app()
178 app = get_app()
177 before_cursor = app.current_buffer.document.current_line_before_cursor
179 before_cursor = app.current_buffer.document.current_line_before_cursor
178 return bool(m.match(before_cursor))
180 return bool(m.match(before_cursor))
179
181
180 _preceding_text.__name__ = f"preceding_text({pattern!r})"
182 _preceding_text.__name__ = f"preceding_text({pattern!r})"
181
183
182 condition = Condition(_preceding_text)
184 condition = Condition(_preceding_text)
183 _preceding_text_cache[pattern] = condition
185 _preceding_text_cache[pattern] = condition
184 return condition
186 return condition
185
187
186 def following_text(pattern):
188 def following_text(pattern):
187 try:
189 try:
188 return _following_text_cache[pattern]
190 return _following_text_cache[pattern]
189 except KeyError:
191 except KeyError:
190 pass
192 pass
191 m = re.compile(pattern)
193 m = re.compile(pattern)
192
194
193 def _following_text():
195 def _following_text():
194 app = get_app()
196 app = get_app()
195 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
197 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
196
198
197 _following_text.__name__ = f"following_text({pattern!r})"
199 _following_text.__name__ = f"following_text({pattern!r})"
198
200
199 condition = Condition(_following_text)
201 condition = Condition(_following_text)
200 _following_text_cache[pattern] = condition
202 _following_text_cache[pattern] = condition
201 return condition
203 return condition
202
204
203 @Condition
205 @Condition
204 def not_inside_unclosed_string():
206 def not_inside_unclosed_string():
205 app = get_app()
207 app = get_app()
206 s = app.current_buffer.document.text_before_cursor
208 s = app.current_buffer.document.text_before_cursor
207 # remove escaped quotes
209 # remove escaped quotes
208 s = s.replace('\\"', "").replace("\\'", "")
210 s = s.replace('\\"', "").replace("\\'", "")
209 # remove triple-quoted string literals
211 # remove triple-quoted string literals
210 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
212 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
211 # remove single-quoted string literals
213 # remove single-quoted string literals
212 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
214 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
213 return not ('"' in s or "'" in s)
215 return not ('"' in s or "'" in s)
214
216
215 # auto match
217 # auto match
216 auto_match_parens = {"(": match.parenthesis, "[": match.brackets, "{": match.braces}
218 auto_match_parens = {"(": match.parenthesis, "[": match.brackets, "{": match.braces}
217 for key, cmd in auto_match_parens.items():
219 for key, cmd in auto_match_parens.items():
218 kb.add(key, filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))(
220 kb.add(key, filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))(
219 cmd
221 cmd
220 )
222 )
221
223
222 kb.add(
224 kb.add(
223 '"',
225 '"',
224 filter=focused_insert
226 filter=focused_insert
225 & auto_match
227 & auto_match
226 & not_inside_unclosed_string
228 & not_inside_unclosed_string
227 & preceding_text(lambda line: all_quotes_paired('"', line))
229 & preceding_text(lambda line: all_quotes_paired('"', line))
228 & following_text(r"[,)}\]]|$"),
230 & following_text(r"[,)}\]]|$"),
229 )(match.double_quote)
231 )(match.double_quote)
230
232
231 kb.add(
233 kb.add(
232 "'",
234 "'",
233 filter=focused_insert
235 filter=focused_insert
234 & auto_match
236 & auto_match
235 & not_inside_unclosed_string
237 & not_inside_unclosed_string
236 & preceding_text(lambda line: all_quotes_paired("'", line))
238 & preceding_text(lambda line: all_quotes_paired("'", line))
237 & following_text(r"[,)}\]]|$"),
239 & following_text(r"[,)}\]]|$"),
238 )(match.single_quote)
240 )(match.single_quote)
239
241
240 kb.add(
242 kb.add(
241 '"',
243 '"',
242 filter=focused_insert
244 filter=focused_insert
243 & auto_match
245 & auto_match
244 & not_inside_unclosed_string
246 & not_inside_unclosed_string
245 & preceding_text(r'^.*""$'),
247 & preceding_text(r'^.*""$'),
246 )(match.docstring_double_quotes)
248 )(match.docstring_double_quotes)
247
249
248 kb.add(
250 kb.add(
249 "'",
251 "'",
250 filter=focused_insert
252 filter=focused_insert
251 & auto_match
253 & auto_match
252 & not_inside_unclosed_string
254 & not_inside_unclosed_string
253 & preceding_text(r"^.*''$"),
255 & preceding_text(r"^.*''$"),
254 )(match.docstring_single_quotes)
256 )(match.docstring_single_quotes)
255
257
256 # raw string
258 # raw string
257 auto_match_parens_raw_string = {
259 auto_match_parens_raw_string = {
258 "(": match.raw_string_parenthesis,
260 "(": match.raw_string_parenthesis,
259 "[": match.raw_string_bracket,
261 "[": match.raw_string_bracket,
260 "{": match.raw_string_braces,
262 "{": match.raw_string_braces,
261 }
263 }
262 for key, cmd in auto_match_parens_raw_string.items():
264 for key, cmd in auto_match_parens_raw_string.items():
263 kb.add(
265 kb.add(
264 key,
266 key,
265 filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$"),
267 filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$"),
266 )(cmd)
268 )(cmd)
267
269
268 # just move cursor
270 # just move cursor
269 kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))(
271 kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))(
270 match.skip_over
272 match.skip_over
271 )
273 )
272 kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))(
274 kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))(
273 match.skip_over
275 match.skip_over
274 )
276 )
275 kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))(
277 kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))(
276 match.skip_over
278 match.skip_over
277 )
279 )
278 kb.add('"', filter=focused_insert & auto_match & following_text('^"'))(
280 kb.add('"', filter=focused_insert & auto_match & following_text('^"'))(
279 match.skip_over
281 match.skip_over
280 )
282 )
281 kb.add("'", filter=focused_insert & auto_match & following_text("^'"))(
283 kb.add("'", filter=focused_insert & auto_match & following_text("^'"))(
282 match.skip_over
284 match.skip_over
283 )
285 )
284
286
285 kb.add(
287 kb.add(
286 "backspace",
288 "backspace",
287 filter=focused_insert
289 filter=focused_insert
288 & preceding_text(r".*\($")
290 & preceding_text(r".*\($")
289 & auto_match
291 & auto_match
290 & following_text(r"^\)"),
292 & following_text(r"^\)"),
291 )(match.delete_pair)
293 )(match.delete_pair)
292 kb.add(
294 kb.add(
293 "backspace",
295 "backspace",
294 filter=focused_insert
296 filter=focused_insert
295 & preceding_text(r".*\[$")
297 & preceding_text(r".*\[$")
296 & auto_match
298 & auto_match
297 & following_text(r"^\]"),
299 & following_text(r"^\]"),
298 )(match.delete_pair)
300 )(match.delete_pair)
299 kb.add(
301 kb.add(
300 "backspace",
302 "backspace",
301 filter=focused_insert
303 filter=focused_insert
302 & preceding_text(r".*\{$")
304 & preceding_text(r".*\{$")
303 & auto_match
305 & auto_match
304 & following_text(r"^\}"),
306 & following_text(r"^\}"),
305 )(match.delete_pair)
307 )(match.delete_pair)
306 kb.add(
308 kb.add(
307 "backspace",
309 "backspace",
308 filter=focused_insert
310 filter=focused_insert
309 & preceding_text('.*"$')
311 & preceding_text('.*"$')
310 & auto_match
312 & auto_match
311 & following_text('^"'),
313 & following_text('^"'),
312 )(match.delete_pair)
314 )(match.delete_pair)
313 kb.add(
315 kb.add(
314 "backspace",
316 "backspace",
315 filter=focused_insert
317 filter=focused_insert
316 & preceding_text(r".*'$")
318 & preceding_text(r".*'$")
317 & auto_match
319 & auto_match
318 & following_text(r"^'"),
320 & following_text(r"^'"),
319 )(match.delete_pair)
321 )(match.delete_pair)
320
322
321 if shell.display_completions == "readlinelike":
323 if shell.display_completions == "readlinelike":
322 kb.add(
324 kb.add(
323 "c-i",
325 "c-i",
324 filter=(
326 filter=(
325 has_focus(DEFAULT_BUFFER)
327 has_focus(DEFAULT_BUFFER)
326 & ~has_selection
328 & ~has_selection
327 & insert_mode
329 & insert_mode
328 & ~cursor_in_leading_ws
330 & ~cursor_in_leading_ws
329 ),
331 ),
330 )(display_completions_like_readline)
332 )(display_completions_like_readline)
331
333
332 if sys.platform == "win32" or for_all_platforms:
334 if sys.platform == "win32" or for_all_platforms:
333 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
335 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
334
336
335 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
337 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
336
338
337 # autosuggestions
339 # autosuggestions
338 kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))(
340 kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))(
339 auto_suggest.accept_in_vi_insert_mode
341 auto_suggest.accept_in_vi_insert_mode
340 )
342 )
341 kb.add("c-e", filter=focused_insert_vi & ebivim)(
343 kb.add("c-e", filter=focused_insert_vi & ebivim)(
342 auto_suggest.accept_in_vi_insert_mode
344 auto_suggest.accept_in_vi_insert_mode
343 )
345 )
344 kb.add("c-f", filter=focused_insert_vi)(auto_suggest.accept)
346 kb.add("c-f", filter=focused_insert_vi)(auto_suggest.accept)
345 kb.add("escape", "f", filter=focused_insert_vi & ebivim)(auto_suggest.accept_word)
347 kb.add("escape", "f", filter=focused_insert_vi & ebivim)(auto_suggest.accept_word)
346 kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
348 kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
347 auto_suggest.accept_token
349 auto_suggest.accept_token
348 )
350 )
349 from functools import partial
351 from functools import partial
350
352
351 kb.add("up", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
353 kb.add("up", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
352 auto_suggest.swap_autosuggestion_up(shell.auto_suggest)
354 auto_suggest.swap_autosuggestion_up(shell.auto_suggest)
353 )
355 )
354 kb.add("down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
356 kb.add("down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
355 auto_suggest.swap_autosuggestion_down(shell.auto_suggest)
357 auto_suggest.swap_autosuggestion_down(shell.auto_suggest)
356 )
358 )
357 kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
359 kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
358 auto_suggest.accept_character
360 auto_suggest.accept_character
359 )
361 )
360 kb.add("left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
362 kb.add("left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
361 auto_suggest.accept_and_move_cursor_left
363 auto_suggest.accept_and_move_cursor_left
362 )
364 )
363 kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
365 kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
364 auto_suggest.accept_and_keep_cursor
366 auto_suggest.accept_and_keep_cursor
365 )
367 )
366 kb.add("backspace", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
368 kb.add("backspace", filter=has_suggestion & has_focus(DEFAULT_BUFFER))(
367 auto_suggest.backspace_and_resume_hint
369 auto_suggest.backspace_and_resume_hint
368 )
370 )
369
371
370 # Simple Control keybindings
372 # Simple Control keybindings
371 key_cmd_dict = {
373 key_cmd_dict = {
372 "c-a": nc.beginning_of_line,
374 "c-a": nc.beginning_of_line,
373 "c-b": nc.backward_char,
375 "c-b": nc.backward_char,
374 "c-k": nc.kill_line,
376 "c-k": nc.kill_line,
375 "c-w": nc.backward_kill_word,
377 "c-w": nc.backward_kill_word,
376 "c-y": nc.yank,
378 "c-y": nc.yank,
377 "c-_": nc.undo,
379 "c-_": nc.undo,
378 }
380 }
379
381
380 for key, cmd in key_cmd_dict.items():
382 for key, cmd in key_cmd_dict.items():
381 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
383 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
382
384
383 # Alt and Combo Control keybindings
385 # Alt and Combo Control keybindings
384 keys_cmd_dict = {
386 keys_cmd_dict = {
385 # Control Combos
387 # Control Combos
386 ("c-x", "c-e"): nc.edit_and_execute,
388 ("c-x", "c-e"): nc.edit_and_execute,
387 ("c-x", "e"): nc.edit_and_execute,
389 ("c-x", "e"): nc.edit_and_execute,
388 # Alt
390 # Alt
389 ("escape", "b"): nc.backward_word,
391 ("escape", "b"): nc.backward_word,
390 ("escape", "c"): nc.capitalize_word,
392 ("escape", "c"): nc.capitalize_word,
391 ("escape", "d"): nc.kill_word,
393 ("escape", "d"): nc.kill_word,
392 ("escape", "h"): nc.backward_kill_word,
394 ("escape", "h"): nc.backward_kill_word,
393 ("escape", "l"): nc.downcase_word,
395 ("escape", "l"): nc.downcase_word,
394 ("escape", "u"): nc.uppercase_word,
396 ("escape", "u"): nc.uppercase_word,
395 ("escape", "y"): nc.yank_pop,
397 ("escape", "y"): nc.yank_pop,
396 ("escape", "."): nc.yank_last_arg,
398 ("escape", "."): nc.yank_last_arg,
397 }
399 }
398
400
399 for keys, cmd in keys_cmd_dict.items():
401 for keys, cmd in keys_cmd_dict.items():
400 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
402 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
401
403
402 def get_input_mode(self):
404 def get_input_mode(self):
403 app = get_app()
405 app = get_app()
404 app.ttimeoutlen = shell.ttimeoutlen
406 app.ttimeoutlen = shell.ttimeoutlen
405 app.timeoutlen = shell.timeoutlen
407 app.timeoutlen = shell.timeoutlen
406
408
407 return self._input_mode
409 return self._input_mode
408
410
409 def set_input_mode(self, mode):
411 def set_input_mode(self, mode):
410 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
412 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
411 cursor = "\x1b[{} q".format(shape)
413 cursor = "\x1b[{} q".format(shape)
412
414
413 sys.stdout.write(cursor)
415 sys.stdout.write(cursor)
414 sys.stdout.flush()
416 sys.stdout.flush()
415
417
416 self._input_mode = mode
418 self._input_mode = mode
417
419
418 if shell.editing_mode == "vi" and shell.modal_cursor:
420 if shell.editing_mode == "vi" and shell.modal_cursor:
419 ViState._input_mode = InputMode.INSERT # type: ignore
421 ViState._input_mode = InputMode.INSERT # type: ignore
420 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
422 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
421
423
422 return kb
424 return kb
423
425
424
426
425 def reformat_text_before_cursor(buffer, document, shell):
427 def reformat_text_before_cursor(buffer, document, shell):
426 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
428 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
427 try:
429 try:
428 formatted_text = shell.reformat_handler(text)
430 formatted_text = shell.reformat_handler(text)
429 buffer.insert_text(formatted_text)
431 buffer.insert_text(formatted_text)
430 except Exception as e:
432 except Exception as e:
431 buffer.insert_text(text)
433 buffer.insert_text(text)
432
434
433
435
434 def newline_or_execute_outer(shell):
436 def newline_or_execute_outer(shell):
435 def newline_or_execute(event):
437 def newline_or_execute(event):
436 """When the user presses return, insert a newline or execute the code."""
438 """When the user presses return, insert a newline or execute the code."""
437 b = event.current_buffer
439 b = event.current_buffer
438 d = b.document
440 d = b.document
439
441
440 if b.complete_state:
442 if b.complete_state:
441 cc = b.complete_state.current_completion
443 cc = b.complete_state.current_completion
442 if cc:
444 if cc:
443 b.apply_completion(cc)
445 b.apply_completion(cc)
444 else:
446 else:
445 b.cancel_completion()
447 b.cancel_completion()
446 return
448 return
447
449
448 # If there's only one line, treat it as if the cursor is at the end.
450 # If there's only one line, treat it as if the cursor is at the end.
449 # See https://github.com/ipython/ipython/issues/10425
451 # See https://github.com/ipython/ipython/issues/10425
450 if d.line_count == 1:
452 if d.line_count == 1:
451 check_text = d.text
453 check_text = d.text
452 else:
454 else:
453 check_text = d.text[: d.cursor_position]
455 check_text = d.text[: d.cursor_position]
454 status, indent = shell.check_complete(check_text)
456 status, indent = shell.check_complete(check_text)
455
457
456 # if all we have after the cursor is whitespace: reformat current text
458 # if all we have after the cursor is whitespace: reformat current text
457 # before cursor
459 # before cursor
458 after_cursor = d.text[d.cursor_position :]
460 after_cursor = d.text[d.cursor_position :]
459 reformatted = False
461 reformatted = False
460 if not after_cursor.strip():
462 if not after_cursor.strip():
461 reformat_text_before_cursor(b, d, shell)
463 reformat_text_before_cursor(b, d, shell)
462 reformatted = True
464 reformatted = True
463 if not (
465 if not (
464 d.on_last_line
466 d.on_last_line
465 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
467 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
466 ):
468 ):
467 if shell.autoindent:
469 if shell.autoindent:
468 b.insert_text("\n" + indent)
470 b.insert_text("\n" + indent)
469 else:
471 else:
470 b.insert_text("\n")
472 b.insert_text("\n")
471 return
473 return
472
474
473 if (status != "incomplete") and b.accept_handler:
475 if (status != "incomplete") and b.accept_handler:
474 if not reformatted:
476 if not reformatted:
475 reformat_text_before_cursor(b, d, shell)
477 reformat_text_before_cursor(b, d, shell)
476 b.validate_and_handle()
478 b.validate_and_handle()
477 else:
479 else:
478 if shell.autoindent:
480 if shell.autoindent:
479 b.insert_text("\n" + indent)
481 b.insert_text("\n" + indent)
480 else:
482 else:
481 b.insert_text("\n")
483 b.insert_text("\n")
482
484
483 newline_or_execute.__qualname__ = "newline_or_execute"
485 newline_or_execute.__qualname__ = "newline_or_execute"
484
486
485 return newline_or_execute
487 return newline_or_execute
486
488
487
489
488 def previous_history_or_previous_completion(event):
490 def previous_history_or_previous_completion(event):
489 """
491 """
490 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
492 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
491
493
492 If completer is open this still select previous completion.
494 If completer is open this still select previous completion.
493 """
495 """
494 event.current_buffer.auto_up()
496 event.current_buffer.auto_up()
495
497
496
498
497 def next_history_or_next_completion(event):
499 def next_history_or_next_completion(event):
498 """
500 """
499 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
501 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
500
502
501 If completer is open this still select next completion.
503 If completer is open this still select next completion.
502 """
504 """
503 event.current_buffer.auto_down()
505 event.current_buffer.auto_down()
504
506
505
507
506 def dismiss_completion(event):
508 def dismiss_completion(event):
507 """Dismiss completion"""
509 """Dismiss completion"""
508 b = event.current_buffer
510 b = event.current_buffer
509 if b.complete_state:
511 if b.complete_state:
510 b.cancel_completion()
512 b.cancel_completion()
511
513
512
514
513 def reset_buffer(event):
515 def reset_buffer(event):
514 """Reset buffer"""
516 """Reset buffer"""
515 b = event.current_buffer
517 b = event.current_buffer
516 if b.complete_state:
518 if b.complete_state:
517 b.cancel_completion()
519 b.cancel_completion()
518 else:
520 else:
519 b.reset()
521 b.reset()
520
522
521
523
522 def reset_search_buffer(event):
524 def reset_search_buffer(event):
523 """Reset search buffer"""
525 """Reset search buffer"""
524 if event.current_buffer.document.text:
526 if event.current_buffer.document.text:
525 event.current_buffer.reset()
527 event.current_buffer.reset()
526 else:
528 else:
527 event.app.layout.focus(DEFAULT_BUFFER)
529 event.app.layout.focus(DEFAULT_BUFFER)
528
530
529
531
530 def suspend_to_bg(event):
532 def suspend_to_bg(event):
531 """Suspend to background"""
533 """Suspend to background"""
532 event.app.suspend_to_background()
534 event.app.suspend_to_background()
533
535
534
536
535 def quit(event):
537 def quit(event):
536 """
538 """
537 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
539 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
538
540
539 On platforms that support SIGQUIT, send SIGQUIT to the current process.
541 On platforms that support SIGQUIT, send SIGQUIT to the current process.
540 On other platforms, just exit the process with a message.
542 On other platforms, just exit the process with a message.
541 """
543 """
542 sigquit = getattr(signal, "SIGQUIT", None)
544 sigquit = getattr(signal, "SIGQUIT", None)
543 if sigquit is not None:
545 if sigquit is not None:
544 os.kill(0, signal.SIGQUIT)
546 os.kill(0, signal.SIGQUIT)
545 else:
547 else:
546 sys.exit("Quit")
548 sys.exit("Quit")
547
549
548
550
549 def indent_buffer(event):
551 def indent_buffer(event):
550 """Indent buffer"""
552 """Indent buffer"""
551 event.current_buffer.insert_text(" " * 4)
553 event.current_buffer.insert_text(" " * 4)
552
554
553
555
554 @undoc
556 @undoc
555 def newline_with_copy_margin(event):
557 def newline_with_copy_margin(event):
556 """
558 """
557 DEPRECATED since IPython 6.0
559 DEPRECATED since IPython 6.0
558
560
559 See :any:`newline_autoindent_outer` for a replacement.
561 See :any:`newline_autoindent_outer` for a replacement.
560
562
561 Preserve margin and cursor position when using
563 Preserve margin and cursor position when using
562 Control-O to insert a newline in EMACS mode
564 Control-O to insert a newline in EMACS mode
563 """
565 """
564 warnings.warn(
566 warnings.warn(
565 "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
567 "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
566 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
568 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
567 DeprecationWarning,
569 DeprecationWarning,
568 stacklevel=2,
570 stacklevel=2,
569 )
571 )
570
572
571 b = event.current_buffer
573 b = event.current_buffer
572 cursor_start_pos = b.document.cursor_position_col
574 cursor_start_pos = b.document.cursor_position_col
573 b.newline(copy_margin=True)
575 b.newline(copy_margin=True)
574 b.cursor_up(count=1)
576 b.cursor_up(count=1)
575 cursor_end_pos = b.document.cursor_position_col
577 cursor_end_pos = b.document.cursor_position_col
576 if cursor_start_pos != cursor_end_pos:
578 if cursor_start_pos != cursor_end_pos:
577 pos_diff = cursor_start_pos - cursor_end_pos
579 pos_diff = cursor_start_pos - cursor_end_pos
578 b.cursor_right(count=pos_diff)
580 b.cursor_right(count=pos_diff)
579
581
580
582
581 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
583 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
582 """
584 """
583 Return a function suitable for inserting a indented newline after the cursor.
585 Return a function suitable for inserting a indented newline after the cursor.
584
586
585 Fancier version of deprecated ``newline_with_copy_margin`` which should
587 Fancier version of deprecated ``newline_with_copy_margin`` which should
586 compute the correct indentation of the inserted line. That is to say, indent
588 compute the correct indentation of the inserted line. That is to say, indent
587 by 4 extra space after a function definition, class definition, context
589 by 4 extra space after a function definition, class definition, context
588 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
590 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
589 """
591 """
590
592
591 def newline_autoindent(event):
593 def newline_autoindent(event):
592 """Insert a newline after the cursor indented appropriately."""
594 """Insert a newline after the cursor indented appropriately."""
593 b = event.current_buffer
595 b = event.current_buffer
594 d = b.document
596 d = b.document
595
597
596 if b.complete_state:
598 if b.complete_state:
597 b.cancel_completion()
599 b.cancel_completion()
598 text = d.text[: d.cursor_position] + "\n"
600 text = d.text[: d.cursor_position] + "\n"
599 _, indent = inputsplitter.check_complete(text)
601 _, indent = inputsplitter.check_complete(text)
600 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
602 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
601
603
602 newline_autoindent.__qualname__ = "newline_autoindent"
604 newline_autoindent.__qualname__ = "newline_autoindent"
603
605
604 return newline_autoindent
606 return newline_autoindent
605
607
606
608
607 def open_input_in_editor(event):
609 def open_input_in_editor(event):
608 """Open code from input in external editor"""
610 """Open code from input in external editor"""
609 event.app.current_buffer.open_in_editor()
611 event.app.current_buffer.open_in_editor()
610
612
611
613
612 if sys.platform == "win32":
614 if sys.platform == "win32":
613 from IPython.core.error import TryNext
615 from IPython.core.error import TryNext
614 from IPython.lib.clipboard import (
616 from IPython.lib.clipboard import (
615 ClipboardEmpty,
617 ClipboardEmpty,
616 win32_clipboard_get,
617 tkinter_clipboard_get,
618 tkinter_clipboard_get,
619 win32_clipboard_get,
618 )
620 )
619
621
620 @undoc
622 @undoc
621 def win_paste(event):
623 def win_paste(event):
622 try:
624 try:
623 text = win32_clipboard_get()
625 text = win32_clipboard_get()
624 except TryNext:
626 except TryNext:
625 try:
627 try:
626 text = tkinter_clipboard_get()
628 text = tkinter_clipboard_get()
627 except (TryNext, ClipboardEmpty):
629 except (TryNext, ClipboardEmpty):
628 return
630 return
629 except ClipboardEmpty:
631 except ClipboardEmpty:
630 return
632 return
631 event.current_buffer.insert_text(text.replace("\t", " " * 4))
633 event.current_buffer.insert_text(text.replace("\t", " " * 4))
632
634
633 else:
635 else:
634
636
635 @undoc
637 @undoc
636 def win_paste(event):
638 def win_paste(event):
637 """Stub used when auto-generating shortcuts for documentation"""
639 """Stub used when auto-generating shortcuts for documentation"""
638 pass
640 pass
@@ -1,308 +1,308 b''
1 import re
1 import re
2 import tokenize
2 import tokenize
3 from io import StringIO
3 from io import StringIO
4 from typing import Callable, List, Optional, Union, Generator, Tuple
4 from typing import Callable, List, Optional, Union, Generator, Tuple
5
5
6 from prompt_toolkit.buffer import Buffer
6 from prompt_toolkit.buffer import Buffer
7 from prompt_toolkit.key_binding import KeyPressEvent
7 from prompt_toolkit.key_binding import KeyPressEvent
8 from prompt_toolkit.key_binding.bindings import named_commands as nc
8 from prompt_toolkit.key_binding.bindings import named_commands as nc
9 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion
9 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion
10 from prompt_toolkit.document import Document
10 from prompt_toolkit.document import Document
11 from prompt_toolkit.history import History
11 from prompt_toolkit.history import History
12 from prompt_toolkit.shortcuts import PromptSession
12 from prompt_toolkit.shortcuts import PromptSession
13
13
14 from IPython.utils.tokenutil import generate_tokens
14 from IPython.utils.tokenutil import generate_tokens
15
15
16
16
17 def _get_query(document: Document):
17 def _get_query(document: Document):
18 return document.text.rsplit("\n", 1)[-1]
18 return document.text.rsplit("\n", 1)[-1]
19
19
20
20
21 class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
21 class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
22 """
22 """
23 A subclass of AutoSuggestFromHistory that allow navigation to next/previous
23 A subclass of AutoSuggestFromHistory that allow navigation to next/previous
24 suggestion from history. To do so it remembers the current position, but it
24 suggestion from history. To do so it remembers the current position, but it
25 state need to carefully be cleared on the right events.
25 state need to carefully be cleared on the right events.
26 """
26 """
27
27
28 def __init__(
28 def __init__(
29 self,
29 self,
30 ):
30 ):
31 self.skip_lines = 0
31 self.skip_lines = 0
32 self._connected_apps = []
32 self._connected_apps = []
33
33
34 def reset_history_position(self, _: Buffer):
34 def reset_history_position(self, _: Buffer):
35 self.skip_lines = 0
35 self.skip_lines = 0
36
36
37 def disconnect(self):
37 def disconnect(self):
38 for pt_app in self._connected_apps:
38 for pt_app in self._connected_apps:
39 text_insert_event = pt_app.default_buffer.on_text_insert
39 text_insert_event = pt_app.default_buffer.on_text_insert
40 text_insert_event.remove_handler(self.reset_history_position)
40 text_insert_event.remove_handler(self.reset_history_position)
41
41
42 def connect(self, pt_app: PromptSession):
42 def connect(self, pt_app: PromptSession):
43 self._connected_apps.append(pt_app)
43 self._connected_apps.append(pt_app)
44 # note: `on_text_changed` could be used for a bit different behaviour
44 # note: `on_text_changed` could be used for a bit different behaviour
45 # on character deletion (i.e. reseting history position on backspace)
45 # on character deletion (i.e. reseting history position on backspace)
46 pt_app.default_buffer.on_text_insert.add_handler(self.reset_history_position)
46 pt_app.default_buffer.on_text_insert.add_handler(self.reset_history_position)
47
47
48 def get_suggestion(
48 def get_suggestion(
49 self, buffer: Buffer, document: Document
49 self, buffer: Buffer, document: Document
50 ) -> Optional[Suggestion]:
50 ) -> Optional[Suggestion]:
51 text = _get_query(document)
51 text = _get_query(document)
52
52
53 if text.strip():
53 if text.strip():
54 for suggestion, _ in self._find_next_match(
54 for suggestion, _ in self._find_next_match(
55 text, self.skip_lines, buffer.history
55 text, self.skip_lines, buffer.history
56 ):
56 ):
57 return Suggestion(suggestion)
57 return Suggestion(suggestion)
58
58
59 return None
59 return None
60
60
61 def _find_match(
61 def _find_match(
62 self, text: str, skip_lines: float, history: History, previous: bool
62 self, text: str, skip_lines: float, history: History, previous: bool
63 ) -> Generator[Tuple[str, float], None, None]:
63 ) -> Generator[Tuple[str, float], None, None]:
64 """
64 """
65 text: str
65 text: str
66
66
67 skip_lines: float
67 skip_lines: float
68 float is used as the base value is +inf
68 float is used as the base value is +inf
69
69
70 Yields
70 Yields
71 ------
71 ------
72 Tuple with:
72 Tuple with:
73 str:
73 str:
74 current suggestion.
74 current suggestion.
75 float:
75 float:
76 will actually yield only ints, which is passed back via skip_lines,
76 will actually yield only ints, which is passed back via skip_lines,
77 which may be a +inf (float)
77 which may be a +inf (float)
78
78
79
79
80 """
80 """
81 line_number = -1
81 line_number = -1
82 for string in reversed(list(history.get_strings())):
82 for string in reversed(list(history.get_strings())):
83 for line in reversed(string.splitlines()):
83 for line in reversed(string.splitlines()):
84 line_number += 1
84 line_number += 1
85 if not previous and line_number < skip_lines:
85 if not previous and line_number < skip_lines:
86 continue
86 continue
87 # do not return empty suggestions as these
87 # do not return empty suggestions as these
88 # close the auto-suggestion overlay (and are useless)
88 # close the auto-suggestion overlay (and are useless)
89 if line.startswith(text) and len(line) > len(text):
89 if line.startswith(text) and len(line) > len(text):
90 yield line[len(text) :], line_number
90 yield line[len(text) :], line_number
91 if previous and line_number >= skip_lines:
91 if previous and line_number >= skip_lines:
92 return
92 return
93
93
94 def _find_next_match(self, text: str, skip_lines: float, history: History):
94 def _find_next_match(self, text: str, skip_lines: float, history: History):
95 return self._find_match(text, skip_lines, history, previous=False)
95 return self._find_match(text, skip_lines, history, previous=False)
96
96
97 def _find_previous_match(self, text: str, skip_lines: float, history: History):
97 def _find_previous_match(self, text: str, skip_lines: float, history: History):
98 return reversed(
98 return reversed(
99 list(self._find_match(text, skip_lines, history, previous=True))
99 list(self._find_match(text, skip_lines, history, previous=True))
100 )
100 )
101
101
102 def up(self, query: str, other_than: str, history: History):
102 def up(self, query: str, other_than: str, history: History):
103 for suggestion, line_number in self._find_next_match(
103 for suggestion, line_number in self._find_next_match(
104 query, self.skip_lines, history
104 query, self.skip_lines, history
105 ):
105 ):
106 # if user has history ['very.a', 'very', 'very.b'] and typed 'very'
106 # if user has history ['very.a', 'very', 'very.b'] and typed 'very'
107 # we want to switch from 'very.b' to 'very.a' because a) if the
107 # we want to switch from 'very.b' to 'very.a' because a) if the
108 # suggestion equals current text, prompt-toolkit aborts suggesting
108 # suggestion equals current text, prompt-toolkit aborts suggesting
109 # b) user likely would not be interested in 'very' anyways (they
109 # b) user likely would not be interested in 'very' anyways (they
110 # already typed it).
110 # already typed it).
111 if query + suggestion != other_than:
111 if query + suggestion != other_than:
112 self.skip_lines = line_number
112 self.skip_lines = line_number
113 break
113 break
114 else:
114 else:
115 # no matches found, cycle back to beginning
115 # no matches found, cycle back to beginning
116 self.skip_lines = 0
116 self.skip_lines = 0
117
117
118 def down(self, query: str, other_than: str, history: History):
118 def down(self, query: str, other_than: str, history: History):
119 for suggestion, line_number in self._find_previous_match(
119 for suggestion, line_number in self._find_previous_match(
120 query, self.skip_lines, history
120 query, self.skip_lines, history
121 ):
121 ):
122 if query + suggestion != other_than:
122 if query + suggestion != other_than:
123 self.skip_lines = line_number
123 self.skip_lines = line_number
124 break
124 break
125 else:
125 else:
126 # no matches found, cycle to end
126 # no matches found, cycle to end
127 for suggestion, line_number in self._find_previous_match(
127 for suggestion, line_number in self._find_previous_match(
128 query, float("Inf"), history
128 query, float("Inf"), history
129 ):
129 ):
130 if query + suggestion != other_than:
130 if query + suggestion != other_than:
131 self.skip_lines = line_number
131 self.skip_lines = line_number
132 break
132 break
133
133
134
134
135 # Needed for to accept autosuggestions in vi insert mode
135 # Needed for to accept autosuggestions in vi insert mode
136 def accept_in_vi_insert_mode(event: KeyPressEvent):
136 def accept_in_vi_insert_mode(event: KeyPressEvent):
137 """Apply autosuggestion if at end of line."""
137 """Apply autosuggestion if at end of line."""
138 buffer = event.current_buffer
138 buffer = event.current_buffer
139 d = buffer.document
139 d = buffer.document
140 after_cursor = d.text[d.cursor_position :]
140 after_cursor = d.text[d.cursor_position :]
141 lines = after_cursor.split("\n")
141 lines = after_cursor.split("\n")
142 end_of_current_line = lines[0].strip()
142 end_of_current_line = lines[0].strip()
143 suggestion = buffer.suggestion
143 suggestion = buffer.suggestion
144 if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
144 if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
145 buffer.insert_text(suggestion.text)
145 buffer.insert_text(suggestion.text)
146 else:
146 else:
147 nc.end_of_line(event)
147 nc.end_of_line(event)
148
148
149
149
150 def accept(event: KeyPressEvent):
150 def accept(event: KeyPressEvent):
151 """Accept autosuggestion"""
151 """Accept autosuggestion"""
152 buffer = event.current_buffer
152 buffer = event.current_buffer
153 suggestion = buffer.suggestion
153 suggestion = buffer.suggestion
154 if suggestion:
154 if suggestion:
155 buffer.insert_text(suggestion.text)
155 buffer.insert_text(suggestion.text)
156 else:
156 else:
157 nc.forward_char(event)
157 nc.forward_char(event)
158
158
159
159
160 def accept_word(event: KeyPressEvent):
160 def accept_word(event: KeyPressEvent):
161 """Fill partial autosuggestion by word"""
161 """Fill partial autosuggestion by word"""
162 buffer = event.current_buffer
162 buffer = event.current_buffer
163 suggestion = buffer.suggestion
163 suggestion = buffer.suggestion
164 if suggestion:
164 if suggestion:
165 t = re.split(r"(\S+\s+)", suggestion.text)
165 t = re.split(r"(\S+\s+)", suggestion.text)
166 buffer.insert_text(next((x for x in t if x), ""))
166 buffer.insert_text(next((x for x in t if x), ""))
167 else:
167 else:
168 nc.forward_word(event)
168 nc.forward_word(event)
169
169
170
170
171 def accept_character(event: KeyPressEvent):
171 def accept_character(event: KeyPressEvent):
172 """Fill partial autosuggestion by character"""
172 """Fill partial autosuggestion by character"""
173 b = event.current_buffer
173 b = event.current_buffer
174 suggestion = b.suggestion
174 suggestion = b.suggestion
175 if suggestion and suggestion.text:
175 if suggestion and suggestion.text:
176 b.insert_text(suggestion.text[0])
176 b.insert_text(suggestion.text[0])
177
177
178
178
179 def accept_and_keep_cursor(event: KeyPressEvent):
179 def accept_and_keep_cursor(event: KeyPressEvent):
180 """Accept autosuggestion and keep cursor in place"""
180 """Accept autosuggestion and keep cursor in place"""
181 buffer = event.current_buffer
181 buffer = event.current_buffer
182 old_position = buffer.cursor_position
182 old_position = buffer.cursor_position
183 suggestion = buffer.suggestion
183 suggestion = buffer.suggestion
184 if suggestion:
184 if suggestion:
185 buffer.insert_text(suggestion.text)
185 buffer.insert_text(suggestion.text)
186 buffer.cursor_position = old_position
186 buffer.cursor_position = old_position
187
187
188
188
189 def accept_and_move_cursor_left(event: KeyPressEvent):
189 def accept_and_move_cursor_left(event: KeyPressEvent):
190 """Accept autosuggestion and move cursor left in place"""
190 """Accept autosuggestion and move cursor left in place"""
191 accept_and_keep_cursor(event)
191 accept_and_keep_cursor(event)
192 nc.backward_char(event)
192 nc.backward_char(event)
193
193
194
194
195 def backspace_and_resume_hint(event: KeyPressEvent):
195 def backspace_and_resume_hint(event: KeyPressEvent):
196 """Resume autosuggestions after deleting last character"""
196 """Resume autosuggestions after deleting last character"""
197 current_buffer = event.current_buffer
197 current_buffer = event.current_buffer
198
198
199 def resume_hinting(buffer: Buffer):
199 def resume_hinting(buffer: Buffer):
200 if buffer.auto_suggest:
200 if buffer.auto_suggest:
201 suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document)
201 suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document)
202 if suggestion:
202 if suggestion:
203 buffer.suggestion = suggestion
203 buffer.suggestion = suggestion
204 current_buffer.on_text_changed.remove_handler(resume_hinting)
204 current_buffer.on_text_changed.remove_handler(resume_hinting)
205
205
206 current_buffer.on_text_changed.add_handler(resume_hinting)
206 current_buffer.on_text_changed.add_handler(resume_hinting)
207 nc.backward_delete_char(event)
207 nc.backward_delete_char(event)
208
208
209
209
210 def accept_token(event: KeyPressEvent):
210 def accept_token(event: KeyPressEvent):
211 """Fill partial autosuggestion by token"""
211 """Fill partial autosuggestion by token"""
212 b = event.current_buffer
212 b = event.current_buffer
213 suggestion = b.suggestion
213 suggestion = b.suggestion
214
214
215 if suggestion:
215 if suggestion:
216 prefix = _get_query(b.document)
216 prefix = _get_query(b.document)
217 text = prefix + suggestion.text
217 text = prefix + suggestion.text
218
218
219 tokens: List[Optional[str]] = [None, None, None]
219 tokens: List[Optional[str]] = [None, None, None]
220 substrings = [""]
220 substrings = [""]
221 i = 0
221 i = 0
222
222
223 for token in generate_tokens(StringIO(text).readline):
223 for token in generate_tokens(StringIO(text).readline):
224 if token.type == tokenize.NEWLINE:
224 if token.type == tokenize.NEWLINE:
225 index = len(text)
225 index = len(text)
226 else:
226 else:
227 index = text.index(token[1], len(substrings[-1]))
227 index = text.index(token[1], len(substrings[-1]))
228 substrings.append(text[:index])
228 substrings.append(text[:index])
229 tokenized_so_far = substrings[-1]
229 tokenized_so_far = substrings[-1]
230 if tokenized_so_far.startswith(prefix):
230 if tokenized_so_far.startswith(prefix):
231 if i == 0 and len(tokenized_so_far) > len(prefix):
231 if i == 0 and len(tokenized_so_far) > len(prefix):
232 tokens[0] = tokenized_so_far[len(prefix) :]
232 tokens[0] = tokenized_so_far[len(prefix) :]
233 substrings.append(tokenized_so_far)
233 substrings.append(tokenized_so_far)
234 i += 1
234 i += 1
235 tokens[i] = token[1]
235 tokens[i] = token[1]
236 if i == 2:
236 if i == 2:
237 break
237 break
238 i += 1
238 i += 1
239
239
240 if tokens[0]:
240 if tokens[0]:
241 to_insert: str
241 to_insert: str
242 insert_text = substrings[-2]
242 insert_text = substrings[-2]
243 if tokens[1] and len(tokens[1]) == 1:
243 if tokens[1] and len(tokens[1]) == 1:
244 insert_text = substrings[-1]
244 insert_text = substrings[-1]
245 to_insert = insert_text[len(prefix) :]
245 to_insert = insert_text[len(prefix) :]
246 b.insert_text(to_insert)
246 b.insert_text(to_insert)
247 return
247 return
248
248
249 nc.forward_word(event)
249 nc.forward_word(event)
250
250
251
251
252 Provider = Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None]
252 Provider = Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None]
253
253
254
254
255 def _swap_autosuggestion(
255 def _swap_autosuggestion(
256 buffer: Buffer,
256 buffer: Buffer,
257 provider: NavigableAutoSuggestFromHistory,
257 provider: NavigableAutoSuggestFromHistory,
258 direction_method: Callable,
258 direction_method: Callable,
259 ):
259 ):
260 """
260 """
261 We skip most recent history entry (in either direction) if it equals the
261 We skip most recent history entry (in either direction) if it equals the
262 current autosuggestion because if user cycles when auto-suggestion is shown
262 current autosuggestion because if user cycles when auto-suggestion is shown
263 they most likely want something else than what was suggested (othewrise
263 they most likely want something else than what was suggested (otherwise
264 they would have accepted the suggestion).
264 they would have accepted the suggestion).
265 """
265 """
266 suggestion = buffer.suggestion
266 suggestion = buffer.suggestion
267 if not suggestion:
267 if not suggestion:
268 return
268 return
269
269
270 query = _get_query(buffer.document)
270 query = _get_query(buffer.document)
271 current = query + suggestion.text
271 current = query + suggestion.text
272
272
273 direction_method(query=query, other_than=current, history=buffer.history)
273 direction_method(query=query, other_than=current, history=buffer.history)
274
274
275 new_suggestion = provider.get_suggestion(buffer, buffer.document)
275 new_suggestion = provider.get_suggestion(buffer, buffer.document)
276 buffer.suggestion = new_suggestion
276 buffer.suggestion = new_suggestion
277
277
278
278
279 def swap_autosuggestion_up(provider: Provider):
279 def swap_autosuggestion_up(provider: Provider):
280 def swap_autosuggestion_up(event: KeyPressEvent):
280 def swap_autosuggestion_up(event: KeyPressEvent):
281 """Get next autosuggestion from history."""
281 """Get next autosuggestion from history."""
282 if not isinstance(provider, NavigableAutoSuggestFromHistory):
282 if not isinstance(provider, NavigableAutoSuggestFromHistory):
283 return
283 return
284
284
285 return _swap_autosuggestion(
285 return _swap_autosuggestion(
286 buffer=event.current_buffer, provider=provider, direction_method=provider.up
286 buffer=event.current_buffer, provider=provider, direction_method=provider.up
287 )
287 )
288
288
289 swap_autosuggestion_up.__name__ = "swap_autosuggestion_up"
289 swap_autosuggestion_up.__name__ = "swap_autosuggestion_up"
290 return swap_autosuggestion_up
290 return swap_autosuggestion_up
291
291
292
292
293 def swap_autosuggestion_down(
293 def swap_autosuggestion_down(
294 provider: Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None]
294 provider: Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None]
295 ):
295 ):
296 def swap_autosuggestion_down(event: KeyPressEvent):
296 def swap_autosuggestion_down(event: KeyPressEvent):
297 """Get previous autosuggestion from history."""
297 """Get previous autosuggestion from history."""
298 if not isinstance(provider, NavigableAutoSuggestFromHistory):
298 if not isinstance(provider, NavigableAutoSuggestFromHistory):
299 return
299 return
300
300
301 return _swap_autosuggestion(
301 return _swap_autosuggestion(
302 buffer=event.current_buffer,
302 buffer=event.current_buffer,
303 provider=provider,
303 provider=provider,
304 direction_method=provider.down,
304 direction_method=provider.down,
305 )
305 )
306
306
307 swap_autosuggestion_down.__name__ = "swap_autosuggestion_down"
307 swap_autosuggestion_down.__name__ = "swap_autosuggestion_down"
308 return swap_autosuggestion_down
308 return swap_autosuggestion_down
@@ -1,115 +1,115 b''
1 [metadata]
1 [metadata]
2 name = ipython
2 name = ipython
3 version = attr: IPython.core.release.__version__
3 version = attr: IPython.core.release.__version__
4 url = https://ipython.org
4 url = https://ipython.org
5 description = IPython: Productive Interactive Computing
5 description = IPython: Productive Interactive Computing
6 long_description_content_type = text/x-rst
6 long_description_content_type = text/x-rst
7 long_description = file: long_description.rst
7 long_description = file: long_description.rst
8 license_file = LICENSE
8 license_file = LICENSE
9 project_urls =
9 project_urls =
10 Documentation = https://ipython.readthedocs.io/
10 Documentation = https://ipython.readthedocs.io/
11 Funding = https://numfocus.org/
11 Funding = https://numfocus.org/
12 Source = https://github.com/ipython/ipython
12 Source = https://github.com/ipython/ipython
13 Tracker = https://github.com/ipython/ipython/issues
13 Tracker = https://github.com/ipython/ipython/issues
14 keywords = Interactive, Interpreter, Shell, Embedding
14 keywords = Interactive, Interpreter, Shell, Embedding
15 platforms = Linux, Mac OSX, Windows
15 platforms = Linux, Mac OSX, Windows
16 classifiers =
16 classifiers =
17 Framework :: IPython
17 Framework :: IPython
18 Framework :: Jupyter
18 Framework :: Jupyter
19 Intended Audience :: Developers
19 Intended Audience :: Developers
20 Intended Audience :: Science/Research
20 Intended Audience :: Science/Research
21 License :: OSI Approved :: BSD License
21 License :: OSI Approved :: BSD License
22 Programming Language :: Python
22 Programming Language :: Python
23 Programming Language :: Python :: 3
23 Programming Language :: Python :: 3
24 Programming Language :: Python :: 3 :: Only
24 Programming Language :: Python :: 3 :: Only
25 Topic :: System :: Shells
25 Topic :: System :: Shells
26
26
27 [options]
27 [options]
28 packages = find:
28 packages = find:
29 python_requires = >=3.8
29 python_requires = >=3.8
30 zip_safe = False
30 zip_safe = False
31 install_requires =
31 install_requires =
32 appnope; sys_platform == "darwin"
32 appnope; sys_platform == "darwin"
33 backcall
33 backcall
34 colorama; sys_platform == "win32"
34 colorama; sys_platform == "win32"
35 decorator
35 decorator
36 jedi>=0.16
36 jedi>=0.16
37 matplotlib-inline
37 matplotlib-inline
38 pexpect>4.3; sys_platform != "win32"
38 pexpect>4.3; sys_platform != "win32"
39 pickleshare
39 pickleshare
40 prompt_toolkit>=3.0.11,<3.1.0
40 prompt_toolkit>=3.0.30,<3.1.0
41 pygments>=2.4.0
41 pygments>=2.4.0
42 stack_data
42 stack_data
43 traitlets>=5
43 traitlets>=5
44
44
45 [options.extras_require]
45 [options.extras_require]
46 black =
46 black =
47 black
47 black
48 doc =
48 doc =
49 ipykernel
49 ipykernel
50 setuptools>=18.5
50 setuptools>=18.5
51 sphinx>=1.3
51 sphinx>=1.3
52 sphinx-rtd-theme
52 sphinx-rtd-theme
53 docrepr
53 docrepr
54 matplotlib
54 matplotlib
55 stack_data
55 stack_data
56 pytest<7
56 pytest<7
57 typing_extensions
57 typing_extensions
58 %(test)s
58 %(test)s
59 kernel =
59 kernel =
60 ipykernel
60 ipykernel
61 nbconvert =
61 nbconvert =
62 nbconvert
62 nbconvert
63 nbformat =
63 nbformat =
64 nbformat
64 nbformat
65 notebook =
65 notebook =
66 ipywidgets
66 ipywidgets
67 notebook
67 notebook
68 parallel =
68 parallel =
69 ipyparallel
69 ipyparallel
70 qtconsole =
70 qtconsole =
71 qtconsole
71 qtconsole
72 terminal =
72 terminal =
73 test =
73 test =
74 pytest<7.1
74 pytest<7.1
75 pytest-asyncio
75 pytest-asyncio
76 testpath
76 testpath
77 test_extra =
77 test_extra =
78 %(test)s
78 %(test)s
79 curio
79 curio
80 matplotlib!=3.2.0
80 matplotlib!=3.2.0
81 nbformat
81 nbformat
82 numpy>=1.20
82 numpy>=1.20
83 pandas
83 pandas
84 trio
84 trio
85 all =
85 all =
86 %(black)s
86 %(black)s
87 %(doc)s
87 %(doc)s
88 %(kernel)s
88 %(kernel)s
89 %(nbconvert)s
89 %(nbconvert)s
90 %(nbformat)s
90 %(nbformat)s
91 %(notebook)s
91 %(notebook)s
92 %(parallel)s
92 %(parallel)s
93 %(qtconsole)s
93 %(qtconsole)s
94 %(terminal)s
94 %(terminal)s
95 %(test_extra)s
95 %(test_extra)s
96 %(test)s
96 %(test)s
97
97
98 [options.packages.find]
98 [options.packages.find]
99 exclude =
99 exclude =
100 setupext
100 setupext
101
101
102 [options.package_data]
102 [options.package_data]
103 IPython = py.typed
103 IPython = py.typed
104 IPython.core = profile/README*
104 IPython.core = profile/README*
105 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
105 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
106 IPython.lib.tests = *.wav
106 IPython.lib.tests = *.wav
107 IPython.testing.plugin = *.txt
107 IPython.testing.plugin = *.txt
108
108
109 [velin]
109 [velin]
110 ignore_patterns =
110 ignore_patterns =
111 IPython/core/tests
111 IPython/core/tests
112 IPython/testing
112 IPython/testing
113
113
114 [tool.black]
114 [tool.black]
115 exclude = 'timing\.py'
115 exclude = 'timing\.py'
General Comments 0
You need to be logged in to leave comments. Login now