##// END OF EJS Templates
Updated to use the operating system's path delimiter in the displayed path suggestion #11441
Elliott Morgan Jobson -
Show More
@@ -1,166 +1,166
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(string, *, min_elide=30):
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
46 46 if len(object_parts) > 3:
47 47 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
48 48
49 49 elif len(file_parts) > 3:
50 return '{}/{}\N{HORIZONTAL ELLIPSIS}{}/{}'.format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
50 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
51 51
52 52 return string
53 53
54 54
55 55 def _adjust_completion_text_based_on_context(text, body, offset):
56 56 if text.endswith('=') and len(body) > offset and body[offset] is '=':
57 57 return text[:-1]
58 58 else:
59 59 return text
60 60
61 61
62 62 class IPythonPTCompleter(Completer):
63 63 """Adaptor to provide IPython completions to prompt_toolkit"""
64 64 def __init__(self, ipy_completer=None, shell=None):
65 65 if shell is None and ipy_completer is None:
66 66 raise TypeError("Please pass shell=an InteractiveShell instance.")
67 67 self._ipy_completer = ipy_completer
68 68 self.shell = shell
69 69
70 70 @property
71 71 def ipy_completer(self):
72 72 if self._ipy_completer:
73 73 return self._ipy_completer
74 74 else:
75 75 return self.shell.Completer
76 76
77 77 def get_completions(self, document, complete_event):
78 78 if not document.current_line.strip():
79 79 return
80 80 # Some bits of our completion system may print stuff (e.g. if a module
81 81 # is imported). This context manager ensures that doesn't interfere with
82 82 # the prompt.
83 83
84 84 with patch_stdout(), provisionalcompleter():
85 85 body = document.text
86 86 cursor_row = document.cursor_position_row
87 87 cursor_col = document.cursor_position_col
88 88 cursor_position = document.cursor_position
89 89 offset = cursor_to_position(body, cursor_row, cursor_col)
90 90 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
91 91
92 92 @staticmethod
93 93 def _get_completions(body, offset, cursor_position, ipyc):
94 94 """
95 95 Private equivalent of get_completions() use only for unit_testing.
96 96 """
97 97 debug = getattr(ipyc, 'debug', False)
98 98 completions = _deduplicate_completions(
99 99 body, ipyc.completions(body, offset))
100 100 for c in completions:
101 101 if not c.text:
102 102 # Guard against completion machinery giving us an empty string.
103 103 continue
104 104 text = unicodedata.normalize('NFC', c.text)
105 105 # When the first character of the completion has a zero length,
106 106 # then it's probably a decomposed unicode character. E.g. caused by
107 107 # the "\dot" completion. Try to compose again with the previous
108 108 # character.
109 109 if wcwidth(text[0]) == 0:
110 110 if cursor_position + c.start > 0:
111 111 char_before = body[c.start - 1]
112 112 fixed_text = unicodedata.normalize(
113 113 'NFC', char_before + text)
114 114
115 115 # Yield the modified completion instead, if this worked.
116 116 if wcwidth(text[0:1]) == 1:
117 117 yield Completion(fixed_text, start_position=c.start - offset - 1)
118 118 continue
119 119
120 120 # TODO: Use Jedi to determine meta_text
121 121 # (Jedi currently has a bug that results in incorrect information.)
122 122 # meta_text = ''
123 123 # yield Completion(m, start_position=start_pos,
124 124 # display_meta=meta_text)
125 125 display_text = c.text
126 126
127 127 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
128 128 if c.type == 'function':
129 129 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()'), display_meta=c.type+c.signature)
130 130 else:
131 131 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text), display_meta=c.type)
132 132
133 133 class IPythonPTLexer(Lexer):
134 134 """
135 135 Wrapper around PythonLexer and BashLexer.
136 136 """
137 137 def __init__(self):
138 138 l = pygments_lexers
139 139 self.python_lexer = PygmentsLexer(l.Python3Lexer)
140 140 self.shell_lexer = PygmentsLexer(l.BashLexer)
141 141
142 142 self.magic_lexers = {
143 143 'HTML': PygmentsLexer(l.HtmlLexer),
144 144 'html': PygmentsLexer(l.HtmlLexer),
145 145 'javascript': PygmentsLexer(l.JavascriptLexer),
146 146 'js': PygmentsLexer(l.JavascriptLexer),
147 147 'perl': PygmentsLexer(l.PerlLexer),
148 148 'ruby': PygmentsLexer(l.RubyLexer),
149 149 'latex': PygmentsLexer(l.TexLexer),
150 150 }
151 151
152 152 def lex_document(self, document):
153 153 text = document.text.lstrip()
154 154
155 155 lexer = self.python_lexer
156 156
157 157 if text.startswith('!') or text.startswith('%%bash'):
158 158 lexer = self.shell_lexer
159 159
160 160 elif text.startswith('%%'):
161 161 for magic, l in self.magic_lexers.items():
162 162 if text.startswith('%%' + magic):
163 163 lexer = l
164 164 break
165 165
166 166 return lexer.lex_document(document)
General Comments 0
You need to be logged in to leave comments. Login now