Completed
Push — master ( dfe802...608c2f )
by Lambda
01:39
created

build_echon_expr()   B

Complexity

Conditions 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 0
loc 22
rs 8.3411
1
"""Utility module."""
2
import re
3
4
_cached_encoding = None
5
6
ESCAPE_ECHO = str.maketrans({
7
    '"': '\\"',
8
    '\\': '\\\\',
9
})
10
11
IMPRINTABLE_REPRESENTS = {
12
    '\a': '^G',
13
    '\b': '^H',             # NOTE: Neovim: <BS>, Vim: ^H. Follow Vim.
14
    '\t': '^I',
15
    '\n': '^J',
16
    '\v': '^K',
17
    '\f': '^L',
18
    '\r': '^M',
19
    '\udc80\udcffX': '^@',  # NOTE: ^0 representation in Vim.
20
}
21
22
IMPRINTABLE_PATTERN = re.compile(r'(%s)' % '|'.join(
23
    IMPRINTABLE_REPRESENTS.keys()
24
))
25
26
27
def get_encoding(nvim):
28
    """Return a Vim's internal encoding.
29
30
    The retrieve encoding is cached to the function instance while encoding
31
    options should not be changed in Vim's live session (see :h encoding) to
32
    enhance performance.
33
34
    Args:
35
        nvim (neovim.Nvim): A ``neovim.Nvim`` instance.
36
37
    Returns:
38
        str: A Vim's internal encoding.
39
    """
40
    global _cached_encoding
41
    if _cached_encoding is None:
42
        _cached_encoding = nvim.options['encoding']
43
    return _cached_encoding
44
45
46
def ensure_bytes(nvim, seed):
47
    """Encode `str` to `bytes` if necessary and return.
48
49
    Args:
50
        nvim (neovim.Nvim): A ``neovim.Nvim`` instance.
51
        seed (AnyStr): A str or bytes instance.
52
53
    Example:
54
        >>> from unittest.mock import MagicMock
55
        >>> nvim = MagicMock()
56
        >>> nvim.options = {'encoding': 'utf-8'}
57
        >>> ensure_bytes(nvim, b'a')
58
        b'a'
59
        >>> ensure_bytes(nvim, 'a')
60
        b'a'
61
62
    Returns:
63
        bytes: A bytes represantation of ``seed``.
64
    """
65
    if isinstance(seed, str):
66
        encoding = get_encoding(nvim)
67
        return seed.encode(encoding, 'surrogateescape')
68
    return seed
69
70
71
def ensure_str(nvim, seed):
72
    """Decode `bytes` to `str` if necessary and return.
73
74
    Args:
75
        nvim (neovim.Nvim): A ``neovim.Nvim`` instance.
76
        seed (AnyStr): A str or bytes instance.
77
78
    Example:
79
        >>> from unittest.mock import MagicMock
80
        >>> nvim = MagicMock()
81
        >>> nvim.options = {'encoding': 'utf-8'}
82
        >>> ensure_str(nvim, b'a')
83
        'a'
84
        >>> ensure_str(nvim, 'a')
85
        'a'
86
87
    Returns:
88
        str: A str represantation of ``seed``.
89
    """
90
    if isinstance(seed, bytes):
91
        encoding = get_encoding(nvim)
92
        return seed.decode(encoding, 'surrogateescape')
93
    return seed
94
95
96
def int2char(nvim, code):
97
    """Return a corresponding char of `code`.
98
99
    It uses "nr2char()" in Vim script when 'encoding' option is not utf-8.
100
    Otherwise it uses "chr()" in Python to improve the performance.
101
102
    Args:
103
        nvim (neovim.Nvim): A ``neovim.Nvim`` instance.
104
        code (int): A int which represent a single character.
105
106
    Example:
107
        >>> from unittest.mock import MagicMock
108
        >>> nvim = MagicMock()
109
        >>> nvim.options = {'encoding': 'utf-8'}
110
        >>> int2char(nvim, 97)
111
        'a'
112
113
    Returns:
114
        str: A str of ``code``.
115
    """
116
    encoding = get_encoding(nvim)
117
    if encoding in ('utf-8', 'utf8'):
118
        return chr(code)
119
    return nvim.call('nr2char', code)
120
121
122
def int2repr(nvim, code):
123
    from .key import Key
124
    if isinstance(code, int):
125
        return int2char(nvim, code)
126
    return Key.represent(nvim, ensure_bytes(nvim, code))
127
128
129
def getchar(nvim, *args):
130
    """Call getchar and return int or bytes instance.
131
132
    Args:
133
        nvim (neovim.Nvim): A ``neovim.Nvim`` instance.
134
        *args: Arguments passed to getchar function in Vim.
135
136
    Returns:
137
        Union[int, bytes]: A int or bytes.
138
    """
139
    try:
140
        ret = nvim.call('getchar', *args)
141
        if isinstance(ret, int):
142
            return ret
143
        return ensure_bytes(nvim, ret)
144
    except nvim.error as e:
145
        # NOTE:
146
        # Vim returns 0x03 when ^C is pressed but Neovim.
147
        # Additionally, KeyboardInterrupt is not raised but
148
        # A general nvim.error and only the following dirty
149
        # implementation works.
150
        if str(e) == "b'Keyboard interrupt'":
151
            return 0x03  # ^C
152
        raise e
153
154
155
def build_echon_expr(text, hl='None'):
156
    """Build 'echon' expression.
157
158
    Imprintable characters (e.g. '^M') are replaced to a corresponding
159
    representations used in Vim's command-line interface.
160
161
    Args:
162
        text (str): A text to be echon.
163
        hl (str): A highline name. Default is 'None'.
164
165
    Return:
166
        str: A Vim's command expression for 'echon'.
167
    """
168
    if not IMPRINTABLE_PATTERN.search(text):
169
        return 'echohl %s|echon "%s"' % (
170
            hl, text.translate(ESCAPE_ECHO)
171
        )
172
    p = 'echohl %s|echon "%%s"' % hl
173
    i = 'echohl %s|echon "%%s"' % ('SpecialKey' if hl == 'None' else hl)
174
    return '|'.join(
175
        p % term if index % 2 == 0 else i % IMPRINTABLE_REPRESENTS[term]
176
        for index, term in enumerate(IMPRINTABLE_PATTERN.split(text))
177
    )
178
179
180
# http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
181
class Singleton(type):
182
    """A singleton metaclass."""
183
184
    instance = None
185
186
    def __call__(cls, *args, **kwargs):
187
        if not cls.instance:
188
            cls.instance = super().__call__(*args, **kwargs)
189
        return cls.instance
190