github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/complete/line.go (about)

     1  // Copyright 2012-2018 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 complete
     6  
     7  import (
     8  	"io"
     9  	"path/filepath"
    10  	"strings"
    11  )
    12  
    13  const (
    14  	controlD  = 4
    15  	backSpace = 8
    16  	del       = 127
    17  )
    18  
    19  // LineReader has three things and returns one string.
    20  // The three things are an io.Reader, an io.Writer, and a Completer
    21  // Bytes are read one at a time, and depending on their value,
    22  // are written to the io.Writer.
    23  // If the completer returns 0 or 1 answers, a 0 or 1 length string is returned.
    24  // If there are two or more answers, they are printed and the line is
    25  // printed out again.
    26  // Most characters are just echoed.
    27  // Special handling: newline or space returns.
    28  // tab tries to complete
    29  // backspace erases. Since everything is ansi, we assume ansi.
    30  //
    31  // LineReader is used to implement input for a Completer.
    32  // It uses a Completer, io.Reader, io.Writer, and bytes.Buffer.
    33  // bytes are read from the reader, processed, held in the
    34  // bytes.Buffer and, as a side effect, some information is
    35  // written to the io.Writer.
    36  type LineReader struct {
    37  	// Completer for this LineReader
    38  	C Completer
    39  	// R is used for input. Most characters are stored in the
    40  	// Line, while some initiate special processing.
    41  	R io.Reader
    42  	// W is used for output, usually for showing completions.
    43  	W io.Writer
    44  	// Lines holds incoming data as it is read.
    45  	Line string
    46  	// Exact is the exact match.
    47  	// It can be "" if there is not one.
    48  	Exact string
    49  	// Candidates are any completion candidates.
    50  	// The UI can decide how to handle them.
    51  	Candidates []string
    52  	EOF        bool
    53  	Fields     int
    54  }
    55  
    56  // NewLineReader returns a LineReader.
    57  func NewLineReader(c Completer, r io.Reader, w io.Writer) *LineReader {
    58  	return &LineReader{C: c, R: r, W: w}
    59  }
    60  
    61  // ReadChar reads one character and processes it. It is inflexible by design.
    62  func (l *LineReader) ReadChar(b byte) (err error) {
    63  	defer func() {
    64  		l.Fields = len(strings.Fields(l.Line))
    65  	}()
    66  	Debug("LineReader: start with %v", l)
    67  	l.Exact, l.Candidates = "", []string{}
    68  	switch b {
    69  	default:
    70  		Debug("LineReader.Just add it to line and pipe")
    71  		l.Line += string(b)
    72  	case controlD:
    73  		l.EOF = true
    74  		return io.EOF
    75  	case backSpace, del:
    76  		s := l.Line
    77  		if len(s) > 0 {
    78  			s = s[:len(s)-1]
    79  			l.Line = s
    80  		}
    81  	case '\n', '\r':
    82  		return ErrEOL
    83  	case ' ':
    84  		l.Line += string(b)
    85  		return nil
    86  	case '\t':
    87  		ll := len(l.Line)
    88  		s := l.Line
    89  		bl := strings.LastIndexAny(s, " ")
    90  		flds := strings.Fields(s)
    91  		Debug("fields of %q is %v", s, flds)
    92  		var cc string
    93  		// The rules are complex.
    94  		// It might be zero length.
    95  		// It might end in whitespace
    96  		// It might be one or more things
    97  		switch {
    98  		case ll == 0 || bl == ll-1:
    99  		case len(flds) == 0:
   100  			cc = flds[0]
   101  		default:
   102  			cc = flds[len(flds)-1]
   103  		}
   104  
   105  		Debug("ReadChar.Try complete with %s", cc)
   106  		x, cmpl, err := l.C.Complete(cc)
   107  		Debug("ReadChar.Complete returns %q, %v, %v", x, cmpl, err)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		if len(cmpl) == 1 && x == "" {
   112  			Debug("Readchar: only one candidate, so use it")
   113  			x, cmpl = cmpl[0], []string{}
   114  		}
   115  		l.Exact = x
   116  		l.Candidates = cmpl
   117  		if x != "" && filepath.Clean(cc) != x {
   118  			// Paste the completion over where we found the candidate.
   119  			Debug("ReadChar: l.Line %q bl %d cmpl[0] %q", l.Line, bl, x)
   120  			// In the case of multicompleters, x might have multiple
   121  			// matches. So return the base, not the whole thing.
   122  			if !filepath.IsAbs(cc) {
   123  				x = filepath.Base(x)
   124  			}
   125  			l.Line = l.Line[:bl+1] + x
   126  			Debug("Return is %v", l)
   127  			l.Exact = x
   128  			return nil
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  // ReadLine reads until an error occurs
   135  func (l *LineReader) ReadLine() error {
   136  	for {
   137  		Debug("ReadLine: start with %v", l)
   138  		var b [1]byte
   139  		n, err := l.R.Read(b[:])
   140  		if err != nil && err != io.EOF {
   141  			return err
   142  		}
   143  		Debug("ReadLine: got %s, %v, %v", b, n, err)
   144  		if n == 0 {
   145  			return io.EOF
   146  		}
   147  		if err := l.ReadChar(b[0]); err != nil {
   148  			Debug("Readline: %v", l)
   149  			if err == io.EOF || err == ErrEOL {
   150  				Debug("Readline: %v", err)
   151  				return nil
   152  			}
   153  			return err
   154  		}
   155  	}
   156  }