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