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 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 }