package prompt import ( "log" "strings" "github.com/mattn/go-runewidth" ) const ( shortenSuffix = "..." leftPrefix = " " leftSuffix = " " rightPrefix = " " rightSuffix = " " ) var ( leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix) rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix) completionMargin = leftMargin + rightMargin ) // Suggest is printed when completing. type Suggest struct { Text string Description string } // CompletionManager manages which suggestion is now selected. type CompletionManager struct { selected int // -1 means nothing one is selected. tmp []Suggest max uint16 completer Completer verticalScroll int wordSeparator string showAtStart bool } // GetSelectedSuggestion returns the selected item. func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) { if c.selected == -1 { return Suggest{}, false } else if c.selected < -1 { log.Printf("[ERROR] shoud be reached here, selected=%d", c.selected) c.selected = -1 return Suggest{}, false } return c.tmp[c.selected], true } // GetSuggestions returns the list of suggestion. func (c *CompletionManager) GetSuggestions() []Suggest { return c.tmp } // Reset to select nothing. func (c *CompletionManager) Reset() { c.selected = -1 c.verticalScroll = 0 c.Update(*NewDocument()) return } // Update to update the suggestions. func (c *CompletionManager) Update(in Document) { c.tmp = c.completer(in) return } // Previous to select the previous suggestion item. func (c *CompletionManager) Previous() { if c.verticalScroll == c.selected && c.selected > 0 { c.verticalScroll-- } c.selected-- c.update() return } // Next to select the next suggestion item. func (c *CompletionManager) Next() { if c.verticalScroll+int(c.max)-1 == c.selected { c.verticalScroll++ } c.selected++ c.update() return } // Completing returns whether the CompletionManager selects something one. func (c *CompletionManager) Completing() bool { return c.selected != -1 } func (c *CompletionManager) update() { max := int(c.max) if len(c.tmp) < max { max = len(c.tmp) } if c.selected >= len(c.tmp) { c.Reset() } else if c.selected < -1 { c.selected = len(c.tmp) - 1 c.verticalScroll = len(c.tmp) - max } } func deleteBreakLineCharacters(s string) string { s = strings.Replace(s, "\n", "", -1) s = strings.Replace(s, "\r", "", -1) return s } func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) { l := len(o) n := make([]string, l) lenPrefix := runewidth.StringWidth(prefix) lenSuffix := runewidth.StringWidth(suffix) lenShorten := runewidth.StringWidth(shortenSuffix) min := lenPrefix + lenSuffix + lenShorten for i := 0; i < l; i++ { o[i] = deleteBreakLineCharacters(o[i]) w := runewidth.StringWidth(o[i]) if width < w { width = w } } if width == 0 { return n, 0 } if min >= max { log.Println("[WARN] formatTexts: max is lower than length of prefix and suffix.") return n, 0 } if lenPrefix+width+lenSuffix > max { width = max - lenPrefix - lenSuffix } for i := 0; i < l; i++ { x := runewidth.StringWidth(o[i]) if x <= width { spaces := strings.Repeat(" ", width-x) n[i] = prefix + o[i] + spaces + suffix } else if x > width { x := runewidth.Truncate(o[i], width, shortenSuffix) // When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..." // But the length of this result is 10. So we need fill right using runewidth.FillRight. n[i] = prefix + runewidth.FillRight(x, width) + suffix } } return n, lenPrefix + width + lenSuffix } func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) { num := len(suggests) new = make([]Suggest, num) left := make([]string, num) for i := 0; i < num; i++ { left[i] = suggests[i].Text } right := make([]string, num) for i := 0; i < num; i++ { right[i] = suggests[i].Description } left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix) if leftWidth == 0 { return []Suggest{}, 0 } right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix) for i := 0; i < num; i++ { new[i] = Suggest{Text: left[i], Description: right[i]} } return new, leftWidth + rightWidth } // NewCompletionManager returns initialized CompletionManager object. func NewCompletionManager(completer Completer, max uint16) *CompletionManager { return &CompletionManager{ selected: -1, max: max, completer: completer, verticalScroll: 0, } }