Show More
@@ -50,7 +50,10 b' from .pt_inputhooks import get_inputhook_name_and_func' | |||
|
50 | 50 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook |
|
51 | 51 | from .ptutils import IPythonPTCompleter, IPythonPTLexer |
|
52 | 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 | 58 | PTK3 = ptk_version.startswith('3.') |
|
56 | 59 | |
@@ -577,23 +580,39 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
577 | 580 | get_message = get_message() |
|
578 | 581 | |
|
579 | 582 | options = { |
|
580 |
|
|
|
581 |
|
|
|
582 |
|
|
|
583 |
|
|
|
584 |
|
|
|
585 |
|
|
|
586 |
|
|
|
587 |
|
|
|
588 | 'complete_style': self.pt_complete_style, | |
|
589 | ||
|
583 | "complete_in_thread": False, | |
|
584 | "lexer": IPythonPTLexer(), | |
|
585 | "reserve_space_for_menu": self.space_for_menu, | |
|
586 | "message": get_message, | |
|
587 | "prompt_continuation": ( | |
|
588 | lambda width, lineno, is_soft_wrap: PygmentsTokens( | |
|
589 | self.prompts.continuation_prompt_tokens(width) | |
|
590 | ) | |
|
591 | ), | |
|
592 | "multiline": True, | |
|
593 | "complete_style": self.pt_complete_style, | |
|
594 | "input_processors": [ | |
|
590 | 595 | # Highlight matching brackets, but only when this setting is |
|
591 | 596 | # enabled, and only when the DEFAULT_BUFFER has the focus. |
|
592 |
|
|
|
593 |
|
|
|
594 |
|
|
|
595 | Condition(lambda: self.highlight_matching_brackets))], | |
|
596 | } | |
|
597 | ConditionalProcessor( | |
|
598 | processor=HighlightMatchingBracketProcessor(chars="[](){}"), | |
|
599 | filter=HasFocus(DEFAULT_BUFFER) | |
|
600 | & ~IsDone() | |
|
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 | 616 | if not PTK3: |
|
598 | 617 | options['inputhook'] = self.inputhook |
|
599 | 618 |
@@ -52,6 +52,18 b' def has_focus(value: FocusableElement):' | |||
|
52 | 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 | 67 | def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings: |
|
56 | 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 | 344 | focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode |
|
333 | 345 | |
|
334 | 346 | # autosuggestions |
|
347 | @Condition | |
|
348 | def navigable_suggestions(): | |
|
349 | return isinstance( | |
|
350 | shell.auto_suggest, auto_suggest.NavigableAutoSuggestFromHistory | |
|
351 | ) | |
|
352 | ||
|
335 | 353 | kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))( |
|
336 | 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 | 361 | kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
344 | 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 | 365 | auto_suggest.discard |
|
348 | 366 | ) |
|
349 | kb.add("up", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
350 | auto_suggest.swap_autosuggestion_up(shell.auto_suggest) | |
|
367 | kb.add( | |
|
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= |
|
|
353 | auto_suggest.swap_autosuggestion_down(shell.auto_suggest) | |
|
384 | kb.add("down", filter=navigable_suggestions & has_focus(DEFAULT_BUFFER))( | |
|
385 | auto_suggest.down_and_update_hint | |
|
354 | 386 | ) |
|
355 | 387 | kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( |
|
356 | 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 | 391 | auto_suggest.accept_and_move_cursor_left |
|
360 | 392 | ) |
|
361 | 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 | 10 | from prompt_toolkit.document import Document |
|
11 | 11 | from prompt_toolkit.history import History |
|
12 | 12 | from prompt_toolkit.shortcuts import PromptSession |
|
13 | from prompt_toolkit.layout.processors import ( | |
|
14 | Processor, | |
|
15 | Transformation, | |
|
16 | TransformationInput, | |
|
17 | ) | |
|
13 | 18 | |
|
14 | 19 | from IPython.utils.tokenutil import generate_tokens |
|
15 | 20 | |
|
16 | 21 | |
|
17 | 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 | 52 | class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): |
@@ -208,21 +239,40 b' def accept_and_move_cursor_left(event: KeyPressEvent):' | |||
|
208 | 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 | 248 | def backspace_and_resume_hint(event: KeyPressEvent): |
|
212 | 249 | """Resume autosuggestions after deleting last character""" |
|
213 | 250 | current_buffer = event.current_buffer |
|
214 | 251 | |
|
215 | 252 | def resume_hinting(buffer: Buffer): |
|
216 | if buffer.auto_suggest: | |
|
217 | suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document) | |
|
218 | if suggestion: | |
|
219 | buffer.suggestion = suggestion | |
|
253 | _update_hint(buffer) | |
|
220 | 254 | current_buffer.on_text_changed.remove_handler(resume_hinting) |
|
221 | 255 | |
|
222 | 256 | current_buffer.on_text_changed.add_handler(resume_hinting) |
|
223 | 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 | 276 | def accept_token(event: KeyPressEvent): |
|
227 | 277 | """Fill partial autosuggestion by token""" |
|
228 | 278 | b = event.current_buffer |
@@ -14,6 +14,7 b' from IPython.terminal.shortcuts.auto_suggest import (' | |||
|
14 | 14 | |
|
15 | 15 | from prompt_toolkit.history import InMemoryHistory |
|
16 | 16 | from prompt_toolkit.buffer import Buffer |
|
17 | from prompt_toolkit.document import Document | |
|
17 | 18 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory |
|
18 | 19 | |
|
19 | 20 | from unittest.mock import patch, Mock |
@@ -26,10 +27,7 b' def make_event(text, cursor, suggestion):' | |||
|
26 | 27 | event.current_buffer.text = text |
|
27 | 28 | event.current_buffer.cursor_position = cursor |
|
28 | 29 | event.current_buffer.suggestion.text = suggestion |
|
29 |
event.current_buffer.document = |
|
|
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 | |
|
30 | event.current_buffer.document = Document(text=text, cursor_position=cursor) | |
|
33 | 31 | return event |
|
34 | 32 | |
|
35 | 33 | |
@@ -252,6 +250,42 b' async def test_navigable_provider():' | |||
|
252 | 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 | 289 | def create_session_mock(): |
|
256 | 290 | session = Mock() |
|
257 | 291 | session.default_buffer = Buffer() |
General Comments 0
You need to be logged in to leave comments.
Login now