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