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
}