##// END OF EJS Templates
Implement token-by-token autosuggestions
krassowski -
Show More
@@ -333,6 +333,7 b' def create_ipython_shortcuts(shell, for_all_platforms: bool = False):'
333 333 kb.add("escape", "f", filter=focused_insert_vi & ebivim)(
334 334 autosuggestions.accept_word
335 335 )
336 kb.add("c-right", filter=has_focus(DEFAULT_BUFFER))(autosuggestions.accept_token)
336 337
337 338 # Simple Control keybindings
338 339 key_cmd_dict = {
@@ -1,7 +1,13 b''
1 1 import re
2 import tokenize
3 from io import StringIO
4 from typing import List, Optional
5
2 6 from prompt_toolkit.key_binding import KeyPressEvent
3 7 from prompt_toolkit.key_binding.bindings import named_commands as nc
4 8
9 from IPython.utils.tokenutil import generate_tokens
10
5 11
6 12 # Needed for to accept autosuggestions in vi insert mode
7 13 def accept_in_vi_insert_mode(event: KeyPressEvent):
@@ -18,7 +24,7 b' def accept_in_vi_insert_mode(event: KeyPressEvent):'
18 24 nc.end_of_line(event)
19 25
20 26
21 def accept(event):
27 def accept(event: KeyPressEvent):
22 28 """Accept suggestion"""
23 29 b = event.current_buffer
24 30 suggestion = b.suggestion
@@ -28,7 +34,7 b' def accept(event):'
28 34 nc.forward_char(event)
29 35
30 36
31 def accept_word(event):
37 def accept_word(event: KeyPressEvent):
32 38 """Fill partial suggestion by word"""
33 39 b = event.current_buffer
34 40 suggestion = b.suggestion
@@ -37,3 +43,45 b' def accept_word(event):'
37 43 b.insert_text(next((x for x in t if x), ""))
38 44 else:
39 45 nc.forward_word(event)
46
47
48 def accept_token(event: KeyPressEvent):
49 """Fill partial suggestion by token"""
50 b = event.current_buffer
51 suggestion = b.suggestion
52
53 if suggestion:
54 prefix = b.text
55 text = prefix + suggestion.text
56
57 tokens: List[Optional[str]] = [None, None, None]
58 substings = [""]
59 i = 0
60
61 for token in generate_tokens(StringIO(text).readline):
62 if token.type == tokenize.NEWLINE:
63 index = len(text)
64 else:
65 index = text.index(token[1], len(substings[-1]))
66 substings.append(text[:index])
67 tokenized_so_far = substings[-1]
68 if tokenized_so_far.startswith(prefix):
69 if i == 0 and len(tokenized_so_far) > len(prefix):
70 tokens[0] = tokenized_so_far[len(prefix) :]
71 substings.append(tokenized_so_far)
72 i += 1
73 tokens[i] = token[1]
74 if i == 2:
75 break
76 i += 1
77
78 if tokens[0]:
79 to_insert: str
80 insert_text = substings[-2]
81 if tokens[1] and len(tokens[1]) == 1:
82 insert_text = substings[-1]
83 to_insert = insert_text[len(prefix) :]
84 b.insert_text(to_insert)
85 return
86
87 nc.forward_word(event)
@@ -1,13 +1,17 b''
1 1 import pytest
2 from IPython.terminal.shortcuts import _apply_autosuggest
2 from IPython.terminal.shortcuts.autosuggestions import (
3 accept_in_vi_insert_mode,
4 accept_token,
5 )
3 6
4 from unittest.mock import Mock
7 from unittest.mock import patch, Mock
5 8
6 9
7 10 def make_event(text, cursor, suggestion):
8 11 event = Mock()
9 12 event.current_buffer = Mock()
10 13 event.current_buffer.suggestion = Mock()
14 event.current_buffer.text = text
11 15 event.current_buffer.cursor_position = cursor
12 16 event.current_buffer.suggestion.text = suggestion
13 17 event.current_buffer.document = Mock()
@@ -32,9 +36,59 b' def test_autosuggest_at_EOL(text, cursor, suggestion, called):'
32 36
33 37 event = make_event(text, cursor, suggestion)
34 38 event.current_buffer.insert_text = Mock()
35 _apply_autosuggest(event)
39 accept_in_vi_insert_mode(event)
36 40 if called:
37 41 event.current_buffer.insert_text.assert_called()
38 42 else:
39 43 event.current_buffer.insert_text.assert_not_called()
40 44 # event.current_buffer.document.get_end_of_line_position.assert_called()
45
46
47 @pytest.mark.parametrize(
48 "text, suggestion, expected",
49 [
50 ("", "def out(tag: str, n=50):", "def "),
51 ("d", "ef out(tag: str, n=50):", "ef "),
52 ("de ", "f out(tag: str, n=50):", "f "),
53 ("def", " out(tag: str, n=50):", " "),
54 ("def ", "out(tag: str, n=50):", "out("),
55 ("def o", "ut(tag: str, n=50):", "ut("),
56 ("def ou", "t(tag: str, n=50):", "t("),
57 ("def out", "(tag: str, n=50):", "("),
58 ("def out(", "tag: str, n=50):", "tag: "),
59 ("def out(t", "ag: str, n=50):", "ag: "),
60 ("def out(ta", "g: str, n=50):", "g: "),
61 ("def out(tag", ": str, n=50):", ": "),
62 ("def out(tag:", " str, n=50):", " "),
63 ("def out(tag: ", "str, n=50):", "str, "),
64 ("def out(tag: s", "tr, n=50):", "tr, "),
65 ("def out(tag: st", "r, n=50):", "r, "),
66 ("def out(tag: str", ", n=50):", ", n"),
67 ("def out(tag: str,", " n=50):", " n"),
68 ("def out(tag: str, ", "n=50):", "n="),
69 ("def out(tag: str, n", "=50):", "="),
70 ("def out(tag: str, n=", "50):", "50)"),
71 ("def out(tag: str, n=5", "0):", "0)"),
72 ("def out(tag: str, n=50", "):", "):"),
73 ("def out(tag: str, n=50)", ":", ":"),
74 ],
75 )
76 def test_autosuggest_token(text, suggestion, expected):
77 event = make_event(text, len(text), suggestion)
78 event.current_buffer.insert_text = Mock()
79 accept_token(event)
80 assert event.current_buffer.insert_text.called
81 assert event.current_buffer.insert_text.call_args[0] == (expected,)
82
83
84 def test_autosuggest_token_empty():
85 full = "def out(tag: str, n=50):"
86 event = make_event(full, len(full), "")
87 event.current_buffer.insert_text = Mock()
88
89 with patch(
90 "prompt_toolkit.key_binding.bindings.named_commands.forward_word"
91 ) as forward_word:
92 accept_token(event)
93 assert not event.current_buffer.insert_text.called
94 assert forward_word.called
General Comments 0
You need to be logged in to leave comments. Login now