Решение на Piece table от Мирослав Лалев

Обратно към всички решения

Към профила на Мирослав Лалев

Резултати

  • 10 точки от тестове
  • 0 бонус точки
  • 10 точки общо
  • 8 успешни тест(а)
  • 0 неуспешни тест(а)

Код

package main
import (
"strings"
)
type Editor interface {
// Insert text starting from given position.
Insert(position uint, text string) Editor
// Delete length items from offset.
Delete(offset, length uint) Editor
// Undo reverts latest change.
Undo() Editor
// Redo re-applies latest undone change.
Redo() Editor
// String returns complete representation of what a file looks
// like after all manipulations.
String() string
}
type pieceTable []*piece
func (pt *pieceTable) InsertAt(i int, p ...*piece) {
if i < 0 || i > len(*pt) {
return
}
*pt = append((*pt)[:i], append(p, (*pt)[i:]...)...)
}
func (pt *pieceTable) RemoveAt(i int) {
if i < 0 || i > len(*pt) {
return
}
copy((*pt)[i:], (*pt)[i+1:])
(*pt)[len(*pt)-1] = nil
*pt = (*pt)[:len(*pt)-1]
}
type piece struct {
origin bool
offset int
length int
}
func newPiece(origin bool, offset, length int) *piece {
return &piece{
origin: origin,
offset: offset,
length: length,
}
}
type addBuffer []byte
type action struct {
apply func(*pieceEditor)
revert func(*pieceEditor)
}
var emptyAction = action{
apply: func(*pieceEditor) {},
revert: func(*pieceEditor) {},
}
func newAction(apply, revert func(*pieceEditor)) action {
return action{
apply: apply,
revert: revert,
}
}
type actionStack struct {
actions []action
pos int
}
func (as *actionStack) Push(a action) action {
if as.pos != len(as.actions) {
// not at the end, overwrite all others
as.actions = as.actions[:as.pos]
}
as.actions = append(as.actions, a)
as.pos++
return a
}
func (as *actionStack) Pop() action {
if as.pos == 0 {
return emptyAction
}
as.pos--
return as.actions[as.pos]
}
func (as *actionStack) Undo(pe *pieceEditor) {
a := as.Pop()
a.revert(pe)
}
func (as *actionStack) Redo(pe *pieceEditor) {
if as.pos == len(as.actions) {
return
}
as.actions[as.pos].apply(pe)
as.pos++
}
type pieceEditor struct {
oBuf string
aBuf addBuffer
pTab pieceTable
sLen int
actions *actionStack
}
func (pe *pieceEditor) Insert(position uint, text string) Editor {
if text == "" {
return pe
}
pos := int(position)
insPos, insLen, insInd := len(pe.aBuf), len(text), 0
pe.aBuf = append(pe.aBuf, addBuffer(text)...)
if pos >= pe.sLen {
insInd = len(pe.pTab)
pos = pe.sLen
}
if pos == 0 || pos == pe.sLen {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
},
))
a.apply(pe)
return pe
}
// find the correct place to insert in piece table
lenSum := 0
for _, p := range pe.pTab {
if lenSum >= pos {
break
}
insInd++
lenSum += p.length
}
// if the position doesn't intersect with piece just add it
if lenSum == pos {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
},
))
a.apply(pe)
return pe
}
// if position intersects with piece we need to split it like so:
// Define piece as a vector with the following base: p(offset,len)
// => p(off, len) = p1(off, len-(lenSum-pos)) + p2(off+p1.len, lenSum-pos)
// The new sequence in piece table should be: [ p1, newPiece, p2 ]
breakLen := lenSum - pos
fpOff, fpLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length-breakLen
spOff, spLen := pe.pTab[insInd-1].offset+fpLen, breakLen
sOff, sLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab[insInd-1].offset = fpOff
pe.pTab[insInd-1].length = fpLen
pe.pTab.InsertAt(insInd,
newPiece(false, insPos, insLen),
newPiece(pe.pTab[insInd-1].origin, spOff, spLen),
)
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
pe.pTab.RemoveAt(insInd)
pe.pTab[insInd-1].offset = sOff
pe.pTab[insInd-1].length = sLen
},
))
a.apply(pe)
return pe
}
func (pe *pieceEditor) Delete(offset, length uint) Editor {
if length == 0 {
return pe
}
off, delLen := int(offset), int(length)
delInd, lenSum := 0, 0
// find the correct deletion place in piece table
for _, p := range pe.pTab {
if lenSum >= off {
break
}
delInd++
lenSum += p.length
}
if off > lenSum {
return pe
}
var applies []func(*pieceEditor)
var reverts []func(*pieceEditor)
// if previous piece intersects with the deletion area it must be shortened
// there are three cases:
// 1. The deletion area is completely within the piece. In that case we
// should split the piece in three:
// p(off,len) = p(off,delP.off-off) + delP + p(delP.off+delP.len, off+len-(delP.off+delP.len))
// 2. The deletion area ends at the same place as the last piece:
// - 2.1: The deletion area starts at the same place as the piece:
// In that case we should just remove the whole piece.
// - 2.2: The deletion area doesn't start at the same place as the piece:
// In that case we should set the length of the piece to `p.off = delP.off - p.off`.
// - If either `1`, or `2` is valid, then this is the only action we need to execute and it is ok to return.
// - If neither are valid, then we should shorten the length of last piece with `lenSum-delP.off`
// and continue with processing the next pieces.
if lenSum > off {
lastPiece := pe.pTab[delInd-1]
// lastPiece.offset is relative to add buffer, so we don't want to use it during comparison.
lastPieceOffset := lenSum - lastPiece.length
if off+delLen < lastPieceOffset+lastPiece.length {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
y := lastPieceOffset + lastPiece.length - off - delLen
pe.pTab.InsertAt(delInd, newPiece(
lastPiece.origin,
lastPiece.offset+lastPiece.length-y,
y,
))
lastPiece.length = off - lastPieceOffset
},
func() func(*pieceEditor) {
oldLen := lastPiece.length
return func(pe *pieceEditor) {
lastPiece.length = oldLen
pe.pTab.RemoveAt(delInd)
}
}(),
))
a.apply(pe)
return pe
}
if off+delLen == lastPieceOffset+lastPiece.length {
var a action
if off == lastPieceOffset {
a = newAction(
func(pe *pieceEditor) {
pe.pTab.RemoveAt(delInd - 1)
},
func() func(*pieceEditor) {
pc := pe.pTab[delInd-1]
return func(pe *pieceEditor) {
pe.pTab.InsertAt(delInd-1, pc)
}
}(),
)
} else {
a = newAction(
func(pe *pieceEditor) {
lastPiece.length -= off - lastPieceOffset
},
func() func(*pieceEditor) {
oldLen := lastPiece.length
return func(pe *pieceEditor) {
lastPiece.length = oldLen
}
}(),
)
}
pe.actions.Push(a).apply(pe)
return pe
}
breakOff := lenSum - off
applies = append(applies, func(pe *pieceEditor) {
lastPiece.length -= breakOff
pe.sLen -= breakOff
})
reverts = append(reverts, func(pe *pieceEditor) {
pe.sLen += breakOff
lastPiece.length += breakOff
})
delLen -= breakOff
}
// Continue to process pieces until the desired delP.len is reached.
// Discard all pieces that you pass by and shorten the last one.
i := 0
for delLen > 0 && delInd+i < len(pe.pTab) {
if delLen < pe.pTab[delInd+i].length {
applies = append(applies, func() func(*pieceEditor) {
dL := delLen
return func(pe *pieceEditor) {
pe.pTab[delInd].offset += dL
pe.pTab[delInd].length -= dL
pe.sLen -= dL
}
}())
reverts = append(reverts, func() func(*pieceEditor) {
dL := delLen
return func(pe *pieceEditor) {
pe.sLen += dL
pe.pTab[delInd].length += dL
pe.pTab[delInd].offset -= dL
}
}())
break
}
applies = append(applies, func(pe *pieceEditor) {
pe.sLen -= pe.pTab[delInd].length
pe.pTab.RemoveAt(delInd)
})
reverts = append(reverts, func() func(*pieceEditor) {
pc := pe.pTab[delInd+i]
return func(pe *pieceEditor) {
pe.pTab.InsertAt(delInd, pc)
pe.sLen += pc.length
}
}())
delLen -= pe.pTab[delInd+i].length
i++
}
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
for _, apply := range applies {
apply(pe)
}
},
func(pe *pieceEditor) {
for i := len(reverts) - 1; i >= 0; i-- {
reverts[i](pe)
}
},
))
a.apply(pe)
return pe
}
func (pe *pieceEditor) Undo() Editor {
pe.actions.Undo(pe)
return pe
}
func (pe *pieceEditor) Redo() Editor {
pe.actions.Redo(pe)
return pe
}
func (pe *pieceEditor) String() string {
sb := strings.Builder{}
for _, p := range pe.pTab {
if p.origin {
sb.WriteString(pe.oBuf[p.offset : p.offset+p.length])
continue
}
sb.Write(pe.aBuf[p.offset : p.offset+p.length])
}
return sb.String()
}
func NewEditor(text string) Editor {
pe := &pieceEditor{
oBuf: text,
pTab: []*piece{newPiece(true, 0, len(text))},
sLen: len(text),
actions: new(actionStack),
}
return pe
}

