Show More
@@ -50,7 +50,10 b' from .pt_inputhooks import get_inputhook_name_and_func' | |||||
50 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook |
|
50 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook | |
51 | from .ptutils import IPythonPTCompleter, IPythonPTLexer |
|
51 | from .ptutils import IPythonPTCompleter, IPythonPTLexer | |
52 | from .shortcuts import create_ipython_shortcuts |
|
52 | from .shortcuts import create_ipython_shortcuts | |
53 |
from .shortcuts.auto_suggest import |
|
53 | from .shortcuts.auto_suggest import ( | |
|
54 | NavigableAutoSuggestFromHistory, | |||
|
55 | AppendAutoSuggestionInAnyLine, | |||
|
56 | ) | |||
54 |
|
57 | |||
55 | PTK3 = ptk_version.startswith('3.') |
|
58 | PTK3 = ptk_version.startswith('3.') | |
56 |
|
59 | |||
@@ -577,23 +580,39 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
577 | get_message = get_message() |
|
580 | get_message = get_message() | |
578 |
|
581 | |||
579 | options = { |
|
582 | options = { | |
580 |
|
|
583 | "complete_in_thread": False, | |
581 |
|
|
584 | "lexer": IPythonPTLexer(), | |
582 |
|
|
585 | "reserve_space_for_menu": self.space_for_menu, | |
583 |
|
|
586 | "message": get_message, | |
584 |
|
|
587 | "prompt_continuation": ( | |
585 |
|
|
588 | lambda width, lineno, is_soft_wrap: PygmentsTokens( | |
586 |
|
|
589 | self.prompts.continuation_prompt_tokens(width) | |
587 |
|
|
590 | ) | |
588 | 'complete_style': self.pt_complete_style, |
|
591 | ), | |
589 |
|
592 | "multiline": True, | ||
|
593 | "complete_style": self.pt_complete_style, | |||
|
594 | "input_processors": [ | |||
590 | # Highlight matching brackets, but only when this setting is |
|
595 | # Highlight matching brackets, but only when this setting is | |
591 | # enabled, and only when the DEFAULT_BUFFER has the focus. |
|
596 | # enabled, and only when the DEFAULT_BUFFER has the focus. | |
592 |
|
|
597 | ConditionalProcessor( | |
593 |
|
|
598 | processor=HighlightMatchingBracketProcessor(chars="[](){}"), | |
594 |
|
|
599 | filter=HasFocus(DEFAULT_BUFFER) | |
595 | Condition(lambda: self.highlight_matching_brackets))], |
|
600 | & ~IsDone() | |
596 | } |
|
601 | & Condition(lambda: self.highlight_matching_brackets), | |
|
602 | ), | |||
|
603 | # Show auto-suggestion in lines other than the last line. | |||
|
604 | ConditionalProcessor( | |||
|
605 | processor=AppendAutoSuggestionInAnyLine(), | |||
|
606 | filter=HasFocus(DEFAULT_BUFFER) | |||
|
607 | & ~IsDone() | |||
|
608 | & Condition( | |||
|
609 | lambda: isinstance( | |||
|
610 | self.auto_suggest, NavigableAutoSuggestFromHistory | |||
|
611 | ) | |||
|
612 | ), | |||
|
613 | ), | |||
|
614 | ], | |||
|
615 | } | |||
597 | if not PTK3: |
|
616 | if not PTK3: | |
598 | options['inputhook'] = self.inputhook |
|
617 | options['inputhook'] = self.inputhook | |
599 |
|
618 |
@@ -52,6 +52,18 b' def has_focus(value: FocusableElement):' | |||||
52 | return Condition(tester) |
|
52 | return Condition(tester) | |
53 |
|
53 | |||
54 |
|
54 | |||
|
55 | @Condition | |||
|
56 | def has_line_below() -> bool: | |||
|
57 | document = get_app().current_buffer.document | |||
|
58 | return document.cursor_position_row < len(document.lines) - 1 | |||
|
59 | ||||
|
60 | ||||
|
61 | @Condition | |||
|
62 | def has_line_above() -> bool: | |||
|
63 | document = get_app().current_buffer.document | |||
|
64 | return document.cursor_position_row != 0 | |||
|
65 | ||||
|
66 | ||||
55 | def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings: |
|
67 | def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings: | |
56 | """Set up the prompt_toolkit keyboard shortcuts for IPython. |
|
68 | """Set up the prompt_toolkit keyboard shortcuts for IPython. | |
57 |
|
69 | |||
@@ -332,6 +344,12 b' def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindi' | |||||
332 | focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode |
|
344 | focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode | |
333 |
|
345 | |||
334 | # autosuggestions |
|
346 | # autosuggestions | |
|
347 | @Condition | |||
|
348 | def navigable_suggestions(): | |||
|
349 | return isinstance( | |||
|
350 | shell.auto_suggest, auto_suggest.NavigableAutoSuggestFromHistory | |||
|
351 | ) | |||
|
352 | ||||
335 | kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))( |
|
353 | kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))( | |
336 | auto_suggest.accept_in_vi_insert_mode |
|
354 | auto_suggest.accept_in_vi_insert_mode | |
337 | ) |
|
355 | ) | |
@@ -343,19 +361,33 b' def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindi' | |||||
343 | kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
361 | kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
344 | auto_suggest.accept_token |
|
362 | auto_suggest.accept_token | |
345 | ) |
|
363 | ) | |
346 |
kb.add("escape", filter=has_suggestion & has_focus(DEFAULT_BUFFER) |
|
364 | kb.add("escape", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
347 | auto_suggest.discard |
|
365 | auto_suggest.discard | |
348 | ) |
|
366 | ) | |
349 | kb.add("up", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
367 | kb.add( | |
350 | auto_suggest.swap_autosuggestion_up(shell.auto_suggest) |
|
368 | "up", | |
|
369 | filter=navigable_suggestions | |||
|
370 | & ~has_line_above | |||
|
371 | & has_suggestion | |||
|
372 | & has_focus(DEFAULT_BUFFER), | |||
|
373 | )(auto_suggest.swap_autosuggestion_up(shell.auto_suggest)) | |||
|
374 | kb.add( | |||
|
375 | "down", | |||
|
376 | filter=navigable_suggestions | |||
|
377 | & ~has_line_below | |||
|
378 | & has_suggestion | |||
|
379 | & has_focus(DEFAULT_BUFFER), | |||
|
380 | )(auto_suggest.swap_autosuggestion_down(shell.auto_suggest)) | |||
|
381 | kb.add("up", filter=navigable_suggestions & has_focus(DEFAULT_BUFFER))( | |||
|
382 | auto_suggest.up_and_update_hint | |||
351 | ) |
|
383 | ) | |
352 |
kb.add("down", filter= |
|
384 | kb.add("down", filter=navigable_suggestions & has_focus(DEFAULT_BUFFER))( | |
353 | auto_suggest.swap_autosuggestion_down(shell.auto_suggest) |
|
385 | auto_suggest.down_and_update_hint | |
354 | ) |
|
386 | ) | |
355 | kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
387 | kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
356 | auto_suggest.accept_character |
|
388 | auto_suggest.accept_character | |
357 | ) |
|
389 | ) | |
358 | kb.add("left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
390 | kb.add("c-left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
359 | auto_suggest.accept_and_move_cursor_left |
|
391 | auto_suggest.accept_and_move_cursor_left | |
360 | ) |
|
392 | ) | |
361 | kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
393 | kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
@@ -10,12 +10,43 b' from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion' | |||||
10 | from prompt_toolkit.document import Document |
|
10 | from prompt_toolkit.document import Document | |
11 | from prompt_toolkit.history import History |
|
11 | from prompt_toolkit.history import History | |
12 | from prompt_toolkit.shortcuts import PromptSession |
|
12 | from prompt_toolkit.shortcuts import PromptSession | |
|
13 | from prompt_toolkit.layout.processors import ( | |||
|
14 | Processor, | |||
|
15 | Transformation, | |||
|
16 | TransformationInput, | |||
|
17 | ) | |||
13 |
|
18 | |||
14 | from IPython.utils.tokenutil import generate_tokens |
|
19 | from IPython.utils.tokenutil import generate_tokens | |
15 |
|
20 | |||
16 |
|
21 | |||
17 | def _get_query(document: Document): |
|
22 | def _get_query(document: Document): | |
18 |
return document. |
|
23 | return document.lines[document.cursor_position_row] | |
|
24 | ||||
|
25 | ||||
|
26 | class AppendAutoSuggestionInAnyLine(Processor): | |||
|
27 | """ | |||
|
28 | Append the auto suggestion to lines other than the last (appending to the | |||
|
29 | last line is natively supported by the prompt toolkit). | |||
|
30 | """ | |||
|
31 | ||||
|
32 | def __init__(self, style: str = "class:auto-suggestion") -> None: | |||
|
33 | self.style = style | |||
|
34 | ||||
|
35 | def apply_transformation(self, ti: TransformationInput) -> Transformation: | |||
|
36 | is_last_line = ti.lineno == ti.document.line_count - 1 | |||
|
37 | is_active_line = ti.lineno == ti.document.cursor_position_row | |||
|
38 | ||||
|
39 | if not is_last_line and is_active_line: | |||
|
40 | buffer = ti.buffer_control.buffer | |||
|
41 | ||||
|
42 | if buffer.suggestion and ti.document.is_cursor_at_the_end_of_line: | |||
|
43 | suggestion = buffer.suggestion.text | |||
|
44 | else: | |||
|
45 | suggestion = "" | |||
|
46 | ||||
|
47 | return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) | |||
|
48 | else: | |||
|
49 | return Transformation(fragments=ti.fragments) | |||
19 |
|
50 | |||
20 |
|
51 | |||
21 | class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): |
|
52 | class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): | |
@@ -208,21 +239,40 b' def accept_and_move_cursor_left(event: KeyPressEvent):' | |||||
208 | nc.backward_char(event) |
|
239 | nc.backward_char(event) | |
209 |
|
240 | |||
210 |
|
241 | |||
|
242 | def _update_hint(buffer: Buffer): | |||
|
243 | if buffer.auto_suggest: | |||
|
244 | suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document) | |||
|
245 | buffer.suggestion = suggestion | |||
|
246 | ||||
|
247 | ||||
211 | def backspace_and_resume_hint(event: KeyPressEvent): |
|
248 | def backspace_and_resume_hint(event: KeyPressEvent): | |
212 | """Resume autosuggestions after deleting last character""" |
|
249 | """Resume autosuggestions after deleting last character""" | |
213 | current_buffer = event.current_buffer |
|
250 | current_buffer = event.current_buffer | |
214 |
|
251 | |||
215 | def resume_hinting(buffer: Buffer): |
|
252 | def resume_hinting(buffer: Buffer): | |
216 | if buffer.auto_suggest: |
|
253 | _update_hint(buffer) | |
217 | suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document) |
|
|||
218 | if suggestion: |
|
|||
219 | buffer.suggestion = suggestion |
|
|||
220 | current_buffer.on_text_changed.remove_handler(resume_hinting) |
|
254 | current_buffer.on_text_changed.remove_handler(resume_hinting) | |
221 |
|
255 | |||
222 | current_buffer.on_text_changed.add_handler(resume_hinting) |
|
256 | current_buffer.on_text_changed.add_handler(resume_hinting) | |
223 | nc.backward_delete_char(event) |
|
257 | nc.backward_delete_char(event) | |
224 |
|
258 | |||
225 |
|
259 | |||
|
260 | def up_and_update_hint(event: KeyPressEvent): | |||
|
261 | """Go up and update hint""" | |||
|
262 | current_buffer = event.current_buffer | |||
|
263 | ||||
|
264 | current_buffer.auto_up(count=event.arg) | |||
|
265 | _update_hint(current_buffer) | |||
|
266 | ||||
|
267 | ||||
|
268 | def down_and_update_hint(event: KeyPressEvent): | |||
|
269 | """Go down and update hint""" | |||
|
270 | current_buffer = event.current_buffer | |||
|
271 | ||||
|
272 | current_buffer.auto_down(count=event.arg) | |||
|
273 | _update_hint(current_buffer) | |||
|
274 | ||||
|
275 | ||||
226 | def accept_token(event: KeyPressEvent): |
|
276 | def accept_token(event: KeyPressEvent): | |
227 | """Fill partial autosuggestion by token""" |
|
277 | """Fill partial autosuggestion by token""" | |
228 | b = event.current_buffer |
|
278 | b = event.current_buffer |
@@ -14,6 +14,7 b' from IPython.terminal.shortcuts.auto_suggest import (' | |||||
14 |
|
14 | |||
15 | from prompt_toolkit.history import InMemoryHistory |
|
15 | from prompt_toolkit.history import InMemoryHistory | |
16 | from prompt_toolkit.buffer import Buffer |
|
16 | from prompt_toolkit.buffer import Buffer | |
|
17 | from prompt_toolkit.document import Document | |||
17 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory |
|
18 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory | |
18 |
|
19 | |||
19 | from unittest.mock import patch, Mock |
|
20 | from unittest.mock import patch, Mock | |
@@ -26,10 +27,7 b' def make_event(text, cursor, suggestion):' | |||||
26 | event.current_buffer.text = text |
|
27 | event.current_buffer.text = text | |
27 | event.current_buffer.cursor_position = cursor |
|
28 | event.current_buffer.cursor_position = cursor | |
28 | event.current_buffer.suggestion.text = suggestion |
|
29 | event.current_buffer.suggestion.text = suggestion | |
29 |
event.current_buffer.document = |
|
30 | event.current_buffer.document = Document(text=text, cursor_position=cursor) | |
30 | event.current_buffer.document.get_end_of_line_position = Mock(return_value=0) |
|
|||
31 | event.current_buffer.document.text = text |
|
|||
32 | event.current_buffer.document.cursor_position = cursor |
|
|||
33 | return event |
|
31 | return event | |
34 |
|
32 | |||
35 |
|
33 | |||
@@ -252,6 +250,42 b' async def test_navigable_provider():' | |||||
252 | assert get_suggestion().text == "_a" |
|
250 | assert get_suggestion().text == "_a" | |
253 |
|
251 | |||
254 |
|
252 | |||
|
253 | async def test_navigable_provider_multiline_entries(): | |||
|
254 | provider = NavigableAutoSuggestFromHistory() | |||
|
255 | history = InMemoryHistory(history_strings=["very_a\nvery_b", "very_c"]) | |||
|
256 | buffer = Buffer(history=history) | |||
|
257 | ||||
|
258 | async for _ in history.load(): | |||
|
259 | pass | |||
|
260 | ||||
|
261 | buffer.cursor_position = 5 | |||
|
262 | buffer.text = "very" | |||
|
263 | up = swap_autosuggestion_up(provider) | |||
|
264 | down = swap_autosuggestion_down(provider) | |||
|
265 | ||||
|
266 | event = Mock() | |||
|
267 | event.current_buffer = buffer | |||
|
268 | ||||
|
269 | def get_suggestion(): | |||
|
270 | suggestion = provider.get_suggestion(buffer, buffer.document) | |||
|
271 | buffer.suggestion = suggestion | |||
|
272 | return suggestion | |||
|
273 | ||||
|
274 | assert get_suggestion().text == "_c" | |||
|
275 | ||||
|
276 | up(event) | |||
|
277 | assert get_suggestion().text == "_b" | |||
|
278 | ||||
|
279 | up(event) | |||
|
280 | assert get_suggestion().text == "_a" | |||
|
281 | ||||
|
282 | down(event) | |||
|
283 | assert get_suggestion().text == "_b" | |||
|
284 | ||||
|
285 | down(event) | |||
|
286 | assert get_suggestion().text == "_c" | |||
|
287 | ||||
|
288 | ||||
255 | def create_session_mock(): |
|
289 | def create_session_mock(): | |
256 | session = Mock() |
|
290 | session = Mock() | |
257 | session.default_buffer = Buffer() |
|
291 | session.default_buffer = Buffer() |
General Comments 0
You need to be logged in to leave comments.
Login now