-
Notifications
You must be signed in to change notification settings - Fork 394
/
filebuffer.go
281 lines (252 loc) · 6.28 KB
/
filebuffer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// filebuffer.go - defines the FileBuffer object
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// A FileBuffer manages a file being edited.
// A FileBuffer never deletes/modifies anything directly until it is replaced.
// It keeps a map of known lines to the current buffer.
// Note: FileBuffer is 0-addressed lines, so off-by-one from what `ed` expects.
type FileBuffer struct {
cbuf []string // cut buffer
buffer []string // all lines we know about, they never get delited
file []int // sequence of buffer lines
lastFile []int // used for undo capability
tmpFile []int // used for undo capability
dirty bool // tracks if the file has been modifed
lastDirty bool // used for undo capability
tmpDirty bool // used for undo capability
mod bool // mod is like dirty, but can be reset for transactions
addr int // current file address
lastAddr int // last address (for undo)
tmpAddr int // last address (for undo)
marks map[byte]int
}
// NewFileBuffer creats a new FileBuffer object
func NewFileBuffer(in []string) *FileBuffer {
f := &FileBuffer{
buffer: in,
file: []int{},
dirty: false,
mod: false,
addr: 0,
marks: make(map[byte]int),
}
for i := range f.buffer {
f.file = append(f.file, i)
}
return f
}
// ErrOOB line is out of bounds
var ErrOOB = fmt.Errorf("line is out of bounds")
// ErrINV address is invalid
var ErrINV = fmt.Errorf("invalid address")
// OOB checks if a line is out of bounds
func (f *FileBuffer) OOB(l int) bool {
if l < 0 || l >= f.Len() {
return true
}
return false
}
// GetMust gets a specified line, to be used when we know it's safe (no error return)
// if "set" is true, sets the current line pointer
func (f *FileBuffer) GetMust(line int, set bool) string {
if set {
f.addr = line
}
return f.buffer[f.file[line]]
}
// Get a specified line range
// this updates the current line pointer
func (f *FileBuffer) Get(r [2]int) (lines []string, e error) {
if f.OOB(r[0]) || f.OOB(r[1]) {
e = ErrOOB
return
}
for l := r[0]; l <= r[1]; l++ {
lines = append(lines, f.buffer[f.file[l]])
f.addr = l
}
return
}
// Copy lines into the cut buffer
func (f *FileBuffer) Copy(r [2]int) (e error) {
var lines []string
if lines, e = f.Get(r); e != nil {
return
}
f.cbuf = lines
return
}
// Paste lines from cut buffer insert at line
func (f *FileBuffer) Paste(line int) (e error) {
e = f.Insert(line, f.cbuf)
return
}
// Delete unmaps lines from the file
func (f *FileBuffer) Delete(r [2]int) (e error) {
blines := []int{}
for l := r[0]; l <= r[1]; l++ {
if f.OOB(l) {
return ErrOOB
}
blines = append(blines, f.file[l])
}
f.cbuf, _ = f.Get(r) // this shouldn't fail here, if it does we've got a bigger problem
for _, b := range blines {
for i, l := range f.file {
if l == b {
f.file = append(f.file[:i], f.file[i+1:]...)
break
}
}
}
f.Touch()
f.addr = r[0] + 1
if f.OOB(f.addr) {
f.addr = 0
}
return
}
// Insert adds nlines to buffer and inserts them at line
func (f *FileBuffer) Insert(line int, nlines []string) (e error) {
if line != f.Len() && f.OOB(line) { // if line == f.Len() we append to the end
return ErrOOB
}
if len(nlines) == 0 {
return
}
first := len(f.buffer)
f.buffer = append(f.buffer, nlines...)
nf := []int{}
for i := first; i < len(f.buffer); i++ {
nf = append(nf, i)
}
f.file = append(f.file[:line], append(nf, f.file[line:]...)...)
f.Touch()
f.addr = line + len(nlines) - 1
return
}
// Len returns the current file length
func (f *FileBuffer) Len() int {
return len(f.file)
}
// Dirty returns whether the file has changed
func (f *FileBuffer) Dirty() bool {
return f.dirty
}
// GetAddr gets the current file addr
func (f *FileBuffer) GetAddr() int {
return f.addr
}
// SetAddr sets the current addr, errors if OOB
func (f *FileBuffer) SetAddr(i int) (e error) {
if f.OOB(i) {
return ErrOOB
}
f.addr = i
return
}
// Clean resets the dirty flag
func (f *FileBuffer) Clean() {
f.dirty = false
f.lastDirty = false
f.lastFile = []int{}
f.lastAddr = 0
}
// FileToBuffer reads a file and creates a new FileBuffer from it
func FileToBuffer(file string) (fb *FileBuffer, e error) {
fb = NewFileBuffer(nil)
e = fb.ReadFile(0, file)
if e == nil {
fb.dirty = false
}
return
}
// SetMark sets a mark (by byte name) in the FileBuffer for later use
func (f *FileBuffer) SetMark(c byte, l int) (e error) {
if f.OOB(l) {
e = ErrOOB
return
}
f.marks[c] = f.file[l]
return
}
// GetMark gets a mark from the FileBuffer (by byte name)
func (f *FileBuffer) GetMark(c byte) (l int, e error) {
bl, ok := f.marks[c]
if !ok {
return -1, fmt.Errorf("no such mark: %c", c)
}
for i := 0; i < f.Len(); i++ {
if f.file[i] == bl {
l = i
return
}
}
return -1, fmt.Errorf("mark was cleared: %c", c)
}
// Size return the size (in bytes) of the current file buffer
func (f *FileBuffer) Size() (s int) {
for _, i := range f.file {
s += len(f.buffer[i])
}
return
}
// Read reads in from an io.Reader interface and inserts at the current line address
func (f *FileBuffer) Read(line int, r io.Reader) (e error) {
b := []string{}
s := bufio.NewScanner(r)
for s.Scan() {
b = append(b, s.Text())
}
e = f.Insert(line, b)
return
}
// ReadFile reads in a file and inserts it at the current line address
func (f *FileBuffer) ReadFile(line int, file string) (e error) {
var fh *os.File
if fh, e = os.Open(file); e != nil {
e = fmt.Errorf("could not read file: %v", e)
return
}
defer fh.Close()
e = f.Read(line, fh)
return
}
// Start a transaction
func (f *FileBuffer) Start() {
f.mod = false
f.tmpFile = make([]int, len(f.file))
copy(f.tmpFile, f.file)
f.tmpAddr = f.addr
f.tmpDirty = f.dirty
}
// End a transaction
func (f *FileBuffer) End() {
if f.mod {
f.lastFile = f.tmpFile
f.lastAddr = f.tmpAddr
f.lastDirty = f.tmpDirty
}
}
// Rewind restores the previous file
func (f *FileBuffer) Rewind() {
if f.Dirty() || f.lastDirty {
f.addr = f.lastAddr
f.file = f.lastFile
f.dirty = f.lastDirty
f.mod = true
}
}
// Touch is the correct way (even internally) to set the dirty & modified bits
func (f *FileBuffer) Touch() {
f.dirty = true
f.mod = true
}