Completed
Pull Request — master (#340)
by Björn
21:46
created

Buffer.detach()   A

Complexity

Conditions 4

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 12.192

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 7
ccs 1
cts 5
cp 0.2
crap 12.192
rs 9.2
c 0
b 0
f 0
1
"""API for working with a Nvim Buffer."""
2 5
from .common import Remote
3 5
from ..compat import IS_PYTHON3, check_async
4
5
6 5
__all__ = ('Buffer')
7
8
9 5
if IS_PYTHON3:
10 3
    basestring = str
11
12
13 5
def adjust_index(idx, default=None):
14
    """Convert from python indexing convention to nvim indexing convention."""
15 5
    if idx is None:
16 5
        return default
17 5
    elif idx < 0:
18 5
        return idx - 1
19
    else:
20 5
        return idx
21
22
23 5
class Buffer(Remote):
24
25
    """A remote Nvim buffer."""
26
27 5
    _api_prefix = "nvim_buf_"
28
29 5
    def __len__(self):
30
        """Return the number of lines contained in a Buffer."""
31 5
        return self.request('nvim_buf_line_count')
32
33 5
    def __getitem__(self, idx):
34
        """Get a buffer line or slice by integer index.
35
36
        Indexes may be negative to specify positions from the end of the
37
        buffer. For example, -1 is the last line, -2 is the line before that
38
        and so on.
39
40
        When retrieving slices, omiting indexes(eg: `buffer[:]`) will bring
41
        the whole buffer.
42
        """
43 5
        if not isinstance(idx, slice):
44 5
            i = adjust_index(idx)
45 5
            return self.request('nvim_buf_get_lines', i, i + 1, True)[0]
46 5
        start = adjust_index(idx.start, 0)
47 5
        end = adjust_index(idx.stop, -1)
48 5
        return self.request('nvim_buf_get_lines', start, end, False)
49
50 5
    def __setitem__(self, idx, item):
51
        """Replace a buffer line or slice by integer index.
52
53
        Like with `__getitem__`, indexes may be negative.
54
55
        When replacing slices, omiting indexes(eg: `buffer[:]`) will replace
56
        the whole buffer.
57
        """
58 5
        if not isinstance(idx, slice):
59 5
            i = adjust_index(idx)
60 5
            lines = [item] if item is not None else []
61 5
            return self.request('nvim_buf_set_lines', i, i + 1, True, lines)
62 5
        lines = item if item is not None else []
63 5
        start = adjust_index(idx.start, 0)
64 5
        end = adjust_index(idx.stop, -1)
65 5
        return self.request('nvim_buf_set_lines', start, end, False, lines)
66
67 5
    def __iter__(self):
68
        """Iterate lines of a buffer.
69
70
        This will retrieve all lines locally before iteration starts. This
71
        approach is used because for most cases, the gain is much greater by
72
        minimizing the number of API calls by transfering all data needed to
73
        work.
74
        """
75
        lines = self[:]
76
        for line in lines:
77
            yield line
78
79 5
    def __delitem__(self, idx):
80
        """Delete line or slice of lines from the buffer.
81
82
        This is the same as __setitem__(idx, [])
83
        """
84 5
        self.__setitem__(idx, None)
85
86 5
    def append(self, lines, index=-1):
87
        """Append a string or list of lines to the buffer."""
88 5
        if isinstance(lines, (basestring, bytes)):
89 5
            lines = [lines]
90 5
        return self.request('nvim_buf_set_lines', index, index, True, lines)
91
92 5
    def mark(self, name):
93
        """Return (row, col) tuple for a named mark."""
94 5
        return self.request('nvim_buf_get_mark', name)
95
96 5
    def range(self, start, end):
97
        """Return a `Range` object, which represents part of the Buffer."""
98
        return Range(self, start, end)
99
100 5
    def add_highlight(self, hl_group, line, col_start=0,
101
                      col_end=-1, src_id=-1, async_=None,
102
                      **kwargs):
103
        """Add a highlight to the buffer."""
104
        async_ = check_async(async_, kwargs, src_id != 0)
105
        return self.request('nvim_buf_add_highlight', src_id, hl_group,
106
                            line, col_start, col_end, async_=async_)
107
108 5
    def clear_highlight(self, src_id, line_start=0, line_end=-1, async_=None,
109
                        **kwargs):
110
        """Clear highlights from the buffer."""
111
        async_ = check_async(async_, kwargs, True)
112
        self.request('nvim_buf_clear_highlight', src_id,
113 5
                     line_start, line_end, async_=async_)
114
115
    def update_highlights(self, src_id, hls, clear_start=0, clear_end=-1,
116 5
                          clear=False, async_=True):
117
        """Add or update highlights in batch to avoid unnecessary redraws.
118 5
119
        A `src_id` must have been allocated prior to use of this function. Use
120
        for instance `nvim.new_highlight_source()` to get a src_id for your
121 5
        plugin.
122
123 5
        `hls` should be a list of highlight items. Each item should be a list
124
        or tuple on the form `("GroupName", linenr, col_start, col_end)` or
125
        `("GroupName", linenr)` to highlight an entire line.
126 5
127
        By default existing highlights are preserved. Specify a line range with
128 5
        clear_start and clear_end to replace highlights in this range. As a
129
        shorthand, use clear=True to clear the entire buffer before adding the
130
        new highlights.
131 5
        """
132
        if clear and clear_start is None:
133
            clear_start = 0
134 5
        lua = self._session._get_lua_private()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _get_lua_private was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
135 5
        lua.update_highlights(self, src_id, hls, clear_start, clear_end,
136
                              async_=async_)
137
    @property
138
    def name(self):
139
        """Get the buffer name."""
140 5
        return self.request('nvim_buf_get_name')
141
142
    @name.setter
143 5
    def name(self, value):
144
        """Set the buffer name. BufFilePre/BufFilePost are triggered."""
145
        return self.request('nvim_buf_set_name', value)
146
147
    @property
148
    def valid(self):
149
        """Return True if the buffer still exists."""
150
        return self.request('nvim_buf_is_valid')
151
152
    @property
153
    def number(self):
154 5
        """Get the buffer number."""
155
        return self.handle
156
157
    def attach(self, cb):
158
        sess = self._session._session
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
159
        if self.handle not in sess.attached_buffers:
160
            a = BufAttachState()
161
            sess.attached_buffers[self.handle] = a
162
            a.callbacks.append(cb)
163
            self.api.attach(True, {})
164
        else:
165
            a = sess.attached_buffers[self.handle]
166 5
            a.callbacks.append(cb)
167
            cb(self, a.changedtick, a.lines)
168
169
        def detach():
170 5
            for i in range(len(a.callbacks)):
171
                if a.callbacks[i] is cb:
172
                    del a.callbacks[i]
173
                    return
174
            else:
0 ignored issues
show
Bug introduced by
The else clause is not necessary as the loop does not contain a break statement.

If the loop cannot exit early through the use of break, the else part will always be executed. You can therefore just leave off the else.

Loading history...
175
                raise ValueError("callback already detached")
176 5
177
        return detach
178
179
class BufAttachState(object):
180
    def __init__(self):
181
        self.callbacks = []
182
        self.lines = []
183
184
class Range(object):
185
    def __init__(self, buffer, start, end):
186
        self._buffer = buffer
187
        self.start = start - 1
188
        self.end = end - 1
189
190
    def __len__(self):
191
        return self.end - self.start + 1
192
193
    def __getitem__(self, idx):
194
        if not isinstance(idx, slice):
195
            return self._buffer[self._normalize_index(idx)]
196
        start = self._normalize_index(idx.start)
197
        end = self._normalize_index(idx.stop)
198
        if start is None:
199
            start = self.start
200
        if end is None:
201
            end = self.end + 1
202
        return self._buffer[start:end]
203
204
    def __setitem__(self, idx, lines):
205
        if not isinstance(idx, slice):
206
            self._buffer[self._normalize_index(idx)] = lines
207
            return
208
        start = self._normalize_index(idx.start)
209
        end = self._normalize_index(idx.stop)
210
        if start is None:
211
            start = self.start
212
        if end is None:
213
            end = self.end
214
        self._buffer[start:end + 1] = lines
215
216
    def __iter__(self):
217
        for i in range(self.start, self.end + 1):
218
            yield self._buffer[i]
219
220
    def append(self, lines, i=None):
221
        i = self._normalize_index(i)
222
        if i is None:
223
            i = self.end + 1
224
        self._buffer.append(lines, i)
225
226
    def _normalize_index(self, index):
227
        if index is None:
228
            return None
229
        if index < 0:
230
            index = self.end
231
        else:
232
            index += self.start
233
            if index > self.end:
234
                index = self.end
235
        return index
236