package acedoc import ( "fmt" "math" "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 ) func (d DeltaAction) String() string { if d == DeltaInsert { return "insert" } return "remove" } func (d DeltaAction) MarshalJSON() ([]byte, error) { if d == DeltaInsert { return []byte(`"insert"`), nil } return []byte(`"remove"`), nil } func (d *DeltaAction) UnmarshalJSON(b []byte) error { if string(b) == "null" { return nil } if string(b) == `"insert"` { *d = DeltaInsert } return nil } type Delta struct { Action DeltaAction `json:"action"` Lines []string `json:"lines"` Start Position `json:"start"` End Position `json:"end"` source *Client } func (d Delta) String() string { return fmt.Sprintf("%s:%q(%v->%v)", d.Action, strings.Join(d.Lines, "\n"), d.Start, d.End) } 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 { dl := Insert(row, col, str) dl.Action = DeltaRemove return dl } func Insert(row, col uint, str string) Delta { lines := strings.Split(str, "\n") var endcol uint addedLines := uint(len(lines) - 1) if len(lines) == 0 { addedLines = 1 } else { lastLine := lines[len(lines)-1] endcol += uint(len(lastLine)) } if addedLines == 0 { endcol += col } return Delta{ Action: DeltaInsert, Lines: lines, Start: Position{row, col}, End: Position{row + addedLines, endcol}, } } func (d *Document) applyInsert(dl Delta) { row := dl.Start.Row col := dl.Start.Column if uint(len(d.lines)) == row { d.lines = append(d.lines, "") } line := d.line(row) if dl.nLines() == 1 { d.lines[row] = line[:col] + dl.line(0) + line[col:] } else { newlines := []string{} newlines = append(newlines, d.lines[:row]...) // 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) } switch dl.Action { case DeltaInsert: d.applyInsert(dl) case DeltaRemove: d.applyRemove(dl) } d.deltas = append(d.deltas, dl) for _, c := range d.clients { if dl.source == c { continue } c.ch <- dl } } return nil } func (d *Document) validate(dl Delta) error { if !d.InDocument(dl.Start) { return fmt.Errorf("start %v is not in document %q", dl.Start, d.contents()) } if dl.Action == DeltaRemove && !d.InDocument(dl.End) { return fmt.Errorf("end %v is not in document %q for remove", dl.End, d.contents()) } 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 !(len(lastDlLine) == 0 && dl.cols() < 0) && len(lastDlLine) != int(math.Abs(float64(dl.cols()))) { return fmt.Errorf("delta %v has %d chars on the final line, but positions (%v -> %v) show range of %d chars", dl, len(lastDlLine), dl.Start, dl.End, dl.cols()) } return nil }