##// END OF EJS Templates
Try to elide long completion based on user input....
Matthias Bussonnier -
Show More
1 NO CONTENT: modified file
@@ -1,168 +1,190 b''
1 1 """prompt-toolkit utilities
2 2
3 3 Everything in this module is a private API,
4 4 not to be used outside IPython.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import unicodedata
11 11 from wcwidth import wcwidth
12 12
13 13 from IPython.core.completer import (
14 14 provisionalcompleter, cursor_to_position,
15 15 _deduplicate_completions)
16 16 from prompt_toolkit.completion import Completer, Completion
17 17 from prompt_toolkit.lexers import Lexer
18 18 from prompt_toolkit.lexers import PygmentsLexer
19 19 from prompt_toolkit.patch_stdout import patch_stdout
20 20
21 21 import pygments.lexers as pygments_lexers
22 22 import os
23 23
24 24 _completion_sentinel = object()
25 25
26 def _elide(string, *, min_elide=30):
26 def _elide_point(string, *, min_elide=30):
27 27 """
28 28 If a string is long enough, and has at least 3 dots,
29 29 replace the middle part with ellipses.
30 30
31 31 If a string naming a file is long enough, and has at least 3 slashes,
32 32 replace the middle part with ellipses.
33 33
34 34 If three consecutive dots, or two consecutive dots are encountered these are
35 35 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
36 36 equivalents
37 37 """
38 38 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
39 39 string = string.replace('..','\N{TWO DOT LEADER}')
40 40 if len(string) < min_elide:
41 41 return string
42 42
43 43 object_parts = string.split('.')
44 44 file_parts = string.split(os.sep)
45 45 if file_parts[-1] == '':
46 46 file_parts.pop()
47 47
48 48 if len(object_parts) > 3:
49 49 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
50 50
51 51 elif len(file_parts) > 3:
52 52 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
53 53
54 54 return string
55 55
56 def _elide_typed(string, typed, *, min_elide=30):
57 """
58 Elide the middle of a long string if the beginning has already been typed.
59 """
60
61 if len(string) < min_elide:
62 return string
63 cut_how_much = len(typed)-3
64 if string.startswith(typed) and len(string)> len(typed):
65 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
66 return string
67
68 def _elide(string, typed, min_elide=30):
69 return _elide_typed(
70 _elide_point(string, min_elide=min_elide),
71 typed, min_elide=min_elide)
72
73
56 74
57 75 def _adjust_completion_text_based_on_context(text, body, offset):
58 76 if text.endswith('=') and len(body) > offset and body[offset] == '=':
59 77 return text[:-1]
60 78 else:
61 79 return text
62 80
63 81
64 82 class IPythonPTCompleter(Completer):
65 83 """Adaptor to provide IPython completions to prompt_toolkit"""
66 84 def __init__(self, ipy_completer=None, shell=None):
67 85 if shell is None and ipy_completer is None:
68 86 raise TypeError("Please pass shell=an InteractiveShell instance.")
69 87 self._ipy_completer = ipy_completer
70 88 self.shell = shell
71 89
72 90 @property
73 91 def ipy_completer(self):
74 92 if self._ipy_completer:
75 93 return self._ipy_completer
76 94 else:
77 95 return self.shell.Completer
78 96
79 97 def get_completions(self, document, complete_event):
80 98 if not document.current_line.strip():
81 99 return
82 100 # Some bits of our completion system may print stuff (e.g. if a module
83 101 # is imported). This context manager ensures that doesn't interfere with
84 102 # the prompt.
85 103
86 104 with patch_stdout(), provisionalcompleter():
87 105 body = document.text
88 106 cursor_row = document.cursor_position_row
89 107 cursor_col = document.cursor_position_col
90 108 cursor_position = document.cursor_position
91 109 offset = cursor_to_position(body, cursor_row, cursor_col)
110 try:
92 111 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
112 except Exception as e:
113 from traceback import print_tb
114 print_tb(e)
93 115
94 116 @staticmethod
95 117 def _get_completions(body, offset, cursor_position, ipyc):
96 118 """
97 119 Private equivalent of get_completions() use only for unit_testing.
98 120 """
99 121 debug = getattr(ipyc, 'debug', False)
100 122 completions = _deduplicate_completions(
101 123 body, ipyc.completions(body, offset))
102 124 for c in completions:
103 125 if not c.text:
104 126 # Guard against completion machinery giving us an empty string.
105 127 continue
106 128 text = unicodedata.normalize('NFC', c.text)
107 129 # When the first character of the completion has a zero length,
108 130 # then it's probably a decomposed unicode character. E.g. caused by
109 131 # the "\dot" completion. Try to compose again with the previous
110 132 # character.
111 133 if wcwidth(text[0]) == 0:
112 134 if cursor_position + c.start > 0:
113 135 char_before = body[c.start - 1]
114 136 fixed_text = unicodedata.normalize(
115 137 'NFC', char_before + text)
116 138
117 139 # Yield the modified completion instead, if this worked.
118 140 if wcwidth(text[0:1]) == 1:
119 141 yield Completion(fixed_text, start_position=c.start - offset - 1)
120 142 continue
121 143
122 144 # TODO: Use Jedi to determine meta_text
123 145 # (Jedi currently has a bug that results in incorrect information.)
124 146 # meta_text = ''
125 147 # yield Completion(m, start_position=start_pos,
126 148 # display_meta=meta_text)
127 149 display_text = c.text
128 150
129 151 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
130 152 if c.type == 'function':
131 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()'), display_meta=c.type+c.signature)
153 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
132 154 else:
133 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text), display_meta=c.type)
155 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
134 156
135 157 class IPythonPTLexer(Lexer):
136 158 """
137 159 Wrapper around PythonLexer and BashLexer.
138 160 """
139 161 def __init__(self):
140 162 l = pygments_lexers
141 163 self.python_lexer = PygmentsLexer(l.Python3Lexer)
142 164 self.shell_lexer = PygmentsLexer(l.BashLexer)
143 165
144 166 self.magic_lexers = {
145 167 'HTML': PygmentsLexer(l.HtmlLexer),
146 168 'html': PygmentsLexer(l.HtmlLexer),
147 169 'javascript': PygmentsLexer(l.JavascriptLexer),
148 170 'js': PygmentsLexer(l.JavascriptLexer),
149 171 'perl': PygmentsLexer(l.PerlLexer),
150 172 'ruby': PygmentsLexer(l.RubyLexer),
151 173 'latex': PygmentsLexer(l.TexLexer),
152 174 }
153 175
154 176 def lex_document(self, document):
155 177 text = document.text.lstrip()
156 178
157 179 lexer = self.python_lexer
158 180
159 181 if text.startswith('!') or text.startswith('%%bash'):
160 182 lexer = self.shell_lexer
161 183
162 184 elif text.startswith('%%'):
163 185 for magic, l in self.magic_lexers.items():
164 186 if text.startswith('%%' + magic):
165 187 lexer = l
166 188 break
167 189
168 190 return lexer.lex_document(document)
General Comments 0
You need to be logged in to leave comments. Login now