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