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