...

Source file src/github.com/c-bata/go-prompt/completion.go

Documentation: github.com/c-bata/go-prompt

     1  package prompt
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/c-bata/go-prompt/internal/debug"
     7  	runewidth "github.com/mattn/go-runewidth"
     8  )
     9  
    10  const (
    11  	shortenSuffix = "..."
    12  	leftPrefix    = " "
    13  	leftSuffix    = " "
    14  	rightPrefix   = " "
    15  	rightSuffix   = " "
    16  )
    17  
    18  var (
    19  	leftMargin       = runewidth.StringWidth(leftPrefix + leftSuffix)
    20  	rightMargin      = runewidth.StringWidth(rightPrefix + rightSuffix)
    21  	completionMargin = leftMargin + rightMargin
    22  )
    23  
    24  // Suggest is printed when completing.
    25  type Suggest struct {
    26  	Text        string
    27  	Description string
    28  }
    29  
    30  // CompletionManager manages which suggestion is now selected.
    31  type CompletionManager struct {
    32  	selected  int // -1 means nothing one is selected.
    33  	tmp       []Suggest
    34  	max       uint16
    35  	completer Completer
    36  
    37  	verticalScroll int
    38  	wordSeparator  string
    39  	showAtStart    bool
    40  }
    41  
    42  // GetSelectedSuggestion returns the selected item.
    43  func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
    44  	if c.selected == -1 {
    45  		return Suggest{}, false
    46  	} else if c.selected < -1 {
    47  		debug.Assert(false, "must not reach here")
    48  		c.selected = -1
    49  		return Suggest{}, false
    50  	}
    51  	return c.tmp[c.selected], true
    52  }
    53  
    54  // GetSuggestions returns the list of suggestion.
    55  func (c *CompletionManager) GetSuggestions() []Suggest {
    56  	return c.tmp
    57  }
    58  
    59  // Reset to select nothing.
    60  func (c *CompletionManager) Reset() {
    61  	c.selected = -1
    62  	c.verticalScroll = 0
    63  	c.Update(*NewDocument())
    64  }
    65  
    66  // Update to update the suggestions.
    67  func (c *CompletionManager) Update(in Document) {
    68  	c.tmp = c.completer(in)
    69  }
    70  
    71  // Previous to select the previous suggestion item.
    72  func (c *CompletionManager) Previous() {
    73  	if c.verticalScroll == c.selected && c.selected > 0 {
    74  		c.verticalScroll--
    75  	}
    76  	c.selected--
    77  	c.update()
    78  }
    79  
    80  // Next to select the next suggestion item.
    81  func (c *CompletionManager) Next() {
    82  	if c.verticalScroll+int(c.max)-1 == c.selected {
    83  		c.verticalScroll++
    84  	}
    85  	c.selected++
    86  	c.update()
    87  }
    88  
    89  // Completing returns whether the CompletionManager selects something one.
    90  func (c *CompletionManager) Completing() bool {
    91  	return c.selected != -1
    92  }
    93  
    94  func (c *CompletionManager) update() {
    95  	max := int(c.max)
    96  	if len(c.tmp) < max {
    97  		max = len(c.tmp)
    98  	}
    99  
   100  	if c.selected >= len(c.tmp) {
   101  		c.Reset()
   102  	} else if c.selected < -1 {
   103  		c.selected = len(c.tmp) - 1
   104  		c.verticalScroll = len(c.tmp) - max
   105  	}
   106  }
   107  
   108  func deleteBreakLineCharacters(s string) string {
   109  	s = strings.Replace(s, "\n", "", -1)
   110  	s = strings.Replace(s, "\r", "", -1)
   111  	return s
   112  }
   113  
   114  func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
   115  	l := len(o)
   116  	n := make([]string, l)
   117  
   118  	lenPrefix := runewidth.StringWidth(prefix)
   119  	lenSuffix := runewidth.StringWidth(suffix)
   120  	lenShorten := runewidth.StringWidth(shortenSuffix)
   121  	min := lenPrefix + lenSuffix + lenShorten
   122  	for i := 0; i < l; i++ {
   123  		o[i] = deleteBreakLineCharacters(o[i])
   124  
   125  		w := runewidth.StringWidth(o[i])
   126  		if width < w {
   127  			width = w
   128  		}
   129  	}
   130  
   131  	if width == 0 {
   132  		return n, 0
   133  	}
   134  	if min >= max {
   135  		return n, 0
   136  	}
   137  	if lenPrefix+width+lenSuffix > max {
   138  		width = max - lenPrefix - lenSuffix
   139  	}
   140  
   141  	for i := 0; i < l; i++ {
   142  		x := runewidth.StringWidth(o[i])
   143  		if x <= width {
   144  			spaces := strings.Repeat(" ", width-x)
   145  			n[i] = prefix + o[i] + spaces + suffix
   146  		} else if x > width {
   147  			x := runewidth.Truncate(o[i], width, shortenSuffix)
   148  			// When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
   149  			// But the length of this result is 10. So we need fill right using runewidth.FillRight.
   150  			n[i] = prefix + runewidth.FillRight(x, width) + suffix
   151  		}
   152  	}
   153  	return n, lenPrefix + width + lenSuffix
   154  }
   155  
   156  func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) {
   157  	num := len(suggests)
   158  	new = make([]Suggest, num)
   159  
   160  	left := make([]string, num)
   161  	for i := 0; i < num; i++ {
   162  		left[i] = suggests[i].Text
   163  	}
   164  	right := make([]string, num)
   165  	for i := 0; i < num; i++ {
   166  		right[i] = suggests[i].Description
   167  	}
   168  
   169  	left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix)
   170  	if leftWidth == 0 {
   171  		return []Suggest{}, 0
   172  	}
   173  	right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix)
   174  
   175  	for i := 0; i < num; i++ {
   176  		new[i] = Suggest{Text: left[i], Description: right[i]}
   177  	}
   178  	return new, leftWidth + rightWidth
   179  }
   180  
   181  // NewCompletionManager returns initialized CompletionManager object.
   182  func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
   183  	return &CompletionManager{
   184  		selected:  -1,
   185  		max:       max,
   186  		completer: completer,
   187  
   188  		verticalScroll: 0,
   189  	}
   190  }
   191  

View as plain text