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