github.com/jlowellwofford/u-root@v1.0.0/xcmds/ed/text.go (about) 1 // Copyright 2012-2017 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 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "reflect" 14 "regexp" 15 ) 16 17 // start and end are used like slices. 18 // so 1,1 defines a point. 1, 2 is line 1. 19 // 1, $ is all the lines where $ is len(lines)+1 20 type file struct { 21 dot int 22 dirty bool 23 lines []int 24 data []byte 25 pat string 26 } 27 28 func makeLines(n []byte) [][]byte { 29 if len(n) == 0 { 30 return nil 31 } 32 var ( 33 data [][]byte 34 sawnl bool 35 pos int 36 i int 37 ) 38 for i = range n { 39 if sawnl { 40 sawnl = false 41 l := make([]byte, i-pos) 42 copy(l, n[pos:i]) 43 data = append(data, l) 44 pos = i 45 } 46 if n[i] == '\n' { 47 sawnl = true 48 } 49 } 50 51 if pos <= i { 52 l := make([]byte, len(n[pos:])) 53 copy(l, n[pos:]) 54 data = append(data, l) 55 } 56 debug("makeLines: %v", data) 57 return data 58 } 59 60 func (f *file) String() string { 61 return fmt.Sprintf("%d %v %s", f.dot, f.lines, string(f.data)) 62 } 63 64 func (f *file) fixLines() { 65 f.lines = nil 66 if len(f.data) == 0 { 67 return 68 } 69 f.lines = []int{0} 70 71 lines := 1 72 var sawnl bool 73 for i, v := range f.data { 74 if sawnl { 75 lines++ 76 f.lines = append(f.lines, i) 77 sawnl = false 78 } 79 if v == '\n' { 80 sawnl = true 81 } 82 } 83 if f.dot < 1 { 84 f.dot = 1 85 } 86 if f.dot > lines { 87 f.dot = lines 88 } 89 // f.start, f.end = f.dot, f.dot 90 } 91 92 // SliceXX returns half-closed indexes of the segment corresponding to [f.start, f.end] 93 // as in the Go slice style. 94 // The intent is that start and end act like slice indices, 95 // i.e. the slice returned should look as though 96 // you'd taken lines[f.start:f.end] 97 // Note that a zero length slice is fine, and is used 98 // for, e.g., inserting. 99 // Also, note, the indices need to be changed from 1-relative 100 // to 0-relative, since the API at all levels is 1-relative. 101 func (f *file) SliceX(startLine, endLine int) (int, int) { 102 var end, start int 103 debug("SliceX: f %v", f) 104 defer debug("SliceX done: f %v, start %v, end %v", f, start, end) 105 if startLine > len(f.lines) { 106 //f.start = len(f.lines) 107 //f.end = f.start 108 return len(f.data), len(f.data) 109 } else { 110 if startLine > 0 { 111 start = f.lines[startLine-1] 112 } 113 } 114 end = start 115 if endLine < startLine { 116 endLine = startLine 117 } 118 if endLine != startLine { 119 if endLine >= len(f.lines) { 120 //f.end = len(f.lines) 121 end = len(f.data) 122 return start, end 123 } 124 end = f.lines[endLine-1] 125 } 126 debug("SliceX: f %v, start %v end %v\n", f, start, end) 127 return start, end 128 } 129 130 func (f *file) Slice(startLine, endLine int) ([]byte, int, int) { 131 s, e := f.SliceX(startLine, endLine) 132 return f.data[s:e], s, e 133 } 134 135 func (f *file) Replace(n []byte, startLine, endLine int) (int, error) { 136 defer debug("Replace done: f %v", f) 137 if f.data != nil { 138 start, end := f.SliceX(startLine, endLine) 139 debug("replace: f %v start %v end %v", f, start, end) 140 pre := f.data[0:start] 141 post := f.data[end:] 142 debug("replace: pre is %v, post is %v\n", pre, post) 143 var b bytes.Buffer 144 b.Write(pre) 145 b.Write(n) 146 b.Write(post) 147 f.data = b.Bytes() 148 } else { 149 f.data = n 150 } 151 f.fixLines() 152 return len(n), nil 153 } 154 155 // Read reads strings from the file after . 156 // If there are lines already the new lines are inserted after '.' 157 // dot is unchanged. 158 func (f *file) Read(r io.Reader, startLine, endLine int) (int, error) { 159 debug("Read: r %v, startLine %v endLine %v", r, startLine, endLine) 160 d, err := ioutil.ReadAll(r) 161 debug("ReadAll returns %v, %v", d, err) 162 if err != nil { 163 return -1, err 164 } 165 // in ed, for text read, the line # is a point, not a line. 166 // For other commands, it's a line. Hence for reading 167 // line x, we need to make a point at line x+1 and 168 // read there. 169 startLine = startLine + 1 170 return f.Replace(d, startLine, endLine) 171 } 172 173 // Write writes the lines out from start to end, inclusive. 174 // dot is unchanged. 175 func (f *file) Write(w io.Writer, startLine, endLine int) (int, error) { 176 if endLine < startLine || startLine < 1 || endLine > len(f.lines)+1 { 177 return -1, fmt.Errorf("file is %d lines and [start, end] is [%d, %d]", len(f.lines), startLine, endLine) 178 } 179 180 start, end := f.SliceX(startLine, endLine) 181 amt, err := w.Write(f.data[start:end]) 182 183 return amt, err 184 } 185 186 func (f *file) Print(w io.Writer, start, end int) (int, error) { 187 debug("Print %v %v %v", f.data, start, end) 188 i, err := f.Write(w, start, end) 189 if err != nil && start < end { 190 f.dot = end + 1 191 } 192 return i, err 193 } 194 195 // Write writes the lines out from start to end, inclusive. 196 // dot is unchanged. 197 func (f *file) WriteFile(n string, startLine, endLine int) (int, error) { 198 out, err := os.Create(n) 199 if err != nil { 200 return -1, err 201 } 202 defer out.Close() 203 return f.Write(out, startLine, endLine) 204 } 205 206 // Sub replaces the regexp with a different one. 207 func (f *file) Sub(re, n, opt string, startLine, endLine int) error { 208 debug("Sub re %s n %s opt %s\n", re, n, opt) 209 if re == "" { 210 return fmt.Errorf("Empty RE") 211 } 212 r, err := regexp.Compile(re) 213 if err != nil { 214 return err 215 } 216 var opts = make(map[byte]bool) 217 for i := range opt { 218 opts[opt[i]] = true 219 } 220 221 o, start, end := f.Slice(startLine, endLine) 222 debug("Slice from [%v,%v] is [%v, %v] %v", startLine, endLine, start, end, string(o)) 223 // Lines can turn into two lines. All kinds of stuff can happen. 224 // So 225 // Break it into lines 226 // make copies of those lines. 227 // for each line, do the sub 228 // paste it back together 229 // put it back in to f.data 230 b := makeLines(o) 231 if b == nil { 232 return nil 233 } 234 for i := range b { 235 var replaced bool 236 debug("Sub: before b[i] is %v", b[i]) 237 b[i] = r.ReplaceAllFunc(b[i], func(o []byte) []byte { 238 debug("Sub: func called with %v", o) 239 if opts['g'] || !replaced { 240 f.dirty = true 241 replaced = true 242 return []byte(n) 243 } 244 return o 245 }) 246 debug("Sub: after b[i] is %v", b[i]) 247 } 248 249 debug("replaced o %v with n %v\n", o, b) 250 var repl = make([]byte, start) 251 copy(repl, f.data[0:start]) 252 for _, v := range b { 253 repl = append(repl, v...) 254 } 255 256 f.data = append(repl, f.data[end:]...) 257 f.fixLines() 258 if opts['p'] { 259 _, err = f.Write(os.Stdout, startLine, endLine) 260 } 261 return err 262 } 263 264 func (f *file) Dirty(d bool) { 265 f.dirty = d 266 } 267 268 func (f *file) IsDirty() bool { 269 return f.dirty 270 } 271 272 // Range returns the closed range 273 // of lines. 274 func (f *file) Range() (int, int) { 275 if f.lines == nil || len(f.lines) == 0 { 276 return 0, 0 277 } 278 return 1, len(f.lines) 279 } 280 281 func (f *file) Equal(e Editor) error { 282 var errors string 283 g := e.(*file) 284 // we should verify dot but let's not do that just yet, we don't 285 // have it right. 286 if !reflect.DeepEqual(f.lines, g.lines) { 287 errors += fmt.Sprintf("%v vs %v: lines don't match", f.lines, g.lines) 288 } 289 if len(f.data) != len(g.data) { 290 errors += fmt.Sprintf("data len differs: %v vs %v", len(f.data), len(g.data)) 291 } else { 292 for i := range f.data { 293 if f.data[i] != g.data[i] { 294 errors += fmt.Sprintf("%d: %v vs %v: data doesn't match", i, f.data[i], g.data[i]) 295 } 296 } 297 } 298 if errors != "" { 299 return fmt.Errorf(errors) 300 } 301 return nil 302 } 303 304 func (f *file) Dot() int { 305 return f.dot 306 } 307 308 func (f *file) Move(dot int) { 309 f.dot = dot 310 } 311 312 func NewTextEditor(a ...editorArg) (Editor, error) { 313 var f = &file{} 314 315 for _, v := range a { 316 if err := v(f); err != nil { 317 return nil, err 318 } 319 } 320 f.dot = 1 321 return f, nil 322 }