aboutsummaryrefslogtreecommitdiff
path: root/testfiles/text.txt
diff options
context:
space:
mode:
authorPetter Rodhelind <petter.rodhelind@gmail.com>2018-02-22 23:15:13 +0100
committerPetter Rodhelind <petter.rodhelind@gmail.com>2018-02-22 23:15:13 +0100
commit4bca49f807544bd948a5f5f78e3787411252650f (patch)
tree5014acfd25b349488fd8116dccccac714bedb65d /testfiles/text.txt
downloadpoe-4bca49f807544bd948a5f5f78e3787411252650f.tar.gz
poe-4bca49f807544bd948a5f5f78e3787411252650f.tar.bz2
poe-4bca49f807544bd948a5f5f78e3787411252650f.zip
first commit
Diffstat (limited to 'testfiles/text.txt')
-rw-r--r--testfiles/text.txt229
1 files changed, 229 insertions, 0 deletions
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
+}