From 4bca49f807544bd948a5f5f78e3787411252650f Mon Sep 17 00:00:00 2001 From: Petter Rodhelind Date: Thu, 22 Feb 2018 23:15:13 +0100 Subject: first commit --- testfiles/text.txt | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 testfiles/text.txt (limited to 'testfiles/text.txt') diff --git a/testfiles/text.txt b/testfiles/text.txt new file mode 100644 index 0000000..c2dc4af --- /dev/null +++ b/testfiles/text.txt @@ -0,0 +1,229 @@ +package main + +import ( + "unicode" + + "github.com/pkg/errors" + "github.com/prodhe/poe/gapbuffer" +) + +type Text struct { + buf *gapbuffer.Buffer + cursorpos int + p0, p1 int // not implemented + history History +} + +// Sync synchronizes the underlying buffer with the cursor position. This should be called before every call that manipulates data in the buffer. +func (t *Text) Sync() { + t.buf.Seek(t.cursorpos) +} + +func (t *Text) Write(p []byte) (int, error) { + c := Change{t.cursorpos, ActionInsert, p} + n, err := t.commit(c) + if err != nil { + return n, err + } + t.history.Do(c) + return n, nil +} + +func (t *Text) Delete() (int, error) { + if t.cursorpos <= 0 { + return 0, errors.New("out of range") + } + b, _ := t.buf.ByteAt(t.cursorpos - 1) + c := Change{t.cursorpos, ActionDelete, []byte{b}} + n, err := t.commit(c) + if err != nil { + return n, err + } + t.history.Do(c) + return n, nil +} + +func (t *Text) Undo() error { + change, err := t.history.Undo() + if err != nil { + return errors.Wrap(err, "undo") + } + t.commit(change) + return nil +} + +func (t *Text) Redo() error { + change, err := t.history.Redo() + if err != nil { + return errors.Wrap(err, "redo") + } + t.commit(change) + return nil +} + +func (t *Text) commit(c Change) (int, error) { + switch c.action { + case ActionInsert: + t.SetCursor(c.offset, 0) + t.Sync() + n, err := t.buf.Write(c.content) + if err != nil { + return 0, err + } + t.SetCursor(n, 1) + return n, err + case ActionDelete: + t.SetCursor(c.offset, 0) + t.Sync() + var ds []byte + for i := c.offset; i <= c.offset; i++ { + ds = append(ds, t.buf.Delete()) + } + t.SetCursor(len(ds), -1) + return len(ds), nil + default: + return 0, errors.New("invalid action in change") + } +} + +// WordOffset returns the absolute offset for the beginning of the word and the end. A word is a letter/digit sequence of bytes separated by anything else. It scans from the current cursor position. +func (t *Text) WordOffset() (start, end int) { + start, end = t.cursorpos, t.cursorpos + c, _ := t.buf.ByteAt(start) + for unicode.IsLetter(rune(c)) || unicode.IsDigit(rune(c)) { + start-- + c, _ = t.buf.ByteAt(start) + } + if start < t.cursorpos { + start++ + } + c, _ = t.buf.ByteAt(end) + for unicode.IsLetter(rune(c)) || unicode.IsDigit(rune(c)) { + end++ + c, _ = t.buf.ByteAt(end) + } + return start, end +} + +// NextNL returns number of bytes from given position to the nearest next new line. +func (t *Text) NextNL(start int) int { + if start >= t.buf.Len() { + return 0 + } + n := start + offset := 0 + if c, _ := t.buf.ByteAt(n); c == '\n' { + n++ + offset++ + } + for { + c, _ := t.buf.ByteAt(n) + if c == '\n' || n >= t.buf.Len() { + break + } + n++ + offset++ + } + return offset +} + +// PrevNL returns number of bytes from given position to the nearest new line backwards. +func (t *Text) PrevNL(start int) int { + if start == 0 { + return 0 + } + if start > t.buf.Len() { + start = t.buf.Len() - 1 + } + n := start - 1 + offset := 1 + if c, _ := t.buf.ByteAt(n); c == '\n' { + return offset + } + for { + c, _ := t.buf.ByteAt(n) + if c == '\n' || n == 0 { + break + } + n-- + offset++ + } + return offset +} + +// SetCursor moves the cursor to different offsets in the text buffer. +// +// A negative whence is backwards from current position, a positive whence is forward and a zero whence sets the absolute position from the start of the buffer. +func (t *Text) SetCursor(pos, whence int) { + switch whence { + case -1: + t.cursorpos -= pos + case 0: + t.cursorpos = pos + case 1: + t.cursorpos += pos + } + + // check out of range + if t.cursorpos < 0 { + t.cursorpos = 0 + } + if t.cursorpos >= t.buf.Len() { + t.cursorpos = t.buf.Len() + } +} + +/* History */ +type Action int + +const ( + ActionInsert Action = iota + ActionDelete +) + +type Change struct { + offset int + action Action + content []byte +} + +type History struct { + done []Change + recall []Change +} + +func (h *History) Do(c Change) { + h.done = append(h.done, c) + h.recall = nil // clear old recall stack on new do +} + +func (h *History) Undo() (Change, error) { + if len(h.done) == 0 { + return Change{}, errors.New("no history") + } + lastdone := h.done[len(h.done)-1] + h.recall = append(h.recall, lastdone) + h.done = h.done[:len(h.done)-1] // remove last one + + // Reverse the done action so the returned change can be applied directly. + switch lastdone.action { + case ActionInsert: + lastdone.action = ActionDelete + lastdone.offset += len(lastdone.content) + case ActionDelete: + lastdone.action = ActionInsert + lastdone.offset -= len(lastdone.content) + } + + return lastdone, nil +} + +func (h *History) Redo() (Change, error) { + if len(h.recall) == 0 { + return Change{}, errors.New("no recall history") + } + lastrecall := h.recall[len(h.recall)-1] + h.done = append(h.done, lastrecall) + h.recall = h.recall[:len(h.recall)-1] //remove last one + return lastrecall, nil +} -- cgit v1.2.3