Completed
Pull Request — master (#269)
by
unknown
42:35 queued 17:33
created

Region.__init__()   B

Complexity

Conditions 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
c 1
b 0
f 1
dl 0
loc 20
ccs 0
cts 0
cp 0
crap 30
rs 8.5454
1
"""API for working with a Nvim Buffer."""
2 5
from .common import Remote
3 5
from ..compat import IS_PYTHON3
4
5
from collections import Iterable
6 5
import itertools
7
8
9 5
__all__ = ('Buffer')
10 3
11
12
if IS_PYTHON3:
13 5
    basestring = str
14
15 5
16 5
def adjust_index(idx, default=None):
17 5
    """Convert from python indexing convention to nvim indexing convention."""
18 5
    if idx is None:
19
        return default
20 5
    elif idx < 0:
21
        return idx - 1
22
    else:
23 5
        return idx
24
25
26
class Buffer(Remote):
27 5
28
    """A remote Nvim buffer."""
29 5
30
    _api_prefix = "nvim_buf_"
31 5
32
    def __len__(self):
33 5
        """Return the number of lines contained in a Buffer."""
34
        return self.request('nvim_buf_line_count')
35
36
    def __getitem__(self, idx):
37
        """Get a buffer line or slice by integer index.
38
39
        Indexes may be negative to specify positions from the end of the
40
        buffer. For example, -1 is the last line, -2 is the line before that
41
        and so on.
42
43 5
        When retrieving slices, omiting indexes(eg: `buffer[:]`) will bring
44 5
        the whole buffer.
45 5
        """
46 5
        if not isinstance(idx, slice):
47 5
            i = adjust_index(idx)
48 5
            return self.request('nvim_buf_get_lines', i, i + 1, True)[0]
49
        start = adjust_index(idx.start, 0)
50 5
        end = adjust_index(idx.stop, -1)
51
        return self.request('nvim_buf_get_lines', start, end, False)
52
53
    def __setitem__(self, idx, item):
54
        """Replace a buffer line or slice by integer index.
55
56
        Like with `__getitem__`, indexes may be negative.
57
58 5
        When replacing slices, omiting indexes(eg: `buffer[:]`) will replace
59 5
        the whole buffer.
60 5
        """
61 5
        if not isinstance(idx, slice):
62 5
            i = adjust_index(idx)
63 5
            lines = [item] if item is not None else []
64 5
            return self.request('nvim_buf_set_lines', i, i + 1, True, lines)
65 5
        lines = item if item is not None else []
66
        start = adjust_index(idx.start, 0)
67 5
        end = adjust_index(idx.stop, -1)
68
        return self.request('nvim_buf_set_lines', start, end, False, lines)
69
70
    def __iter__(self):
71
        """Iterate lines of a buffer.
72
73
        This will retrieve all lines locally before iteration starts. This
74
        approach is used because for most cases, the gain is much greater by
75
        minimizing the number of API calls by transfering all data needed to
76
        work.
77
        """
78
        lines = self[:]
79 5
        for line in lines:
80
            yield line
81
82
    def __delitem__(self, idx):
83
        """Delete line or slice of lines from the buffer.
84 5
85
        This is the same as __setitem__(idx, [])
86 5
        """
87
        self.__setitem__(idx, None)
88 5
89 5
    def append(self, lines, index=-1):
90 5
        """Append a string or list of lines to the buffer."""
91
        if isinstance(lines, (basestring, bytes)):
92 5
            lines = [lines]
93
        return self.request('nvim_buf_set_lines', index, index, True, lines)
94 5
95
    def mark(self, name):
96 5
        """Return (row, col) tuple for a named mark."""
97
        return self.request('nvim_buf_get_mark', name)
98
99
    def range(self, start, end):
100 5
        """Return a `Range` object, which represents part of the Buffer."""
101
        return Range(self, start, end)
102
103
    def add_highlight(self, hl_group, line, col_start=0,
104
                      col_end=-1, src_id=-1, async=None):
105
        """Add a highlight to the buffer."""
106
        if async is None:
107
            async = (src_id != 0)
108 5
        return self.request('nvim_buf_add_highlight', src_id, hl_group,
109
                            line, col_start, col_end, async=async)
110
111
    def clear_highlight(self, src_id, line_start=0, line_end=-1, async=True):
112
        """Clear highlights from the buffer."""
113 5
        self.request('nvim_buf_clear_highlight', src_id,
114
                     line_start, line_end, async=async)
115
116 5
    @property
117
    def name(self):
118 5
        """Get the buffer name."""
119
        return self.request('nvim_buf_get_name')
120
121 5
    @name.setter
122
    def name(self, value):
123 5
        """Set the buffer name. BufFilePre/BufFilePost are triggered."""
124
        return self.request('nvim_buf_set_name', value)
125
126 5
    @property
127
    def valid(self):
128 5
        """Return True if the buffer still exists."""
129
        return self.request('nvim_buf_is_valid')
130
131 5
    @property
132
    def number(self):
133
        """Get the buffer number."""
134 5
        return self.handle
135 5
136
    @property
137
    def visual_selection(self):
138
        """Get the current visual selection as a Region object."""
139
140 5
        rowstart, colstart = self.mark('<')
141
        rowend, colend     = self.mark('>')
142
143 5
        if rowend - rowstart == 0:
144
            # same line, one liners are easy
145
            return Region(self, rowstart, rowend, partials=(colstart, colend))
146
147
        vmode = self.nvim.funcs.visualmode()
0 ignored issues
show
Bug introduced by
The Instance of Buffer does not seem to have a member named nvim.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
148
149
        if vmode == "v":
150
            # standard visual mode
151
            line_count = rowend - rowstart + 1
152
            full_lines_count = line_count - 2  # all except the first and last line
153
            partiallist = [(colstart, None)] + full_lines_count*[(None, None)] + [(None, colend)]
154 5
            return Region(self, rowstart, rowend, partials=partiallist)
155
        elif vmode == "":
156
            # visual block mode
157
            return Region(self, rowstart, rowend, partials=(colstart, colend))
158
        elif vmode == "V":
159
            # visual line mode
160
            return Region(self, rowstart, rowend, partials=(None, None))
161
162
        return Region(self, startmark, endmark)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'startmark'
Loading history...
Comprehensibility Best Practice introduced by
Undefined variable 'endmark'
Loading history...
163
164
165
class Range(object):
166 5
    def __init__(self, buffer, start, end):
167
        self._buffer = buffer
168
        self.start = start - 1
169
        self.end = end - 1
170 5
171
    def __len__(self):
172
        return self.end - self.start + 1
173
174
    def __getitem__(self, idx):
175
        if not isinstance(idx, slice):
176 5
            return self._buffer[self._normalize_index(idx)]
177
        start = self._normalize_index(idx.start)
178
        end = self._normalize_index(idx.stop)
179
        if start is None:
180
            start = self.start
181
        if end is None:
182
            end = self.end + 1
183
        return self._buffer[start:end]
184
185
    def __setitem__(self, idx, lines):
186
        if not isinstance(idx, slice):
187
            self._buffer[self._normalize_index(idx)] = lines
188
            return
189
        start = self._normalize_index(idx.start)
190
        end = self._normalize_index(idx.stop)
191
        if start is None:
192
            start = self.start
193
        if end is None:
194
            end = self.end
195
        self._buffer[start:end + 1] = lines
196
197
    def __iter__(self):
198
        for i in range(self.start, self.end + 1):
199
            yield self._buffer[i]
200
201
    def append(self, lines, i=None):
202
        i = self._normalize_index(i)
203
        if i is None:
204
            i = self.end + 1
205
        self._buffer.append(lines, i)
206
207
    def _normalize_index(self, index):
208
        if index is None:
209
            return None
210
        if index < 0:
211
            index = self.end
212
        else:
213
            index += self.start
214
            if index > self.end:
215
                index = self.end
216
        return index
217
218
219
220
class Region(object):
221
    def __init__(self, buffer, startrow, endrow, partials=(None, None)):
222
        if not startrow <= endrow:
223
            raise ValueError("Negative span of rows provided.")
224
225
        # the range of the buffer this Region covers
226
        self._range = buffer.range(startrow, endrow)
227
228
        if not isinstance(partials[0], Iterable):
229
            # in this case we assume the provided partial
230
            # to be (int, int) and the default for all lines,
231
            # so create a list of appropriate length
232
            partials = [partials]*len(self._range)
233
        else:
234
            # we only need to assert this if we haven't created
235
            # that list ourselves
236
            if len(self._range) != len(partials):
237
                raise ValueError("length mismatch between partials and provided range")
238
239
        self._partials = [slice(*p) for p in partials]
240
        return
241
242
    def __len__(self):
243
        """
244
        Returns the number of characters covered by the Region
245
        """
246
        return sum([len(line[part]) for line, part in zip(self._range, self._partials)])
247
248
    def __getitem__(self, idx):
249
        if not isinstance(idx, slice):
250
            i = adjust_index(idx)
251
            return self.request('nvim_buf_get_lines', i, i + 1, True)[0]
0 ignored issues
show
Bug introduced by
The Instance of Region does not seem to have a member named request.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
252
        start = adjust_index(idx.start, 0)
253
        end = adjust_index(idx.stop, -1)
254
        #for i, (a, b, c) in enumerate(itertools.islice(zip(l1, l2, l3), 3, 7)):
255
        if not isinstance(idx, slice):
256
            return self._range[idx][self.partials[idx+1]]
0 ignored issues
show
Bug introduced by
The Instance of Region does not seem to have a member named partials.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
257
258
        start = idx.start or 0
259
        end = idx.stop or len(self._range)
260
261
        lineiter = itertools.islice(zip(self._range, self._partials), start, end)
262
        return [line[partial] for line, partial in lineiter]
263
264
    def __setitem__(self, idx, lineparts):
265
        if not isinstance(idx, slice):
266
            new_line = self._assemble_line(idx, lineparts[0])
267
            self._range[idx] = new_line
268
            return
269
        start = idx.start or 0
270
        end = idx.stop or len(self._range)
271
        if end - start != len(lineparts):
272
            raise ValueError("mismatch of target lines and inserts")
273
274
        lines = []
275
        for i in range(start, end):
276
            ni = i - start  # normalized index
277
            lines.append(self._assemble_line(i, lineparts[ni]))
278
        self._range[start:end] = lines
279
280
    def __iter__(self):
281
        return self
282
283
    def __next__(self):
284
        for line, partial in zip(self._range, self._partials):
285
            yield line[partial]
286
287
    def _assemble_line(self, i, replacement):
288
        start = self._partials[i].start or 0
289
        stop = self._partials[i].stop
290
        orig_prefix = self._range[i][:start]
291
292
        if stop:
293
            orig_suffix = self._range[i][stop:]
294
        else:
295
            orig_suffix = ""
296
        new_line = orig_prefix + replacement + orig_suffix
297
        return new_line
298