Лог от изпълнението

PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s
PASS
ok  	_/tmp/d20181107-53-uald96	0.003s
PASS
ok  	_/tmp/d20181107-53-uald96	0.002s

История (2 версии и 0 коментара)

Мирослав обнови решението на 04.11.2018 19:27 (преди 9 месеца)

+package main
+
+import (
+ "strings"
+)
+
+type Editor interface {
+ // Insert text starting from given position.
+ Insert(position uint, text string) Editor
+
+ // Delete length items from offset.
+ Delete(offset, length uint) Editor
+
+ // Undo reverts latest change.
+ Undo() Editor
+
+ // Redo re-applies latest undone change.
+ Redo() Editor
+
+ // String returns complete representation of what a file looks
+ // like after all manipulations.
+ String() string
+}
+
+type pieceTable []*piece
+
+func (pt *pieceTable) InsertAt(i int, p ...*piece) {
+ if i < 0 || i > len(*pt) {
+ return
+ }
+ *pt = append((*pt)[:i], append(p, (*pt)[i:]...)...)
+}
+
+func (pt *pieceTable) RemoveAt(i int) {
+ if i < 0 || i > len(*pt) {
+ return
+ }
+ copy((*pt)[i:], (*pt)[i+1:])
+ (*pt)[len(*pt)-1] = nil
+ *pt = (*pt)[:len(*pt)-1]
+}
+
+type piece struct {
+ origin bool
+ offset int
+ length int
+}
+
+func newPiece(origin bool, offset, length int) *piece {
+ return &piece{
+ origin: origin,
+ offset: offset,
+ length: length,
+ }
+}
+
+type addBuffer []byte
+
+type action struct {
+ apply func(*pieceEditor)
+ revert func(*pieceEditor)
+}
+
+var emptyAction = action{
+ apply: func(*pieceEditor) {},
+ revert: func(*pieceEditor) {},
+}
+
+func newAction(apply, revert func(*pieceEditor)) action {
+ return action{
+ apply: apply,
+ revert: revert,
+ }
+}
+
+type actionStack struct {
+ actions []action
+ pos int
+}
+
+func (as *actionStack) Push(a action) action {
+ if as.pos != len(as.actions) {
+ // not at the end, overwrite all others
+ as.actions = as.actions[:as.pos]
+ }
+ as.actions = append(as.actions, a)
+ as.pos++
+ return a
+}
+
+func (as *actionStack) Pop() action {
+ if as.pos == 0 {
+ return emptyAction
+ }
+ as.pos--
+ return as.actions[as.pos]
+}
+
+func (as *actionStack) Undo(pe *pieceEditor) {
+ a := as.Pop()
+ a.revert(pe)
+}
+
+func (as *actionStack) Redo(pe *pieceEditor) {
+ if as.pos == len(as.actions) {
+ return
+ }
+ as.actions[as.pos].apply(pe)
+ as.pos++
+}
+
+type pieceEditor struct {
+ oBuf string
+ aBuf addBuffer
+ pTab pieceTable
+
+ sLen int
+ actions *actionStack
+}
+
+func (pe *pieceEditor) Insert(position uint, text string) Editor {
+ if text == "" {
+ return pe
+ }
+
+ pos := int(position)
+ insPos, insLen, insInd := len(pe.aBuf), len(text), 0
+ pe.aBuf = append(pe.aBuf, addBuffer(text)...)
+ if pos >= pe.sLen {
+ insInd = len(pe.pTab)
+ pos = pe.sLen
+ }
+ if pos == 0 || pos == pe.sLen {
+ a := pe.actions.Push(newAction(
+ func(pe *pieceEditor) {
+ pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
+ pe.sLen += insLen
+ },
+ func(pe *pieceEditor) {
+ pe.sLen -= insLen
+ pe.pTab.RemoveAt(insInd)
+ },
+ ))
+ a.apply(pe)
+ return pe
+ }
+
+ // find the correct place to insert in piece table
+ lenSum := 0
+ for _, p := range pe.pTab {
+ if lenSum >= pos {
+ break
+ }
+ insInd++
+ lenSum += p.length
+ }
+
+ // if the position doesn't intersect with piece just add it
+ if lenSum == pos {
+ a := pe.actions.Push(newAction(
+ func(pe *pieceEditor) {
+ pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
+ pe.sLen += insLen
+ },
+ func(pe *pieceEditor) {
+ pe.sLen -= insLen
+ pe.pTab.RemoveAt(insInd)
+ },
+ ))
+ a.apply(pe)
+ return pe
+ }
+
+ // if position intersects with piece we need to split it like so:
+ // Define piece as a vector with the following base: p(offset,len)
+ // => p(off, len) = p1(off, len-(lenSum-pos)) + p2(off+p1.len, lenSum-pos)
+ // The new sequence in piece table should be: [ p1, newPiece, p2 ]
+ breakLen := lenSum - pos
+ fpOff, fpLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length-breakLen
+ spOff, spLen := pe.pTab[insInd-1].offset+fpLen, breakLen
+ sOff, sLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length
+ a := pe.actions.Push(newAction(
+ func(pe *pieceEditor) {
+ pe.pTab[insInd-1].offset = fpOff
+ pe.pTab[insInd-1].length = fpLen
+ pe.pTab.InsertAt(insInd,
+ newPiece(false, insPos, insLen),
+ newPiece(pe.pTab[insInd-1].origin, spOff, spLen),
+ )
+ pe.sLen += insLen
+ },
+ func(pe *pieceEditor) {
+ pe.sLen -= insLen
+ pe.pTab.RemoveAt(insInd)
+ pe.pTab.RemoveAt(insInd)
+ pe.pTab[insInd-1].offset = sOff
+ pe.pTab[insInd-1].length = sLen
+ },
+ ))
+ a.apply(pe)
+ return pe
+}
+
+func (pe *pieceEditor) Delete(offset, length uint) Editor {
+ if length == 0 {
+ return pe
+ }
+
+ off, delLen := int(offset), int(length)
+ delInd, lenSum := 0, 0
+
+ // find the correct deletion place in piece table
+ for _, p := range pe.pTab {
+ if lenSum >= off {
+ break
+ }
+ delInd++
+ lenSum += p.length
+ }
+ if off > lenSum {
+ return pe
+ }
+
+ var applies []func(*pieceEditor)
+ var reverts []func(*pieceEditor)
+
+ // if previous piece intersects with the deletion area it must be shortened
+ // there are three cases:
+ // 1. The deletion area is completely within the piece. In that case we
+ // should split the piece in three:
+ // p(off,len) = p(off,delP.off-off) + delP + p(delP.off+delP.len, off+len-(delP.off+delP.len))
+ // 2. The deletion area ends at the same place as the last piece:
+ // - 2.1: The deletion area starts at the same place as the piece:
+ // In that case we should just remove the whole piece.
+ // - 2.2: The deletion area doesn't start at the same place as the piece:
+ // In that case we should set the length of the piece to `p.off = delP.off - p.off`.
+ // - If either `1`, or `2` is valid, then this is the only action we need to execute and it is ok to return.
+ // - If neither are valid, then we should shorten the length of last piece with `lenSum-delP.off`
+ // and continue with processing the next pieces.
+ if lenSum > off {
+ lastPiece := pe.pTab[delInd-1]
+ if off+delLen < lastPiece.offset+lastPiece.length {
+ a := pe.actions.Push(newAction(
+ func(pe *pieceEditor) {
+ pe.pTab.InsertAt(delInd, newPiece(
+ lastPiece.origin,
+ off+delLen,
+ lastPiece.offset+lastPiece.length-off-delLen,
+ ))
+ lastPiece.length = off - lastPiece.offset
+ },
+ func() func(*pieceEditor) {
+ oldLen := lastPiece.length
+ return func(pe *pieceEditor) {
+ lastPiece.length = oldLen
+ pe.pTab.RemoveAt(delInd)
+ }
+ }(),
+ ))
+ a.apply(pe)
+ return pe
+ }
+ if off+delLen == lastPiece.offset+lastPiece.length {
+ var a action
+ if off == lastPiece.offset {
+ a = newAction(
+ func(pe *pieceEditor) {
+ pe.pTab.RemoveAt(delInd - 1)
+ },
+ func() func(*pieceEditor) {
+ pc := pe.pTab[delInd-1]
+ return func(pe *pieceEditor) {
+ pe.pTab.InsertAt(delInd-1, pc)
+ }
+ }(),
+ )
+ } else {
+ a = newAction(
+ func(pe *pieceEditor) {
+ lastPiece.length -= off - lastPiece.offset
+ },
+ func() func(*pieceEditor) {
+ oldLen := lastPiece.length
+ return func(pe *pieceEditor) {
+ lastPiece.length = oldLen
+ }
+ }(),
+ )
+ }
+ pe.actions.Push(a).apply(pe)
+ return pe
+ }
+
+ breakOff := lenSum - off
+ applies = append(applies, func(pe *pieceEditor) {
+ lastPiece.length -= breakOff
+ pe.sLen -= breakOff
+ })
+ reverts = append(reverts, func(pe *pieceEditor) {
+ pe.sLen += breakOff
+ lastPiece.length += breakOff
+ })
+ delLen -= breakOff
+ }
+
+ // Continue to process pieces until the desired delP.len is reached.
+ // Discard all pieces that you pass by and shorten the last one.
+ i := 0
+ for delLen > 0 && delInd+i < len(pe.pTab) {
+ if delLen < pe.pTab[delInd+i].length {
+ applies = append(applies, func() func(*pieceEditor) {
+ dL := delLen
+ return func(pe *pieceEditor) {
+ pe.pTab[delInd].offset += dL
+ pe.pTab[delInd].length -= dL
+ pe.sLen -= dL
+ }
+ }())
+ reverts = append(reverts, func() func(*pieceEditor) {
+ dL := delLen
+ return func(pe *pieceEditor) {
+ pe.sLen += dL
+ pe.pTab[delInd].length += dL
+ pe.pTab[delInd].offset -= dL
+ }
+ }())
+ break
+ }
+ applies = append(applies, func(pe *pieceEditor) {
+ pe.sLen -= pe.pTab[delInd].length
+ pe.pTab.RemoveAt(delInd)
+ })
+ reverts = append(reverts, func() func(*pieceEditor) {
+ pc := pe.pTab[delInd+i]
+ return func(pe *pieceEditor) {
+ pe.pTab.InsertAt(delInd, pc)
+ pe.sLen += pc.length
+ }
+ }())
+ delLen -= pe.pTab[delInd+i].length
+ i++
+ }
+
+ a := pe.actions.Push(newAction(
+ func(pe *pieceEditor) {
+ for _, apply := range applies {
+ apply(pe)
+ }
+ },
+ func(pe *pieceEditor) {
+ for i := len(reverts) - 1; i >= 0; i-- {
+ reverts[i](pe)
+ }
+ },
+ ))
+ a.apply(pe)
+ return pe
+}
+
+func (pe *pieceEditor) Undo() Editor {
+ pe.actions.Undo(pe)
+ return pe
+}
+
+func (pe *pieceEditor) Redo() Editor {
+ pe.actions.Redo(pe)
+ return pe
+}
+
+func (pe *pieceEditor) String() string {
+ sb := strings.Builder{}
+ for _, p := range pe.pTab {
+ if p.origin {
+ sb.WriteString(pe.oBuf[p.offset : p.offset+p.length])
+ continue
+ }
+ sb.Write(pe.aBuf[p.offset : p.offset+p.length])
+ }
+ return sb.String()
+}
+
+func NewEditor(text string) Editor {
+ pe := &pieceEditor{
+ oBuf: text,
+ pTab: []*piece{newPiece(true, 0, len(text))},
+ sLen: len(text),
+ actions: new(actionStack),
+ }
+ return pe
+}

