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