##// END OF EJS Templates
Backport PR #10153: Use prompt_toolkit's patch_stdout_context when finding completions...
Min RK -
Show More
@@ -1,108 +1,112 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.utils.py3compat import PY3
13 from IPython.utils.py3compat import PY3
14
14
15 from IPython.core.completer import IPCompleter
15 from IPython.core.completer import IPCompleter
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
22
23 class IPythonPTCompleter(Completer):
23 class IPythonPTCompleter(Completer):
24 """Adaptor to provide IPython completions to prompt_toolkit"""
24 """Adaptor to provide IPython completions to prompt_toolkit"""
25 def __init__(self, ipy_completer=None, shell=None):
25 def __init__(self, ipy_completer=None, shell=None):
26 if shell is None and ipy_completer is None:
26 if shell is None and ipy_completer is None:
27 raise TypeError("Please pass shell=an InteractiveShell instance.")
27 raise TypeError("Please pass shell=an InteractiveShell instance.")
28 self._ipy_completer = ipy_completer
28 self._ipy_completer = ipy_completer
29 self.shell = shell
29 self.shell = shell
30
30
31 @property
31 @property
32 def ipy_completer(self):
32 def ipy_completer(self):
33 if self._ipy_completer:
33 if self._ipy_completer:
34 return self._ipy_completer
34 return self._ipy_completer
35 else:
35 else:
36 return self.shell.Completer
36 return self.shell.Completer
37
37
38 def get_completions(self, document, complete_event):
38 def get_completions(self, document, complete_event):
39 if not document.current_line.strip():
39 if not document.current_line.strip():
40 return
40 return
41
41
42 used, matches = self.ipy_completer.complete(
42 # Some bits of our completion system may print stuff (e.g. if a module
43 line_buffer=document.current_line,
43 # is imported). This context manager ensures that doesn't interfere with
44 cursor_pos=document.cursor_position_col
44 # the prompt.
45 )
45 with self.shell.pt_cli.patch_stdout_context():
46 used, matches = self.ipy_completer.complete(
47 line_buffer=document.current_line,
48 cursor_pos=document.cursor_position_col
49 )
46 start_pos = -len(used)
50 start_pos = -len(used)
47 for m in matches:
51 for m in matches:
48 if not m:
52 if not m:
49 # Guard against completion machinery giving us an empty string.
53 # Guard against completion machinery giving us an empty string.
50 continue
54 continue
51
55
52 m = unicodedata.normalize('NFC', m)
56 m = unicodedata.normalize('NFC', m)
53
57
54 # When the first character of the completion has a zero length,
58 # When the first character of the completion has a zero length,
55 # then it's probably a decomposed unicode character. E.g. caused by
59 # then it's probably a decomposed unicode character. E.g. caused by
56 # the "\dot" completion. Try to compose again with the previous
60 # the "\dot" completion. Try to compose again with the previous
57 # character.
61 # character.
58 if wcwidth(m[0]) == 0:
62 if wcwidth(m[0]) == 0:
59 if document.cursor_position + start_pos > 0:
63 if document.cursor_position + start_pos > 0:
60 char_before = document.text[document.cursor_position + start_pos - 1]
64 char_before = document.text[document.cursor_position + start_pos - 1]
61 m = unicodedata.normalize('NFC', char_before + m)
65 m = unicodedata.normalize('NFC', char_before + m)
62
66
63 # Yield the modified completion instead, if this worked.
67 # Yield the modified completion instead, if this worked.
64 if wcwidth(m[0:1]) == 1:
68 if wcwidth(m[0:1]) == 1:
65 yield Completion(m, start_position=start_pos - 1)
69 yield Completion(m, start_position=start_pos - 1)
66 continue
70 continue
67
71
68 # TODO: Use Jedi to determine meta_text
72 # TODO: Use Jedi to determine meta_text
69 # (Jedi currently has a bug that results in incorrect information.)
73 # (Jedi currently has a bug that results in incorrect information.)
70 # meta_text = ''
74 # meta_text = ''
71 # yield Completion(m, start_position=start_pos,
75 # yield Completion(m, start_position=start_pos,
72 # display_meta=meta_text)
76 # display_meta=meta_text)
73 yield Completion(m, start_position=start_pos)
77 yield Completion(m, start_position=start_pos)
74
78
75 class IPythonPTLexer(Lexer):
79 class IPythonPTLexer(Lexer):
76 """
80 """
77 Wrapper around PythonLexer and BashLexer.
81 Wrapper around PythonLexer and BashLexer.
78 """
82 """
79 def __init__(self):
83 def __init__(self):
80 l = pygments_lexers
84 l = pygments_lexers
81 self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer)
85 self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer)
82 self.shell_lexer = PygmentsLexer(l.BashLexer)
86 self.shell_lexer = PygmentsLexer(l.BashLexer)
83
87
84 self.magic_lexers = {
88 self.magic_lexers = {
85 'HTML': PygmentsLexer(l.HtmlLexer),
89 'HTML': PygmentsLexer(l.HtmlLexer),
86 'html': PygmentsLexer(l.HtmlLexer),
90 'html': PygmentsLexer(l.HtmlLexer),
87 'javascript': PygmentsLexer(l.JavascriptLexer),
91 'javascript': PygmentsLexer(l.JavascriptLexer),
88 'js': PygmentsLexer(l.JavascriptLexer),
92 'js': PygmentsLexer(l.JavascriptLexer),
89 'perl': PygmentsLexer(l.PerlLexer),
93 'perl': PygmentsLexer(l.PerlLexer),
90 'ruby': PygmentsLexer(l.RubyLexer),
94 'ruby': PygmentsLexer(l.RubyLexer),
91 'latex': PygmentsLexer(l.TexLexer),
95 'latex': PygmentsLexer(l.TexLexer),
92 }
96 }
93
97
94 def lex_document(self, cli, document):
98 def lex_document(self, cli, document):
95 text = document.text.lstrip()
99 text = document.text.lstrip()
96
100
97 lexer = self.python_lexer
101 lexer = self.python_lexer
98
102
99 if text.startswith('!') or text.startswith('%%bash'):
103 if text.startswith('!') or text.startswith('%%bash'):
100 lexer = self.shell_lexer
104 lexer = self.shell_lexer
101
105
102 elif text.startswith('%%'):
106 elif text.startswith('%%'):
103 for magic, l in self.magic_lexers.items():
107 for magic, l in self.magic_lexers.items():
104 if text.startswith('%%' + magic):
108 if text.startswith('%%' + magic):
105 lexer = l
109 lexer = l
106 break
110 break
107
111
108 return lexer.lex_document(cli, document)
112 return lexer.lex_document(cli, document)
General Comments 0
You need to be logged in to leave comments. Login now