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