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