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. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * 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 (d Delta) Equal(other Delta) bool { if d.Start != other.Start { return false } if d.End != other.End { return false } if d.Action != other.Action { return false } if len(d.Lines) != len(other.Lines) { return false } for i := range d.Lines { if d.Lines[i] != other.Lines[i] { return false } } return true } 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) { 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 } } func (d *Document) applyRemove(dl Delta) { 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 } } func (d *Document) Apply(dls ...Delta) error { d.mtx.Lock() defer d.mtx.Unlock() for _, dl := range dls { err := d.validate(dl) if err != nil { return fmt.Errorf("delta does not apply to document: %v", err) } } for _, dl := range dls { switch dl.Action { case DeltaInsert: d.applyInsert(dl) case DeltaRemove: d.applyRemove(dl) } d.deltas = append(d.deltas, dl) for _, c := range d.clients { c.ch <- 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 }