Completed
Pull Request — master (#269)
by
unknown
23:02
created

Buffer.__setitem__()   A

Complexity

Conditions 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
c 0
b 0
f 0
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 4
rs 9.2
1
"""API for working with a Nvim Buffer."""
2 5
from .common import Remote
3 5
from ..compat import IS_PYTHON3
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
        """Add a highlight to the buffer."""
103
        if async is None:
104
            async = (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=True):
109
        """Clear highlights from the buffer."""
110
        self.request('nvim_buf_clear_highlight', src_id,
111
                     line_start, line_end, async=async)
112
113 5
    @property
114
    def name(self):
115
        """Get the buffer name."""
116 5
        return self.request('nvim_buf_get_name')
117
118 5
    @name.setter
119
    def name(self, value):
120
        """Set the buffer name. BufFilePre/BufFilePost are triggered."""
121 5
        return self.request('nvim_buf_set_name', value)
122
123 5
    @property
124
    def valid(self):
125
        """Return True if the buffer still exists."""
126 5
        return self.request('nvim_buf_is_valid')
127
128 5
    @property
129
    def number(self):
130
        """Get the buffer number."""
131 5
        return self.handle
132
133
    @property
134 5
    def visual_selection(self):
135 5
        """Get the current visual selection as a Region object."""
136
        startmark = self.mark('<')
137
        endmark = self.mark('>')
138
        return Region(self, startmark, endmark)
139
140 5
141
class Range(object):
142
    def __init__(self, buffer, start, end):
143 5
        self._buffer = buffer
144
        self.start = start - 1
145
        self.end = end - 1
146
147
    def __len__(self):
148
        return self.end - self.start + 1
149
150
    def __getitem__(self, idx):
151
        if not isinstance(idx, slice):
152
            return self._buffer[self._normalize_index(idx)]
153
        start = self._normalize_index(idx.start)
154 5
        end = self._normalize_index(idx.stop)
155
        if start is None:
156
            start = self.start
157
        if end is None:
158
            end = self.end + 1
159
        return self._buffer[start:end]
160
161
    def __setitem__(self, idx, lines):
162
        if not isinstance(idx, slice):
163
            self._buffer[self._normalize_index(idx)] = lines
164
            return
165
        start = self._normalize_index(idx.start)
166 5
        end = self._normalize_index(idx.stop)
167
        if start is None:
168
            start = self.start
169
        if end is None:
170 5
            end = self.end
171
        self._buffer[start:end + 1] = lines
172
173
    def __iter__(self):
174
        for i in range(self.start, self.end + 1):
175
            yield self._buffer[i]
176 5
177
    def append(self, lines, i=None):
178
        i = self._normalize_index(i)
179
        if i is None:
180
            i = self.end + 1
181
        self._buffer.append(lines, i)
182
183
    def _normalize_index(self, index):
184
        if index is None:
185
            return None
186
        if index < 0:
187
            index = self.end
188
        else:
189
            index += self.start
190
            if index > self.end:
191
                index = self.end
192
        return index
193
194
195
class BlockRegion(object):
196
    def __init__(self, buffer, startmark, endmark):
197
        self._range = buffer.range(startmark[0], endmark[0] + 1)
198
        self.slice = slice(startmark[1], endmark[1] + 1)
199
200
    def __len__(self):
201
        return len(self._range)
202
203
    def __getitem__(self, idx):
204
        if not isinstance(idx, slice):
205
            return self._range[idx]
206
        start = idx.start
207
        end = idx.stop
208
        if start is None:
209
            start = self._range.start
210
        if end is None:
211
            end = self._range.stop
212
        return [l[self.slice] for l in self._range[start:end]]
213
214
    def __setitem__(self, idx, lineparts):
215
        if not isinstance(idx, slice):
216
            new_line = self._assemble_line(self._range[idx], lineparts)
217
            self._range[idx] = new_line
218
            return
219
        start = idx.start
220
        end = idx.stop
221
        if start is None:
222
            start = self._range.start
223
        if end is None:
224
            end = self._range.stop
225
        assert(end - start == len(lineparts))
226
        lines = []
227
        for i in range(start, end):
228
            ni = i - start  # normalized index
229
            lines.append(self._assemble_line(self._range[i], lineparts[ni]))
230
        self._range[start:end] = lines
231
232
    def __iter__(self):
233
        for i in range(self._range.start, self._range.end + 1):
234
            yield self._buffer[i][self.slice]
235
236
    def _assemble_line(self, orig_line, replacement):
237
            orig_prefix = orig_line[:self.slice.start]
238
            orig_suffix = orig_line[self.slice.stop:]
239
            new_line = orig_prefix + replacement + orig_suffix
240
            return new_line
241
242
243
class NormalRegion(object):
244
    def __init__(self, buffer, startmark, endmark):
245
        self._range = buffer.range(startmark[0], endmark[0] + 1)
246
        self.startindex = startmark[1]
247
        self.stopindex = endmark[1]+1
248
249
    def __len__(self):
250
        return len(self._range)
251
252
    def __getitem__(self, idx):
253
        if not isinstance(idx, slice):
254
            if idx == 0:  # first line
255
                return self._range[idx][self.startindex:]
256
            elif idx == len(self._range) - 1:  # last line
257
                return self._range[idx][:self.stopindex]
258
            else:
259
                return self._range[idx]
260
261
        start = idx.start
262
        end = idx.stop
263
        if start is None:
264
            start = self._range.start
265
        if end is None:
266
            end = self._range.stop
267
        return [self[i] for i in range(start, stop+1)]
268
269
270
    def _assemble_line(self, orig_line, replacement):
271
            orig_prefix = orig_line[:self.slice.start]
272
            orig_suffix = orig_line[self.slice.stop:]
273
            new_line = orig_prefix + replacement + orig_suffix
274
            return new_line
275
276
    def __setitem__(self, idx, lineparts):
277
        if not isinstance(idx, slice):
278
            if idx == 0:  # first line
279
                if isinstance(lineparts, str)
280
                    new_line = self._range[:self.start] + lineparts
281
                    self._range[idx] = new_line
282
                    return
283
                else:
284
                    raise Exception("you can only set strings, not {}".format(type(lineparts)))
285
            elif idx == len(self._range) - 1:  # last line
286
                if isinstance(lineparts, str)
287
                    new_line = self._range[:self.start] + lineparts
288
                    self._range[idx] = new_line
289
                    return
290
            else:
291
                self._range[idx] = new_line
292
                return
293
294
        start = idx.start
295
        end = idx.stop
296
        if start is None:
297
            start = self._range.start
298
        if end is None:
299
            end = self._range.stop
300
        assert(end - start == len(lineparts))
301
        lines = []
302
        for i in range(start, end):
303
            ni = i - start  # normalized index
304
            self[i] = lineparts[ni]
305
306
    def __iter__(self):
307
        for i in range(self._range.start, self._range.end + 1):
308
            yield self._buffer[i][self.slice]
309