##// END OF EJS Templates
Autosuggest: only navigate on edges of doc, show in multi-line doc
krassowski -
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 NavigableAutoSuggestFromHistory
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 'complete_in_thread': False,
583 "complete_in_thread": False,
581 'lexer':IPythonPTLexer(),
584 "lexer": IPythonPTLexer(),
582 'reserve_space_for_menu':self.space_for_menu,
585 "reserve_space_for_menu": self.space_for_menu,
583 'message': get_message,
586 "message": get_message,
584 'prompt_continuation': (
587 "prompt_continuation": (
585 lambda width, lineno, is_soft_wrap:
588 lambda width, lineno, is_soft_wrap: PygmentsTokens(
586 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
589 self.prompts.continuation_prompt_tokens(width)
587 'multiline': True,
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 'input_processors': [ConditionalProcessor(
597 ConditionalProcessor(
593 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
598 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
594 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
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), eager=True)(
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=has_suggestion & has_focus(DEFAULT_BUFFER))(
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.text.rsplit("\n", 1)[-1]
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 = Mock()
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