github.hscsec.cn/u-root/u-root@v7.0.0+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  }