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  }