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