├── go.mod ├── edit_test.go ├── LICENSE └── edit.go /go.mod: -------------------------------------------------------------------------------- 1 | module rsc.io/edit 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /edit_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edit 6 | 7 | import "testing" 8 | 9 | func TestEdit(t *testing.T) { 10 | b := NewBuffer([]byte("0123456789")) 11 | b.Insert(8, ",7½,") 12 | b.Replace(9, 10, "the-end") 13 | b.Insert(10, "!") 14 | b.Insert(4, "3.14,") 15 | b.Insert(4, "π,") 16 | b.Insert(4, "3.15,") 17 | b.Replace(3, 4, "three,") 18 | want := "012three,3.14,π,3.15,4567,7½,8the-end!" 19 | 20 | s := b.String() 21 | if s != want { 22 | t.Errorf("b.String() = %q, want %q", s, want) 23 | } 24 | sb := b.Bytes() 25 | if string(sb) != want { 26 | t.Errorf("b.Bytes() = %q, want %q", sb, want) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /edit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package edit implements buffered position-based editing of byte slices. 6 | package edit 7 | 8 | import ( 9 | "fmt" 10 | "sort" 11 | ) 12 | 13 | // A Buffer is a queue of edits to apply to a given byte slice. 14 | type Buffer struct { 15 | old []byte 16 | q edits 17 | } 18 | 19 | // An edit records a single text modification: change the bytes in [start,end) to new. 20 | type edit struct { 21 | start int 22 | end int 23 | new string 24 | } 25 | 26 | // An edits is a list of edits that is sortable by start offset, breaking ties by end offset. 27 | type edits []edit 28 | 29 | func (x edits) Len() int { return len(x) } 30 | func (x edits) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 31 | func (x edits) Less(i, j int) bool { 32 | if x[i].start != x[j].start { 33 | return x[i].start < x[j].start 34 | } 35 | return x[i].end < x[j].end 36 | } 37 | 38 | // NewBuffer returns a new buffer to accumulate changes to an initial data slice. 39 | // The returned buffer maintains a reference to the data, so the caller must ensure 40 | // the data is not modified until after the Buffer is done being used. 41 | func NewBuffer(old []byte) *Buffer { 42 | return &Buffer{old: old} 43 | } 44 | 45 | // Insert inserts the new string at old[pos:pos]. 46 | func (b *Buffer) Insert(pos int, new string) { 47 | if pos < 0 || pos > len(b.old) { 48 | panic("invalid edit position") 49 | } 50 | b.q = append(b.q, edit{pos, pos, new}) 51 | } 52 | 53 | // Delete deletes the text old[start:end]. 54 | func (b *Buffer) Delete(start, end int) { 55 | if end < start || start < 0 || end > len(b.old) { 56 | panic("invalid edit position") 57 | } 58 | b.q = append(b.q, edit{start, end, ""}) 59 | } 60 | 61 | // Replace replaces old[start:end] with new. 62 | func (b *Buffer) Replace(start, end int, new string) { 63 | if end < start || start < 0 || end > len(b.old) { 64 | panic("invalid edit position") 65 | } 66 | b.q = append(b.q, edit{start, end, new}) 67 | } 68 | 69 | // Bytes returns a new byte slice containing the original data 70 | // with the queued edits applied. 71 | func (b *Buffer) Bytes() []byte { 72 | // Sort edits by starting position and then by ending position. 73 | // Breaking ties by ending position allows insertions at point x 74 | // to be applied before a replacement of the text at [x, y). 75 | sort.Stable(b.q) 76 | 77 | var new []byte 78 | offset := 0 79 | for i, e := range b.q { 80 | if e.start < offset { 81 | e0 := b.q[i-1] 82 | panic(fmt.Sprintf("overlapping edits: [%d,%d)->%q, [%d,%d)->%q", e0.start, e0.end, e0.new, e.start, e.end, e.new)) 83 | } 84 | new = append(new, b.old[offset:e.start]...) 85 | offset = e.end 86 | new = append(new, e.new...) 87 | } 88 | new = append(new, b.old[offset:]...) 89 | return new 90 | } 91 | 92 | // String returns a string containing the original data 93 | // with the queued edits applied. 94 | func (b *Buffer) String() string { 95 | return string(b.Bytes()) 96 | } 97 | --------------------------------------------------------------------------------