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