delta tests working
This commit is contained in:
@@ -1,46 +1,10 @@
|
|||||||
package acedoc
|
package acedoc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"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 {
|
type Document struct {
|
||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
Lines []string
|
Lines []string
|
||||||
@@ -69,78 +33,6 @@ func (d Delta) NLines() uint {
|
|||||||
return n
|
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 {
|
func (d Document) Line(i uint) string {
|
||||||
if i >= uint(len(d.Lines)) {
|
if i >= uint(len(d.Lines)) {
|
||||||
return ""
|
return ""
|
||||||
@@ -156,6 +48,7 @@ func (d Delta) Line(i uint) string {
|
|||||||
|
|
||||||
func (d Delta) NRows() uint {
|
func (d Delta) NRows() uint {
|
||||||
r := d.End.Row - d.Start.Row
|
r := d.End.Row - d.Start.Row
|
||||||
|
r += 1
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-11
@@ -11,18 +11,47 @@ func TestDelta(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cases := []c{
|
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")},
|
// empty tests
|
||||||
{"remove across line", "a\nb", "a", Remove(0, 1, "\nb")},
|
|
||||||
|
{"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 {
|
for _, c := range cases {
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package acedoc
|
package acedoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
/* ***** BEGIN LICENSE BLOCK *****
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
*
|
||||||
|
* Original license for JS algorithm, which has been rewritten in Go:
|
||||||
|
*
|
||||||
* Distributed under the BSD license:
|
* Distributed under the BSD license:
|
||||||
*
|
*
|
||||||
* Copyright (c) 2010, Ajax.org B.V.
|
* Copyright (c) 2010, Ajax.org B.V.
|
||||||
@@ -29,3 +37,124 @@ package acedoc
|
|||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
*
|
||||||
* ***** END LICENSE BLOCK ***** */
|
* ***** 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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user