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 }