github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/complete/newline.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 "fmt" 9 "io" 10 "log" 11 "os" 12 "path/filepath" 13 "strings" 14 ) 15 16 // NewerLineReader reads a char and changes state. It does no I/O. 17 // It maintains information such that a caller can figure out 18 // what to do with a line. 19 type NewerLineReader struct { 20 // Completer for this LineReader 21 C Completer 22 F Completer 23 // Prompt is a prompt 24 Prompt string 25 // Lines holds data as it is read. 26 Line string 27 // FullLine holds the last full line read 28 FullLine string 29 // Exact is the exact match. 30 // It can be "" if there is not one. 31 Exact string 32 // Candidates are any completion candidates. 33 // The UI can decide how to handle them. 34 Candidates []string 35 EOF bool 36 Fields int 37 // we only try to do completions if Tabbed is true 38 Tabbed bool 39 } 40 41 // NewNewerLineReader returns a LineReader. 42 func NewNewerLineReader(c, f Completer) *NewerLineReader { 43 return &NewerLineReader{C: c, F: f} 44 } 45 46 // ReadChar reads one character and processes it. It is inflexible by design. 47 func (l *NewerLineReader) ReadChar(b byte) (err error) { 48 defer func() { 49 l.Fields = len(strings.Fields(l.Line)) 50 }() 51 c := l.C 52 Debug("l.Fields %d", l.Fields) 53 if l.Fields > 1 || strings.Trim(l.Line, " ") != l.Line { 54 c = l.F 55 } 56 Debug("NewerLineReader: start with %v", l) 57 l.Exact, l.Candidates = "", []string{} 58 switch b { 59 case '\t': 60 if !l.Tabbed { 61 l.Tabbed = true 62 return nil 63 } 64 default: 65 l.Tabbed = false 66 } 67 switch b { 68 default: 69 Debug("NewerLineReader.Just add it to line and pipe") 70 l.Line += string(b) 71 case controlD: 72 l.EOF = true 73 return io.EOF 74 case backSpace, del: 75 s := l.Line 76 if len(s) > 0 { 77 s = s[:len(s)-1] 78 l.Line = s 79 } 80 case '\n', '\r': 81 Debug("ERREOL") 82 return ErrEOL 83 case ' ': 84 l.Line += string(b) 85 return nil 86 case '\t': 87 ll := len(l.Line) 88 // Special case: if there's nothing in the line yet, 89 // just return. 90 if ll == 0 { 91 return nil 92 } 93 s := l.Line 94 bl := strings.LastIndexAny(s, " ") 95 flds := strings.Fields(s) 96 Debug("fields of %q is %v", s, flds) 97 var cc string 98 // The rules are complex. 99 // It might be zero length. 100 // It might end in whitespace 101 // It might be one or more things 102 switch { 103 case ll == 0 || bl == ll-1: 104 case len(flds) == 0: 105 cc = flds[0] 106 default: 107 cc = flds[len(flds)-1] 108 } 109 110 Debug("ReadChar.Try complete with %s", cc) 111 x, cmpl, err := c.Complete(cc) 112 Debug("ReadChar.Complete returns %q, %v, %v", x, cmpl, err) 113 if err != nil { 114 return err 115 } 116 if len(cmpl) == 1 && x == "" { 117 Debug("Readchar: only one candidate, so use it") 118 x, cmpl = cmpl[0], []string{} 119 } 120 l.Exact = x 121 l.Candidates = cmpl 122 if x != "" && filepath.Clean(cc) != x { 123 // Paste the completion over where we found the candidate. 124 Debug("ReadChar: l.Line %q bl %d cmpl[0] %q", l.Line, bl, x) 125 // In the case of multicompleters, x might have multiple 126 // matches. So return the base, not the whole thing. 127 if !filepath.IsAbs(cc) { 128 x = filepath.Base(x) 129 } 130 l.Line = l.Line[:bl+1] + x 131 Debug("Return is %v", l) 132 l.Exact = x 133 return nil 134 } 135 // see if there is enough for a common prefix. 136 if x == "" { 137 p := Prefix(cmpl) 138 if p != "" { 139 l.Line = l.Line[:bl+1] + p 140 } 141 } 142 } 143 return nil 144 } 145 146 // ReadLine reads until an error occurs 147 func (l *NewerLineReader) ReadLine(r io.Reader, w io.Writer) error { 148 fmt.Print(l.Prompt) 149 for { 150 Debug("ReadLine: start with %v", l) 151 var b [1]byte 152 n, err := r.Read(b[:]) 153 if err != nil && err != io.EOF { 154 return err 155 } 156 Debug("ReadLine: got %s, %v, %v", b, n, err) 157 if n == 0 { 158 return io.EOF 159 } 160 p := l.Line 161 if err := l.ReadChar(b[0]); err != nil { 162 Debug("Readline: %v", l) 163 if err == io.EOF || err == ErrEOL { 164 Debug("Readline: %v", err) 165 l.FullLine = l.Line 166 l.Line = "" 167 return nil 168 } 169 return err 170 } 171 Debug("readline, exact %q, cand %q", l.Exact, l.Candidates) 172 if len(l.Candidates) > 1 { 173 c := l.Candidates 174 if l.Exact != "" { 175 c = append([]string{l.Exact}, l.Candidates...) 176 } 177 if _, err := fmt.Fprintf(w, "\r\n%s\r\n%s%s", c, l.Prompt, l.Line); err != nil { 178 log.Printf("Showing completions: %v", err) 179 } 180 continue 181 } 182 // How we handle this depends on whether it is a directory 183 if s, err := os.Stat(l.Exact); err == nil && s.IsDir() { 184 l.Line += "/" 185 } 186 if p != l.Line { 187 if _, err := w.Write([]byte("\r" + strings.Repeat(" ", len(l.Prompt+p)) + "\r" + l.Prompt + l.Line)); err != nil { 188 log.Print(err) 189 } 190 } 191 } 192 }