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