| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 1 | // Modified from rsc.io/edit |
| 2 | |
| 3 | // Copyright 2017 The Go Authors. All rights reserved. |
| 4 | // Use of this source code is governed by a BSD-style |
| 5 | // license that can be found in the LICENSE file. |
| 6 | |
| 7 | // Package edit implements buffered position-based editing of byte slices. |
| 8 | package editbuf |
| 9 | |
| 10 | import ( |
| 11 | "fmt" |
| 12 | "sort" |
| 13 | ) |
| 14 | |
| 15 | // A Buffer is a queue of edits to apply to a given byte slice. |
| 16 | type Buffer struct { |
| 17 | old []byte |
| 18 | q edits |
| 19 | } |
| 20 | |
| 21 | // An edit records a single text modification: change the bytes in [start,end) to new. |
| 22 | type edit struct { |
| 23 | start int |
| 24 | end int |
| 25 | new string |
| 26 | } |
| 27 | |
| 28 | // An edits is a list of edits that is sortable by start offset, breaking ties by end offset. |
| 29 | type edits []edit |
| 30 | |
| 31 | func (x edits) Len() int { return len(x) } |
| 32 | func (x edits) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
| 33 | func (x edits) Less(i, j int) bool { |
| 34 | if x[i].start != x[j].start { |
| 35 | return x[i].start < x[j].start |
| 36 | } |
| 37 | return x[i].end < x[j].end |
| 38 | } |
| 39 | |
| 40 | // NewBuffer returns a new buffer to accumulate changes to an initial data slice. |
| 41 | // The returned buffer maintains a reference to the data, so the caller must ensure |
| 42 | // the data is not modified until after the Buffer is done being used. |
| 43 | func NewBuffer(old []byte) *Buffer { |
| 44 | return &Buffer{old: old} |
| 45 | } |
| 46 | |
| 47 | // Insert inserts the new string at old[pos:pos]. |
| 48 | func (b *Buffer) Insert(pos int, new string) { |
| 49 | if pos < 0 || pos > len(b.old) { |
| 50 | panic("invalid edit position") |
| 51 | } |
| 52 | b.q = append(b.q, edit{pos, pos, new}) |
| 53 | } |
| 54 | |
| 55 | // Delete deletes the text old[start:end]. |
| 56 | func (b *Buffer) Delete(start, end int) { |
| 57 | if end < start || start < 0 || end > len(b.old) { |
| 58 | panic("invalid edit position") |
| 59 | } |
| 60 | b.q = append(b.q, edit{start, end, ""}) |
| 61 | } |
| 62 | |
| 63 | // Replace replaces old[start:end] with new. |
| 64 | func (b *Buffer) Replace(start, end int, new string) { |
| 65 | if end < start || start < 0 || end > len(b.old) { |
| 66 | panic("invalid edit position") |
| 67 | } |
| 68 | b.q = append(b.q, edit{start, end, new}) |
| 69 | } |
| 70 | |
| 71 | // Bytes returns a new byte slice containing the original data |
| 72 | // with the queued edits applied. |
| 73 | func (b *Buffer) Bytes() ([]byte, error) { |
| 74 | // Sort edits by starting position and then by ending position. |
| 75 | // Breaking ties by ending position allows insertions at point x |
| 76 | // to be applied before a replacement of the text at [x, y). |
| 77 | sort.Stable(b.q) |
| 78 | |
| 79 | var new []byte |
| 80 | offset := 0 |
| 81 | for i, e := range b.q { |
| 82 | if e.start < offset { |
| 83 | e0 := b.q[i-1] |
| 84 | return nil, fmt.Errorf("overlapping edits: [%d,%d)->%q, [%d,%d)->%q", e0.start, e0.end, e0.new, e.start, e.end, e.new) |
| 85 | } |
| 86 | new = append(new, b.old[offset:e.start]...) |
| 87 | offset = e.end |
| 88 | new = append(new, e.new...) |
| 89 | } |
| 90 | new = append(new, b.old[offset:]...) |
| 91 | return new, nil |
| 92 | } |