From 3713dc63007febd5eb723795506208339da5f956 Mon Sep 17 00:00:00 2001 From: Felix Angell Date: Fri, 22 Jun 2018 09:43:02 +0100 Subject: [PATCH] added piectable --- piecetable | 1 - piecetable/line.go | 55 +++++++++++++ piecetable/node.go | 17 ++++ piecetable/piecetable_test.go | 149 ++++++++++++++++++++++++++++++++++ piecetable/table.go | 106 ++++++++++++++++++++++++ 5 files changed, 327 insertions(+), 1 deletion(-) delete mode 160000 piecetable create mode 100644 piecetable/line.go create mode 100644 piecetable/node.go create mode 100644 piecetable/piecetable_test.go create mode 100644 piecetable/table.go diff --git a/piecetable b/piecetable deleted file mode 160000 index 4446bac..0000000 --- a/piecetable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4446bac0ef28e0391057ecb181f9eaf823a2b21e diff --git a/piecetable/line.go b/piecetable/line.go new file mode 100644 index 0000000..fa883d6 --- /dev/null +++ b/piecetable/line.go @@ -0,0 +1,55 @@ +package piecetable + +type Line struct { + Buffer string + parent *PieceTable + mods map[int]bool + keys []int +} + +func NewLine(data string, parent *PieceTable) *Line { + return &Line{ + data, parent, map[int]bool{}, []int{}, + } +} + +func (l *Line) AppendNode(node *PieceNode) { + nodeIndex := len(l.parent.nodes) + l.mods[nodeIndex] = true + l.keys = append(l.keys, nodeIndex) + l.parent.nodes = append(l.parent.nodes, node) +} + +func (l *Line) Len() int { + return len(l.String()) +} + +func (l *Line) String() string { + data := l.Buffer + + for _, keyName := range l.keys { + thing, ok := l.mods[keyName] + // ? + if !ok || !thing { + continue + } + + mod := l.parent.nodes[keyName] + + if mod.Length >= 0 { + + // append! + if mod.Start >= len(data) { + data += mod.Data + continue + } + + fst, end := data[:mod.Start], data[mod.Start:] + data = fst + mod.Data + end + } else { + data = data[:mod.Start-1] + data[mod.Start:] + } + } + + return data +} diff --git a/piecetable/node.go b/piecetable/node.go new file mode 100644 index 0000000..2f96a5d --- /dev/null +++ b/piecetable/node.go @@ -0,0 +1,17 @@ +package piecetable + +type PieceNode struct { + Index int + Start int + Length int + Data string +} + +func NewPiece(data string, line int, start int) *PieceNode { + return &PieceNode{ + line, + start, + len(data), + data, + } +} diff --git a/piecetable/piecetable_test.go b/piecetable/piecetable_test.go new file mode 100644 index 0000000..73ec843 --- /dev/null +++ b/piecetable/piecetable_test.go @@ -0,0 +1,149 @@ +package piecetable + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeleteSingle(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := ` my testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + + for i := 0; i < len("this is "); i++ { + table.Delete(0, 1) + } + table.Undo() + + fmt.Println(table.String()) + + assert.Equal(t, output, table.String()) +} + +func TestBadRedo(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := `this is my piece foo table testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + table.Insert("piece table ", 0, 11) + table.Undo() + table.Redo() + + // no redo history. + table.Redo() + + fmt.Println(table.String()) + + assert.Equal(t, output, table.String()) +} + +func TestMultiStringInsertion(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := `this is my piece foo table testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + table.Insert("piece table ", 0, 11) + table.Insert("foo ", 0, 17) + + fmt.Println(table.String()) + + assert.Equal(t, output, table.String()) +} + +func TestUndo(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := `this is my piece table testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + table.Insert("piece table ", 0, 11) + + fmt.Println(table.String()) + assert.Equal(t, output, table.String()) + + table.Undo() + fmt.Println(table.String()) + assert.Equal(t, text, table.String()) +} + +func TestRedo(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := `this is my piece table testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + table.Insert("piece table ", 0, 11) + + fmt.Println(table.String()) + assert.Equal(t, output, table.String()) + + table.Undo() + fmt.Println(table.String()) + assert.Equal(t, text, table.String()) + + table.Redo() + fmt.Println(table.String()) + assert.Equal(t, output, table.String()) +} + +func TestStringInsertion(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + output := `this is my piece table testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + table.Insert("piece table ", 0, 11) + + fmt.Println(table.String()) + + assert.Equal(t, output, table.String()) +} + +func TestPrintDocument(t *testing.T) { + text := `this is my testing +document i want to see how it fares +and all of that fun +stuff` + + table := MakePieceTable(text) + + fmt.Println(table.String()) + + assert.Equal(t, text, table.String(), "Un-modified piece table output doesn't match value expected") +} diff --git a/piecetable/table.go b/piecetable/table.go new file mode 100644 index 0000000..6b474da --- /dev/null +++ b/piecetable/table.go @@ -0,0 +1,106 @@ +package piecetable + +import ( + "fmt" + "strings" + "unicode/utf8" +) + +type PieceTable struct { + Lines []*Line + nodes []*PieceNode + redoList []*PieceNode +} + +func MakePieceTable(data string) *PieceTable { + readStrings := strings.Split(data, "\n") + + lines := make([]*Line, len(readStrings)) + table := &PieceTable{ + lines, + []*PieceNode{}, + []*PieceNode{}, + } + + for idx, data := range readStrings { + lines[idx] = NewLine(data, table) + } + + return table +} + +func (p *PieceTable) Redo() { + if len(p.redoList) == 0 { + return + } + + action := p.redoList[len(p.redoList)-1] + p.redoList = p.redoList[:len(p.redoList)-1] + + actionIndex := len(p.nodes) + p.nodes = append(p.nodes, action) + + line := p.Lines[action.Index] + line.mods[actionIndex] = true +} + +func (p *PieceTable) Undo() { + if len(p.nodes) == 0 { + return + } + + nodeIndex := len(p.nodes) - 1 + + // get the value we pop + change := p.nodes[nodeIndex] + + // remove the node index from + // the mods (i.e. a dangling + // pointer) + line := p.Lines[change.Index] + delete(line.mods, nodeIndex) + + // pop the most recent change + p.nodes = p.nodes[:nodeIndex] + + // append it so we can redo it later if necessary + p.redoList = append(p.redoList, change) +} + +func (p *PieceTable) Delete(line int, idx int) { + node := NewPiece("", line, idx) + node.Length = -1 + p.Lines[line].AppendNode(node) +} + +// TODO this builds the line and indexes it. +func (p *PieceTable) Index(line int, idx int) rune { + r, _ := utf8.DecodeLastRuneInString(p.Lines[line].String()[idx:]) + return r +} + +func (p *PieceTable) Insert(val string, line int, idx int) { + node := NewPiece(val, line, idx) + p.Lines[line].AppendNode(node) +} + +func (p *PieceTable) Line(idx int) string { + return p.Lines[idx].String() +} + +func (p *PieceTable) String() string { + var result string + for idx, line := range p.Lines { + if idx > 0 { + result += string('\n') + } + result += fmt.Sprintf(line.String()) + } + return result +} + +func (p *PieceTable) Print() { + for _, line := range p.Lines { + fmt.Println(line.Buffer) + } +}