You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
3.6 KiB
197 lines
3.6 KiB
7 years ago
|
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
|
||
|
}
|
||
|
|
||
|
func NewString(str string) *Document {
|
||
|
d := &Document{
|
||
|
Lines: strings.Split(str, "\n"),
|
||
|
}
|
||
|
return d
|
||
|
}
|
||
|
|
||
|
func (d *Document) NLines() uint {
|
||
|
n := uint(len(d.Lines))
|
||
|
if n == 0 {
|
||
|
n = 1
|
||
|
}
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
func (d Delta) NLines() uint {
|
||
|
n := uint(len(d.Lines))
|
||
|
if n == 0 {
|
||
|
n = 1
|
||
|
}
|
||
|
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 ""
|
||
|
}
|
||
|
return d.Lines[i]
|
||
|
}
|
||
|
func (d Delta) Line(i uint) string {
|
||
|
if i >= uint(len(d.Lines)) {
|
||
|
return ""
|
||
|
}
|
||
|
return d.Lines[i]
|
||
|
}
|
||
|
|
||
|
func (d Delta) NRows() uint {
|
||
|
r := d.End.Row - d.Start.Row
|
||
|
if r == 0 {
|
||
|
return 1
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func (d Delta) Rows() uint {
|
||
|
r := d.End.Row - d.Start.Row
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func (d Delta) Cols() uint {
|
||
|
r := d.End.Column
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func (d *Document) Contents() string {
|
||
|
d.mtx.RLock()
|
||
|
defer d.mtx.RUnlock()
|
||
|
|
||
|
return strings.Join(d.Lines, "\n")
|
||
|
}
|
||
|
|
||
|
type Position struct {
|
||
|
Row, Column uint
|
||
|
}
|
||
|
|
||
|
func (d *Document) InDocument(pos Position) bool {
|
||
|
if pos.Row > d.NLines() {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
line := d.Line(pos.Row)
|
||
|
if pos.Column > uint(len(line)) {
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|