--- /dev/null
+"""API for working with a Nvim Buffer."""
+from pynvim.api.common import Remote
+from pynvim.compat import IS_PYTHON3, check_async
+
+
+__all__ = ('Buffer')
+
+
+if IS_PYTHON3:
+ basestring = str
+
+
+def adjust_index(idx, default=None):
+ """Convert from python indexing convention to nvim indexing convention."""
+ if idx is None:
+ return default
+ elif idx < 0:
+ return idx - 1
+ else:
+ return idx
+
+
+class Buffer(Remote):
+
+ """A remote Nvim buffer."""
+
+ _api_prefix = "nvim_buf_"
+
+ def __len__(self):
+ """Return the number of lines contained in a Buffer."""
+ return self.request('nvim_buf_line_count')
+
+ def __getitem__(self, idx):
+ """Get a buffer line or slice by integer index.
+
+ Indexes may be negative to specify positions from the end of the
+ buffer. For example, -1 is the last line, -2 is the line before that
+ and so on.
+
+ When retrieving slices, omiting indexes(eg: `buffer[:]`) will bring
+ the whole buffer.
+ """
+ if not isinstance(idx, slice):
+ i = adjust_index(idx)
+ return self.request('nvim_buf_get_lines', i, i + 1, True)[0]
+ start = adjust_index(idx.start, 0)
+ end = adjust_index(idx.stop, -1)
+ return self.request('nvim_buf_get_lines', start, end, False)
+
+ def __setitem__(self, idx, item):
+ """Replace a buffer line or slice by integer index.
+
+ Like with `__getitem__`, indexes may be negative.
+
+ When replacing slices, omiting indexes(eg: `buffer[:]`) will replace
+ the whole buffer.
+ """
+ if not isinstance(idx, slice):
+ i = adjust_index(idx)
+ lines = [item] if item is not None else []
+ return self.request('nvim_buf_set_lines', i, i + 1, True, lines)
+ lines = item if item is not None else []
+ start = adjust_index(idx.start, 0)
+ end = adjust_index(idx.stop, -1)
+ return self.request('nvim_buf_set_lines', start, end, False, lines)
+
+ def __iter__(self):
+ """Iterate lines of a buffer.
+
+ This will retrieve all lines locally before iteration starts. This
+ approach is used because for most cases, the gain is much greater by
+ minimizing the number of API calls by transfering all data needed to
+ work.
+ """
+ lines = self[:]
+ for line in lines:
+ yield line
+
+ def __delitem__(self, idx):
+ """Delete line or slice of lines from the buffer.
+
+ This is the same as __setitem__(idx, [])
+ """
+ self.__setitem__(idx, None)
+
+ def __ne__(self, other):
+ """Test inequality of Buffers.
+
+ Necessary for Python 2 compatibility.
+ """
+ return not self.__eq__(other)
+
+ def append(self, lines, index=-1):
+ """Append a string or list of lines to the buffer."""
+ if isinstance(lines, (basestring, bytes)):
+ lines = [lines]
+ return self.request('nvim_buf_set_lines', index, index, True, lines)
+
+ def mark(self, name):
+ """Return (row, col) tuple for a named mark."""
+ return self.request('nvim_buf_get_mark', name)
+
+ def range(self, start, end):
+ """Return a `Range` object, which represents part of the Buffer."""
+ return Range(self, start, end)
+
+ def add_highlight(self, hl_group, line, col_start=0,
+ col_end=-1, src_id=-1, async_=None,
+ **kwargs):
+ """Add a highlight to the buffer."""
+ async_ = check_async(async_, kwargs, src_id != 0)
+ return self.request('nvim_buf_add_highlight', src_id, hl_group,
+ line, col_start, col_end, async_=async_)
+
+ def clear_highlight(self, src_id, line_start=0, line_end=-1, async_=None,
+ **kwargs):
+ """Clear highlights from the buffer."""
+ async_ = check_async(async_, kwargs, True)
+ self.request('nvim_buf_clear_highlight', src_id,
+ line_start, line_end, async_=async_)
+
+ def update_highlights(self, src_id, hls, clear_start=0, clear_end=-1,
+ clear=False, async_=True):
+ """Add or update highlights in batch to avoid unnecessary redraws.
+
+ A `src_id` must have been allocated prior to use of this function. Use
+ for instance `nvim.new_highlight_source()` to get a src_id for your
+ plugin.
+
+ `hls` should be a list of highlight items. Each item should be a list
+ or tuple on the form `("GroupName", linenr, col_start, col_end)` or
+ `("GroupName", linenr)` to highlight an entire line.
+
+ By default existing highlights are preserved. Specify a line range with
+ clear_start and clear_end to replace highlights in this range. As a
+ shorthand, use clear=True to clear the entire buffer before adding the
+ new highlights.
+ """
+ if clear and clear_start is None:
+ clear_start = 0
+ lua = self._session._get_lua_private()
+ lua.update_highlights(self, src_id, hls, clear_start, clear_end,
+ async_=async_)
+
+ @property
+ def name(self):
+ """Get the buffer name."""
+ return self.request('nvim_buf_get_name')
+
+ @name.setter
+ def name(self, value):
+ """Set the buffer name. BufFilePre/BufFilePost are triggered."""
+ return self.request('nvim_buf_set_name', value)
+
+ @property
+ def valid(self):
+ """Return True if the buffer still exists."""
+ return self.request('nvim_buf_is_valid')
+
+ @property
+ def number(self):
+ """Get the buffer number."""
+ return self.handle
+
+
+class Range(object):
+ def __init__(self, buffer, start, end):
+ self._buffer = buffer
+ self.start = start - 1
+ self.end = end - 1
+
+ def __len__(self):
+ return self.end - self.start + 1
+
+ def __getitem__(self, idx):
+ if not isinstance(idx, slice):
+ return self._buffer[self._normalize_index(idx)]
+ start = self._normalize_index(idx.start)
+ end = self._normalize_index(idx.stop)
+ if start is None:
+ start = self.start
+ if end is None:
+ end = self.end + 1
+ return self._buffer[start:end]
+
+ def __setitem__(self, idx, lines):
+ if not isinstance(idx, slice):
+ self._buffer[self._normalize_index(idx)] = lines
+ return
+ start = self._normalize_index(idx.start)
+ end = self._normalize_index(idx.stop)
+ if start is None:
+ start = self.start
+ if end is None:
+ end = self.end
+ self._buffer[start:end + 1] = lines
+
+ def __iter__(self):
+ for i in range(self.start, self.end + 1):
+ yield self._buffer[i]
+
+ def append(self, lines, i=None):
+ i = self._normalize_index(i)
+ if i is None:
+ i = self.end + 1
+ self._buffer.append(lines, i)
+
+ def _normalize_index(self, index):
+ if index is None:
+ return None
+ if index < 0:
+ index = self.end
+ else:
+ index += self.start
+ if index > self.end:
+ index = self.end
+ return index