##// END OF EJS Templates
Backport PR #12336: Fix auto formatting to be called only once.
Matthias Bussonnier -
Show More
@@ -1,273 +1,276 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 from typing import Callable
12 from typing import Callable
13
13
14
14
15 from prompt_toolkit.application.current import get_app
15 from prompt_toolkit.application.current import get_app
16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
16 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
17 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
17 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
18 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
18 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
19 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
19 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
20 from prompt_toolkit.key_binding import KeyBindings
20 from prompt_toolkit.key_binding import KeyBindings
21
21
22 from IPython.utils.decorators import undoc
22 from IPython.utils.decorators import undoc
23
23
24 @undoc
24 @undoc
25 @Condition
25 @Condition
26 def cursor_in_leading_ws():
26 def cursor_in_leading_ws():
27 before = get_app().current_buffer.document.current_line_before_cursor
27 before = get_app().current_buffer.document.current_line_before_cursor
28 return (not before) or before.isspace()
28 return (not before) or before.isspace()
29
29
30
30
31 def create_ipython_shortcuts(shell):
31 def create_ipython_shortcuts(shell):
32 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
32 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
33
33
34 kb = KeyBindings()
34 kb = KeyBindings()
35 insert_mode = vi_insert_mode | emacs_insert_mode
35 insert_mode = vi_insert_mode | emacs_insert_mode
36
36
37 if getattr(shell, 'handle_return', None):
37 if getattr(shell, 'handle_return', None):
38 return_handler = shell.handle_return(shell)
38 return_handler = shell.handle_return(shell)
39 else:
39 else:
40 return_handler = newline_or_execute_outer(shell)
40 return_handler = newline_or_execute_outer(shell)
41
41
42 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
42 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
43 & ~has_selection
43 & ~has_selection
44 & insert_mode
44 & insert_mode
45 ))(return_handler)
45 ))(return_handler)
46
46
47 def reformat_and_execute(event):
47 def reformat_and_execute(event):
48 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
48 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
49 event.current_buffer.validate_and_handle()
49 event.current_buffer.validate_and_handle()
50
50
51 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
51 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
52 & ~has_selection
52 & ~has_selection
53 & insert_mode
53 & insert_mode
54 ))(reformat_and_execute)
54 ))(reformat_and_execute)
55
55
56 kb.add('c-\\')(force_exit)
56 kb.add('c-\\')(force_exit)
57
57
58 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
58 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
59 )(previous_history_or_previous_completion)
59 )(previous_history_or_previous_completion)
60
60
61 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
61 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
62 )(next_history_or_next_completion)
62 )(next_history_or_next_completion)
63
63
64 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
64 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
65 )(dismiss_completion)
65 )(dismiss_completion)
66
66
67 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
67 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
68
68
69 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
69 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
70
70
71 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
71 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
72 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
72 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
73
73
74 # Ctrl+I == Tab
74 # Ctrl+I == Tab
75 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
75 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
76 & ~has_selection
76 & ~has_selection
77 & insert_mode
77 & insert_mode
78 & cursor_in_leading_ws
78 & cursor_in_leading_ws
79 ))(indent_buffer)
79 ))(indent_buffer)
80 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
80 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
81 )(newline_autoindent_outer(shell.input_transformer_manager))
81 )(newline_autoindent_outer(shell.input_transformer_manager))
82
82
83 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
83 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
84
84
85 if shell.display_completions == 'readlinelike':
85 if shell.display_completions == 'readlinelike':
86 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
86 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
87 & ~has_selection
87 & ~has_selection
88 & insert_mode
88 & insert_mode
89 & ~cursor_in_leading_ws
89 & ~cursor_in_leading_ws
90 ))(display_completions_like_readline)
90 ))(display_completions_like_readline)
91
91
92 if sys.platform == 'win32':
92 if sys.platform == 'win32':
93 kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
93 kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
94
94
95 return kb
95 return kb
96
96
97
97
98 def reformat_text_before_cursor(buffer, document, shell):
98 def reformat_text_before_cursor(buffer, document, shell):
99 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
99 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
100 try:
100 try:
101 formatted_text = shell.reformat_handler(text)
101 formatted_text = shell.reformat_handler(text)
102 buffer.insert_text(formatted_text)
102 buffer.insert_text(formatted_text)
103 except Exception as e:
103 except Exception as e:
104 buffer.insert_text(text)
104 buffer.insert_text(text)
105
105
106
106
107 def newline_or_execute_outer(shell):
107 def newline_or_execute_outer(shell):
108
108
109 def newline_or_execute(event):
109 def newline_or_execute(event):
110 """When the user presses return, insert a newline or execute the code."""
110 """When the user presses return, insert a newline or execute the code."""
111 b = event.current_buffer
111 b = event.current_buffer
112 d = b.document
112 d = b.document
113
113
114 if b.complete_state:
114 if b.complete_state:
115 cc = b.complete_state.current_completion
115 cc = b.complete_state.current_completion
116 if cc:
116 if cc:
117 b.apply_completion(cc)
117 b.apply_completion(cc)
118 else:
118 else:
119 b.cancel_completion()
119 b.cancel_completion()
120 return
120 return
121
121
122 # If there's only one line, treat it as if the cursor is at the end.
122 # If there's only one line, treat it as if the cursor is at the end.
123 # See https://github.com/ipython/ipython/issues/10425
123 # See https://github.com/ipython/ipython/issues/10425
124 if d.line_count == 1:
124 if d.line_count == 1:
125 check_text = d.text
125 check_text = d.text
126 else:
126 else:
127 check_text = d.text[:d.cursor_position]
127 check_text = d.text[:d.cursor_position]
128 status, indent = shell.check_complete(check_text)
128 status, indent = shell.check_complete(check_text)
129
129
130 # if all we have after the cursor is whitespace: reformat current text
130 # if all we have after the cursor is whitespace: reformat current text
131 # before cursor
131 # before cursor
132 after_cursor = d.text[d.cursor_position:]
132 after_cursor = d.text[d.cursor_position:]
133 reformatted = False
133 if not after_cursor.strip():
134 if not after_cursor.strip():
134 reformat_text_before_cursor(b, d, shell)
135 reformat_text_before_cursor(b, d, shell)
136 reformatted = True
135 if not (d.on_last_line or
137 if not (d.on_last_line or
136 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
138 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
137 ):
139 ):
138 if shell.autoindent:
140 if shell.autoindent:
139 b.insert_text('\n' + indent)
141 b.insert_text('\n' + indent)
140 else:
142 else:
141 b.insert_text('\n')
143 b.insert_text('\n')
142 return
144 return
143
145
144 if (status != 'incomplete') and b.accept_handler:
146 if (status != 'incomplete') and b.accept_handler:
145 reformat_text_before_cursor(b, d, shell)
147 if not reformatted:
148 reformat_text_before_cursor(b, d, shell)
146 b.validate_and_handle()
149 b.validate_and_handle()
147 else:
150 else:
148 if shell.autoindent:
151 if shell.autoindent:
149 b.insert_text('\n' + indent)
152 b.insert_text('\n' + indent)
150 else:
153 else:
151 b.insert_text('\n')
154 b.insert_text('\n')
152 return newline_or_execute
155 return newline_or_execute
153
156
154
157
155 def previous_history_or_previous_completion(event):
158 def previous_history_or_previous_completion(event):
156 """
159 """
157 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
160 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
158
161
159 If completer is open this still select previous completion.
162 If completer is open this still select previous completion.
160 """
163 """
161 event.current_buffer.auto_up()
164 event.current_buffer.auto_up()
162
165
163
166
164 def next_history_or_next_completion(event):
167 def next_history_or_next_completion(event):
165 """
168 """
166 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
169 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
167
170
168 If completer is open this still select next completion.
171 If completer is open this still select next completion.
169 """
172 """
170 event.current_buffer.auto_down()
173 event.current_buffer.auto_down()
171
174
172
175
173 def dismiss_completion(event):
176 def dismiss_completion(event):
174 b = event.current_buffer
177 b = event.current_buffer
175 if b.complete_state:
178 if b.complete_state:
176 b.cancel_completion()
179 b.cancel_completion()
177
180
178
181
179 def reset_buffer(event):
182 def reset_buffer(event):
180 b = event.current_buffer
183 b = event.current_buffer
181 if b.complete_state:
184 if b.complete_state:
182 b.cancel_completion()
185 b.cancel_completion()
183 else:
186 else:
184 b.reset()
187 b.reset()
185
188
186
189
187 def reset_search_buffer(event):
190 def reset_search_buffer(event):
188 if event.current_buffer.document.text:
191 if event.current_buffer.document.text:
189 event.current_buffer.reset()
192 event.current_buffer.reset()
190 else:
193 else:
191 event.app.layout.focus(DEFAULT_BUFFER)
194 event.app.layout.focus(DEFAULT_BUFFER)
192
195
193 def suspend_to_bg(event):
196 def suspend_to_bg(event):
194 event.app.suspend_to_background()
197 event.app.suspend_to_background()
195
198
196 def force_exit(event):
199 def force_exit(event):
197 """
200 """
198 Force exit (with a non-zero return value)
201 Force exit (with a non-zero return value)
199 """
202 """
200 sys.exit("Quit")
203 sys.exit("Quit")
201
204
202 def indent_buffer(event):
205 def indent_buffer(event):
203 event.current_buffer.insert_text(' ' * 4)
206 event.current_buffer.insert_text(' ' * 4)
204
207
205 @undoc
208 @undoc
206 def newline_with_copy_margin(event):
209 def newline_with_copy_margin(event):
207 """
210 """
208 DEPRECATED since IPython 6.0
211 DEPRECATED since IPython 6.0
209
212
210 See :any:`newline_autoindent_outer` for a replacement.
213 See :any:`newline_autoindent_outer` for a replacement.
211
214
212 Preserve margin and cursor position when using
215 Preserve margin and cursor position when using
213 Control-O to insert a newline in EMACS mode
216 Control-O to insert a newline in EMACS mode
214 """
217 """
215 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
218 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
216 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
219 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
217 DeprecationWarning, stacklevel=2)
220 DeprecationWarning, stacklevel=2)
218
221
219 b = event.current_buffer
222 b = event.current_buffer
220 cursor_start_pos = b.document.cursor_position_col
223 cursor_start_pos = b.document.cursor_position_col
221 b.newline(copy_margin=True)
224 b.newline(copy_margin=True)
222 b.cursor_up(count=1)
225 b.cursor_up(count=1)
223 cursor_end_pos = b.document.cursor_position_col
226 cursor_end_pos = b.document.cursor_position_col
224 if cursor_start_pos != cursor_end_pos:
227 if cursor_start_pos != cursor_end_pos:
225 pos_diff = cursor_start_pos - cursor_end_pos
228 pos_diff = cursor_start_pos - cursor_end_pos
226 b.cursor_right(count=pos_diff)
229 b.cursor_right(count=pos_diff)
227
230
228 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
231 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
229 """
232 """
230 Return a function suitable for inserting a indented newline after the cursor.
233 Return a function suitable for inserting a indented newline after the cursor.
231
234
232 Fancier version of deprecated ``newline_with_copy_margin`` which should
235 Fancier version of deprecated ``newline_with_copy_margin`` which should
233 compute the correct indentation of the inserted line. That is to say, indent
236 compute the correct indentation of the inserted line. That is to say, indent
234 by 4 extra space after a function definition, class definition, context
237 by 4 extra space after a function definition, class definition, context
235 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
238 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
236 """
239 """
237
240
238 def newline_autoindent(event):
241 def newline_autoindent(event):
239 """insert a newline after the cursor indented appropriately."""
242 """insert a newline after the cursor indented appropriately."""
240 b = event.current_buffer
243 b = event.current_buffer
241 d = b.document
244 d = b.document
242
245
243 if b.complete_state:
246 if b.complete_state:
244 b.cancel_completion()
247 b.cancel_completion()
245 text = d.text[:d.cursor_position] + '\n'
248 text = d.text[:d.cursor_position] + '\n'
246 _, indent = inputsplitter.check_complete(text)
249 _, indent = inputsplitter.check_complete(text)
247 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
250 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
248
251
249 return newline_autoindent
252 return newline_autoindent
250
253
251
254
252 def open_input_in_editor(event):
255 def open_input_in_editor(event):
253 event.app.current_buffer.open_in_editor()
256 event.app.current_buffer.open_in_editor()
254
257
255
258
256 if sys.platform == 'win32':
259 if sys.platform == 'win32':
257 from IPython.core.error import TryNext
260 from IPython.core.error import TryNext
258 from IPython.lib.clipboard import (ClipboardEmpty,
261 from IPython.lib.clipboard import (ClipboardEmpty,
259 win32_clipboard_get,
262 win32_clipboard_get,
260 tkinter_clipboard_get)
263 tkinter_clipboard_get)
261
264
262 @undoc
265 @undoc
263 def win_paste(event):
266 def win_paste(event):
264 try:
267 try:
265 text = win32_clipboard_get()
268 text = win32_clipboard_get()
266 except TryNext:
269 except TryNext:
267 try:
270 try:
268 text = tkinter_clipboard_get()
271 text = tkinter_clipboard_get()
269 except (TryNext, ClipboardEmpty):
272 except (TryNext, ClipboardEmpty):
270 return
273 return
271 except ClipboardEmpty:
274 except ClipboardEmpty:
272 return
275 return
273 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
276 event.current_buffer.insert_text(text.replace('\t', ' ' * 4))
General Comments 0
You need to be logged in to leave comments. Login now