import weakref class EditError(Exception): pass class Cursor(object): def __init__(self, buffer, pos=0, color=None): self.buffer = buffer self.buffer._cursors[self] = True self._pos = pos self.color = color def copy(self): return Cursor(self.buffer, self.pos) def _getpos(self): return self._pos def _setpos(self, p): self.buffer.validate(p) self._pos = p pos = property(_getpos, _setpos) del _getpos, _setpos def to_line_start(self): self.pos = self.buffer.line_start(self.pos) def to_line_end(self): self.pos = self.buffer.line_end(self.pos) def _getcolumn(self): return self.pos - self.buffer.line_start(self.pos) column = property(_getcolumn) def readline(self): return self.buffer[self.buffer.line_start(self.pos) : self.buffer.line_end(self.pos) + 1] def move_up(self, n=1): p1 = self.pos self.to_line_start() p2 = self.pos column = p1 - p2 for i in range(n): p2 = self.pos self.pos -= 1 self.to_line_start() prevlinelength = p2 - self.pos - 1 self.pos += min(column, prevlinelength) def move_down(self, n=1): column = self.column for i in range(n): self.to_line_end() self.pos += 1 p1 = self.pos self.to_line_end() linelength = self.pos - p1 self.pos = p1 + min(column, linelength) class StableCursor(Cursor): pass class Buffer: def __init__(self, data='', filename=None): self.version = 0 self.filename = filename self._data = data self._cursors = weakref.WeakKeyDictionary() def validate(self, p): if p < 0: raise EditError("beginning of buffer") if p > len(self._data): raise EditError("end of buffer") def __len__(self): return len(self._data) def __getitem__(self, index): return self._data[index] def modify(self, start, length, ndata): if length < 0: raise ValueError("negative length") self.validate(start) self.validate(start+length) if not length and not ndata: return False self._data = self._data[:start] + ndata + self._data[start+length:] self.version += 1 for c in self._cursors.keys(): if start <= c.pos <= start+length: if isinstance(c, StableCursor) or (start==c.pos!=start+length): c.pos = start else: c.pos = start + len(ndata) elif c.pos > start+length: c.pos += len(ndata) - length return True def line_start(self, pos): return self._data.rfind('\n', 0, pos) + 1 def line_end(self, pos): result = self._data.find('\n', pos) if result < 0: result = len(self._data) return result def insert(self, pos, text): self.modify(pos, 0, text) def delete(self, pos, length): self.modify(pos, length, '') def notify(self): self.modify(0, 0, '') def iterlines(self, startpos=0): while startpos < len(self._data): n = self._data.find('\n', startpos) if n < 0: n = len(self._data) n += 1 yield self._data[startpos:n] startpos = n def getcursors(self): return self._cursors.keys()