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