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