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