aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py2/IPython/terminal/ptutils.py
blob: 90def9d209ba25b48ddcce986147fe828b3de810 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""prompt-toolkit utilities 
 
Everything in this module is a private API, 
not to be used outside IPython. 
""" 
 
# Copyright (c) IPython Development Team. 
# Distributed under the terms of the Modified BSD License. 
 
import unicodedata 
from wcwidth import wcwidth 
 
from IPython.utils.py3compat import PY3 
 
from IPython.core.completer import IPCompleter 
from prompt_toolkit.completion import Completer, Completion 
from prompt_toolkit.layout.lexers import Lexer 
from prompt_toolkit.layout.lexers import PygmentsLexer 
 
import pygments.lexers as pygments_lexers 
 
 
class IPythonPTCompleter(Completer): 
    """Adaptor to provide IPython completions to prompt_toolkit""" 
    def __init__(self, ipy_completer=None, shell=None, patch_stdout=None):
        if shell is None and ipy_completer is None: 
            raise TypeError("Please pass shell=an InteractiveShell instance.") 
        self._ipy_completer = ipy_completer 
        self.shell = shell 
        if patch_stdout is None:
            raise TypeError("Please pass patch_stdout")
        self.patch_stdout = patch_stdout
 
    @property 
    def ipy_completer(self): 
        if self._ipy_completer: 
            return self._ipy_completer 
        else: 
            return self.shell.Completer 
 
    def get_completions(self, document, complete_event): 
        if not document.current_line.strip(): 
            return 
 
        # Some bits of our completion system may print stuff (e.g. if a module
        # is imported). This context manager ensures that doesn't interfere with
        # the prompt.
        with self.patch_stdout():
            used, matches = self.ipy_completer.complete(
                                line_buffer=document.current_line,
                                cursor_pos=document.cursor_position_col
            )
        start_pos = -len(used) 
        for m in matches: 
            if not m: 
                # Guard against completion machinery giving us an empty string. 
                continue 
 
            m = unicodedata.normalize('NFC', m) 
 
            # When the first character of the completion has a zero length, 
            # then it's probably a decomposed unicode character. E.g. caused by 
            # the "\dot" completion. Try to compose again with the previous 
            # character. 
            if wcwidth(m[0]) == 0: 
                if document.cursor_position + start_pos > 0: 
                    char_before = document.text[document.cursor_position + start_pos - 1] 
                    m = unicodedata.normalize('NFC', char_before + m) 
 
                    # Yield the modified completion instead, if this worked. 
                    if wcwidth(m[0:1]) == 1: 
                        yield Completion(m, start_position=start_pos - 1) 
                        continue 
 
            # TODO: Use Jedi to determine meta_text 
            # (Jedi currently has a bug that results in incorrect information.) 
            # meta_text = '' 
            # yield Completion(m, start_position=start_pos, 
            #                  display_meta=meta_text) 
            yield Completion(m, start_position=start_pos) 
 
class IPythonPTLexer(Lexer): 
    """ 
    Wrapper around PythonLexer and BashLexer. 
    """ 
    def __init__(self): 
        l = pygments_lexers 
        self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer) 
        self.shell_lexer = PygmentsLexer(l.BashLexer) 
 
        self.magic_lexers = { 
            'HTML': PygmentsLexer(l.HtmlLexer), 
            'html': PygmentsLexer(l.HtmlLexer), 
            'javascript': PygmentsLexer(l.JavascriptLexer), 
            'js': PygmentsLexer(l.JavascriptLexer), 
            'perl': PygmentsLexer(l.PerlLexer), 
            'ruby': PygmentsLexer(l.RubyLexer), 
            'latex': PygmentsLexer(l.TexLexer), 
        } 
 
    def lex_document(self, cli, document): 
        text = document.text.lstrip() 
 
        lexer = self.python_lexer 
 
        if text.startswith('!') or text.startswith('%%bash'): 
            lexer = self.shell_lexer 
 
        elif text.startswith('%%'): 
            for magic, l in self.magic_lexers.items(): 
                if text.startswith('%%' + magic): 
                    lexer = l 
                    break 
 
        return lexer.lex_document(cli, document)