delta tests working

master
Stephen Searles 8 years ago
parent 1c029d9856
commit eafc496037
  1. 109
      acedoc.go
  2. 53
      acedoc_test.go
  3. 129
      delta.go

@ -1,46 +1,10 @@
package acedoc
import (
"fmt"
"strings"
"sync"
)
type DeltaAction bool
const (
DeltaInsert DeltaAction = true
DeltaRemove DeltaAction = false
)
type Delta struct {
Action DeltaAction
Lines []string
Start, End Position
}
func Remove(row, col uint, str string) Delta {
lines := strings.Split(str, "\n")
return Delta{
Action: DeltaRemove,
Lines: lines,
Start: Position{row, col},
End: Position{row + uint(len(lines)-1), uint(len(lines[len(lines)-1]))},
}
}
func Insert(row, col uint, str string) Delta {
lines := strings.Split(str, "\n")
return Delta{
Action: DeltaInsert,
Lines: lines,
Start: Position{row, col},
End: Position{row + uint(len(lines)-1), uint(len(lines[len(lines)-1]))},
}
}
type Document struct {
mtx sync.RWMutex
Lines []string
@ -69,78 +33,6 @@ func (d Delta) NLines() uint {
return n
}
func (d *Document) Apply(dl Delta) error {
d.mtx.Lock()
defer d.mtx.Unlock()
err := d.validate(dl)
if err != nil {
return fmt.Errorf("delta does not apply to document: %v", err)
}
row := dl.Start.Row
col := dl.Start.Column
line := d.Lines[row]
switch dl.Action {
case DeltaInsert:
if dl.NLines() == 1 {
d.Lines[row] = line[:col] + dl.Line(0) + line[col:]
} else {
newlines := []string{}
if row != 0 {
newlines = append(newlines, d.Lines[:row-1]...) // old content
}
newlines = append(newlines, d.Lines[row][:col]+dl.Lines[0])
newlines = append(newlines, dl.Lines[1:]...) // new content
newlines[len(newlines)-1] += d.Lines[row][col:]
if uint(len(d.Lines)) > row {
newlines = append(newlines, d.Lines[row+1:]...) // old content
}
d.Lines = newlines
}
case DeltaRemove:
endCol := dl.End.Column
endRow := dl.End.Row
if row == endRow {
d.Lines[row] = line[:col] + line[endCol:]
} else {
newlines := []string{}
newlines = append(newlines, d.Lines[:row]...)
newlines = append(newlines, d.Lines[endRow:]...)
newlines[endRow] = newlines[endRow][endCol:]
d.Lines = newlines
}
}
return nil
}
func (d *Document) validate(dl Delta) error {
if !d.InDocument(dl.Start) {
return fmt.Errorf("start is not in document")
}
if dl.Action == DeltaRemove && !d.InDocument(dl.End) {
return fmt.Errorf("end is not in document for remove")
}
if dl.NLines() != dl.NRows() {
return fmt.Errorf("delta has %d lines, but positions show range of %d lines", dl.NLines(), dl.NRows())
}
lastDlLine := dl.Line(dl.NRows() - 1)
if uint(len(lastDlLine)) != dl.Cols() {
return fmt.Errorf("delta has %d chars on the final line, but positions show range of %d chars", len(lastDlLine), dl.Cols())
}
return nil
}
func (d Document) Line(i uint) string {
if i >= uint(len(d.Lines)) {
return ""
@ -156,6 +48,7 @@ func (d Delta) Line(i uint) string {
func (d Delta) NRows() uint {
r := d.End.Row - d.Start.Row
r += 1
if r == 0 {
return 1
}

@ -11,18 +11,47 @@ func TestDelta(t *testing.T) {
}
cases := []c{
{"empty", "", "", Delta{}},
{"empty with content", "a", "a", Delta{}},
{"insert with no content", "a", "a", Insert(0, 1, "")},
{"insert", "a", "ab", Insert(0, 1, "b")},
{"insert across line", "a", "a\nb", Insert(0, 1, "\nb")},
{"insert new line", "ab", "a\nb", Insert(0, 1, "\n")},
{"insert lines at beginning", "ab", "\n\nab", Insert(0, 0, "\n\n")},
{"insert lines at end", "ab", "ab\n\n", Insert(0, 2, "\n\n")},
{"insert more lines", "a\nb", "a\n\n\nb", Insert(0, 1, "\n\n")},
{"remove", "a", "", Remove(0, 0, "a")},
{"remove across line", "a\nb", "a", Remove(0, 1, "\nb")},
// empty tests
{"empty",
"", "", Delta{}},
{"empty with content",
"a", "a", Delta{}},
// insert tests
{"insert with no content",
"a", "a", Insert(0, 1, "")},
{"insert",
"a", "ab", Insert(0, 1, "b")},
{"insert across line",
"a", "a\nb", Insert(0, 1, "\nb")},
{"insert new line",
"ab", "a\nb", Insert(0, 1, "\n")},
{"insert lines at beginning",
"ab", "\n\nab", Insert(0, 0, "\n\n")},
{"insert lines at end",
"ab", "ab\n\n", Insert(0, 2, "\n\n")},
{"insert more lines",
"a\nb", "a\n\n\nb", Insert(0, 1, "\n\n")},
// remove tests
{"remove",
"a", "", Remove(0, 0, "a")},
{"remove part",
"aa", "a", Remove(0, 0, "a")},
{"remove across line",
"a\nb", "a", Remove(0, 1, "\nb")},
}
for _, c := range cases {

@ -1,6 +1,14 @@
package acedoc
import (
"fmt"
"strings"
)
/* ***** BEGIN LICENSE BLOCK *****
*
* Original license for JS algorithm, which has been rewritten in Go:
*
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
@ -29,3 +37,124 @@ package acedoc
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
type DeltaAction bool
const (
DeltaInsert DeltaAction = true
DeltaRemove DeltaAction = false
)
type Delta struct {
Action DeltaAction
Lines []string
Start, End Position
}
func Remove(row, col uint, str string) Delta {
lines := strings.Split(str, "\n")
nlines := len(lines) - 1
return Delta{
Action: DeltaRemove,
Lines: lines,
Start: Position{row, col},
End: Position{row + uint(nlines), uint(len(lines[nlines]))},
}
}
func Insert(row, col uint, str string) Delta {
lines := strings.Split(str, "\n")
nlines := uint(len(lines) - 1)
return Delta{
Action: DeltaInsert,
Lines: lines,
Start: Position{row, col},
End: Position{row + nlines, uint(len(lines[nlines]))},
}
}
func (d *Document) applyInsert(dl Delta) error {
row := dl.Start.Row
col := dl.Start.Column
line := d.Lines[row]
if dl.NLines() == 1 {
d.Lines[row] = line[:col] + dl.Line(0) + line[col:]
} else {
newlines := []string{}
if row != 0 {
newlines = append(newlines, d.Lines[:row-1]...) // old content
}
newlines = append(newlines, d.Lines[row][:col]+dl.Lines[0])
newlines = append(newlines, dl.Lines[1:]...) // new content
newlines[len(newlines)-1] += d.Lines[row][col:]
if uint(len(d.Lines)) > row {
newlines = append(newlines, d.Lines[row+1:]...) // old content
}
d.Lines = newlines
}
return nil
}
func (d *Document) applyRemove(dl Delta) error {
row := dl.Start.Row
col := dl.Start.Column
line := d.Lines[row]
endCol := dl.End.Column
endRow := dl.End.Row
if row == endRow {
d.Lines[row] = line[:col] + line[endCol:]
} else {
d.Lines[endRow] = d.Lines[row][:col] + d.Lines[endRow][endCol:]
newlines := []string{}
newlines = append(newlines, d.Lines[:row]...)
newlines = append(newlines, d.Lines[endRow:]...)
d.Lines = newlines
}
return nil
}
func (d *Document) Apply(dl Delta) error {
d.mtx.Lock()
defer d.mtx.Unlock()
err := d.validate(dl)
if err != nil {
return fmt.Errorf("delta does not apply to document: %v", err)
}
switch dl.Action {
case DeltaInsert:
return d.applyInsert(dl)
case DeltaRemove:
return d.applyRemove(dl)
}
return nil
}
func (d *Document) validate(dl Delta) error {
if !d.InDocument(dl.Start) {
return fmt.Errorf("start is not in document")
}
if dl.Action == DeltaRemove && !d.InDocument(dl.End) {
return fmt.Errorf("end is not in document for remove")
}
if dl.NLines() != dl.NRows() {
return fmt.Errorf("delta has %d lines, but positions show range of %d lines", dl.NLines(), dl.NRows())
}
lastDlLine := dl.Line(dl.NRows() - 1)
if uint(len(lastDlLine)) != dl.Cols() {
return fmt.Errorf("delta has %d chars on the final line, but positions show range of %d chars", len(lastDlLine), dl.Cols())
}
return nil
}

Loading…
Cancel
Save