Мирослав обнови решението на 06.11.2018 01:01 (преди 9 месеца)

package main
import (
"strings"
)
type Editor interface {
// Insert text starting from given position.
Insert(position uint, text string) Editor
// Delete length items from offset.
Delete(offset, length uint) Editor
// Undo reverts latest change.
Undo() Editor
// Redo re-applies latest undone change.
Redo() Editor
// String returns complete representation of what a file looks
// like after all manipulations.
String() string
}
type pieceTable []*piece
func (pt *pieceTable) InsertAt(i int, p ...*piece) {
if i < 0 || i > len(*pt) {
return
}
*pt = append((*pt)[:i], append(p, (*pt)[i:]...)...)
}
func (pt *pieceTable) RemoveAt(i int) {
if i < 0 || i > len(*pt) {
return
}
copy((*pt)[i:], (*pt)[i+1:])
(*pt)[len(*pt)-1] = nil
*pt = (*pt)[:len(*pt)-1]
}
type piece struct {
origin bool
offset int
length int
}
func newPiece(origin bool, offset, length int) *piece {
return &piece{
origin: origin,
offset: offset,
length: length,
}
}
type addBuffer []byte
type action struct {
apply func(*pieceEditor)
revert func(*pieceEditor)
}
var emptyAction = action{
apply: func(*pieceEditor) {},
revert: func(*pieceEditor) {},
}
func newAction(apply, revert func(*pieceEditor)) action {
return action{
apply: apply,
revert: revert,
}
}
type actionStack struct {
actions []action
pos int
}
func (as *actionStack) Push(a action) action {
if as.pos != len(as.actions) {
// not at the end, overwrite all others
as.actions = as.actions[:as.pos]
}
as.actions = append(as.actions, a)
as.pos++
return a
}
func (as *actionStack) Pop() action {
if as.pos == 0 {
return emptyAction
}
as.pos--
return as.actions[as.pos]
}
func (as *actionStack) Undo(pe *pieceEditor) {
a := as.Pop()
a.revert(pe)
}
func (as *actionStack) Redo(pe *pieceEditor) {
if as.pos == len(as.actions) {
return
}
as.actions[as.pos].apply(pe)
as.pos++
}
type pieceEditor struct {
oBuf string
aBuf addBuffer
pTab pieceTable
sLen int
actions *actionStack
}
func (pe *pieceEditor) Insert(position uint, text string) Editor {
if text == "" {
return pe
}
pos := int(position)
insPos, insLen, insInd := len(pe.aBuf), len(text), 0
pe.aBuf = append(pe.aBuf, addBuffer(text)...)
if pos >= pe.sLen {
insInd = len(pe.pTab)
pos = pe.sLen
}
if pos == 0 || pos == pe.sLen {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
},
))
a.apply(pe)
return pe
}
// find the correct place to insert in piece table
lenSum := 0
for _, p := range pe.pTab {
if lenSum >= pos {
break
}
insInd++
lenSum += p.length
}
// if the position doesn't intersect with piece just add it
if lenSum == pos {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab.InsertAt(insInd, newPiece(false, insPos, insLen))
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
},
))
a.apply(pe)
return pe
}
// if position intersects with piece we need to split it like so:
// Define piece as a vector with the following base: p(offset,len)
// => p(off, len) = p1(off, len-(lenSum-pos)) + p2(off+p1.len, lenSum-pos)
// The new sequence in piece table should be: [ p1, newPiece, p2 ]
breakLen := lenSum - pos
fpOff, fpLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length-breakLen
spOff, spLen := pe.pTab[insInd-1].offset+fpLen, breakLen
sOff, sLen := pe.pTab[insInd-1].offset, pe.pTab[insInd-1].length
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
pe.pTab[insInd-1].offset = fpOff
pe.pTab[insInd-1].length = fpLen
pe.pTab.InsertAt(insInd,
newPiece(false, insPos, insLen),
newPiece(pe.pTab[insInd-1].origin, spOff, spLen),
)
pe.sLen += insLen
},
func(pe *pieceEditor) {
pe.sLen -= insLen
pe.pTab.RemoveAt(insInd)
pe.pTab.RemoveAt(insInd)
pe.pTab[insInd-1].offset = sOff
pe.pTab[insInd-1].length = sLen
},
))
a.apply(pe)
return pe
}
func (pe *pieceEditor) Delete(offset, length uint) Editor {
if length == 0 {
return pe
}
off, delLen := int(offset), int(length)
delInd, lenSum := 0, 0
// find the correct deletion place in piece table
for _, p := range pe.pTab {
if lenSum >= off {
break
}
delInd++
lenSum += p.length
}
if off > lenSum {
return pe
}
var applies []func(*pieceEditor)
var reverts []func(*pieceEditor)
// if previous piece intersects with the deletion area it must be shortened
// there are three cases:
// 1. The deletion area is completely within the piece. In that case we
// should split the piece in three:
// p(off,len) = p(off,delP.off-off) + delP + p(delP.off+delP.len, off+len-(delP.off+delP.len))
// 2. The deletion area ends at the same place as the last piece:
// - 2.1: The deletion area starts at the same place as the piece:
// In that case we should just remove the whole piece.
// - 2.2: The deletion area doesn't start at the same place as the piece:
// In that case we should set the length of the piece to `p.off = delP.off - p.off`.
// - If either `1`, or `2` is valid, then this is the only action we need to execute and it is ok to return.
// - If neither are valid, then we should shorten the length of last piece with `lenSum-delP.off`
// and continue with processing the next pieces.
if lenSum > off {
lastPiece := pe.pTab[delInd-1]
- if off+delLen < lastPiece.offset+lastPiece.length {
+ // lastPiece.offset is relative to add buffer, so we don't want to use it during comparison.
+ lastPieceOffset := lenSum - lastPiece.length
+ if off+delLen < lastPieceOffset+lastPiece.length {
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
+ y := lastPieceOffset + lastPiece.length - off - delLen
pe.pTab.InsertAt(delInd, newPiece(
lastPiece.origin,
- off+delLen,
- lastPiece.offset+lastPiece.length-off-delLen,
+ lastPiece.offset+lastPiece.length-y,
+ y,
))
- lastPiece.length = off - lastPiece.offset
+ lastPiece.length = off - lastPieceOffset
},
func() func(*pieceEditor) {
oldLen := lastPiece.length
return func(pe *pieceEditor) {
lastPiece.length = oldLen
pe.pTab.RemoveAt(delInd)
}
}(),
))
a.apply(pe)
return pe
}
- if off+delLen == lastPiece.offset+lastPiece.length {
+ if off+delLen == lastPieceOffset+lastPiece.length {
var a action
- if off == lastPiece.offset {
+ if off == lastPieceOffset {
a = newAction(
func(pe *pieceEditor) {
pe.pTab.RemoveAt(delInd - 1)
},
func() func(*pieceEditor) {
pc := pe.pTab[delInd-1]
return func(pe *pieceEditor) {
pe.pTab.InsertAt(delInd-1, pc)
}
}(),
)
} else {
a = newAction(
func(pe *pieceEditor) {
- lastPiece.length -= off - lastPiece.offset
+ lastPiece.length -= off - lastPieceOffset
},
func() func(*pieceEditor) {
oldLen := lastPiece.length
return func(pe *pieceEditor) {
lastPiece.length = oldLen
}
}(),
)
}
pe.actions.Push(a).apply(pe)
return pe
}
breakOff := lenSum - off
applies = append(applies, func(pe *pieceEditor) {
lastPiece.length -= breakOff
pe.sLen -= breakOff
})
reverts = append(reverts, func(pe *pieceEditor) {
pe.sLen += breakOff
lastPiece.length += breakOff
})
delLen -= breakOff
}
// Continue to process pieces until the desired delP.len is reached.
// Discard all pieces that you pass by and shorten the last one.
i := 0
for delLen > 0 && delInd+i < len(pe.pTab) {
if delLen < pe.pTab[delInd+i].length {
applies = append(applies, func() func(*pieceEditor) {
dL := delLen
return func(pe *pieceEditor) {
pe.pTab[delInd].offset += dL
pe.pTab[delInd].length -= dL
pe.sLen -= dL
}
}())
reverts = append(reverts, func() func(*pieceEditor) {
dL := delLen
return func(pe *pieceEditor) {
pe.sLen += dL
pe.pTab[delInd].length += dL
pe.pTab[delInd].offset -= dL
}
}())
break
}
applies = append(applies, func(pe *pieceEditor) {
pe.sLen -= pe.pTab[delInd].length
pe.pTab.RemoveAt(delInd)
})
reverts = append(reverts, func() func(*pieceEditor) {
pc := pe.pTab[delInd+i]
return func(pe *pieceEditor) {
pe.pTab.InsertAt(delInd, pc)
pe.sLen += pc.length
}
}())
delLen -= pe.pTab[delInd+i].length
i++
}
a := pe.actions.Push(newAction(
func(pe *pieceEditor) {
for _, apply := range applies {
apply(pe)
}
},
func(pe *pieceEditor) {
for i := len(reverts) - 1; i >= 0; i-- {
reverts[i](pe)
}
},
))
a.apply(pe)
return pe
}
func (pe *pieceEditor) Undo() Editor {
pe.actions.Undo(pe)
return pe
}
func (pe *pieceEditor) Redo() Editor {
pe.actions.Redo(pe)
return pe
}
func (pe *pieceEditor) String() string {
sb := strings.Builder{}
for _, p := range pe.pTab {
if p.origin {
sb.WriteString(pe.oBuf[p.offset : p.offset+p.length])
continue
}
sb.Write(pe.aBuf[p.offset : p.offset+p.length])
}
return sb.String()
}
func NewEditor(text string) Editor {
pe := &pieceEditor{
oBuf: text,
pTab: []*piece{newPiece(true, 0, len(text))},
sLen: len(text),
actions: new(actionStack),
}
return pe
}