go-prompt/completion.go

196 lines
4.5 KiB
Go
Raw Normal View History

2017-08-04 11:19:12 +00:00
package prompt
2017-08-09 12:33:47 +00:00
import (
"log"
"strings"
2018-06-21 16:11:58 +00:00
"github.com/mattn/go-runewidth"
2017-08-09 12:33:47 +00:00
)
2017-08-12 09:55:36 +00:00
const (
2018-06-21 16:11:58 +00:00
shortenSuffix = "..."
leftPrefix = " "
leftSuffix = " "
rightPrefix = " "
rightSuffix = " "
)
var (
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
2017-08-12 09:55:36 +00:00
completionMargin = leftMargin + rightMargin
)
2017-08-21 15:47:15 +00:00
// Suggest is printed when completing.
2017-08-04 11:30:50 +00:00
type Suggest struct {
2017-08-04 11:19:12 +00:00
Text string
Description string
}
2017-08-21 15:47:15 +00:00
// CompletionManager manages which suggestion is now selected.
2017-08-04 11:19:12 +00:00
type CompletionManager struct {
2017-08-09 12:33:47 +00:00
selected int // -1 means nothing one is selected.
tmp []Suggest
2017-08-12 09:59:10 +00:00
max uint16
2017-08-09 12:33:47 +00:00
completer Completer
2018-02-12 08:40:47 +00:00
verticalScroll int
wordSeparator string
2018-10-18 10:41:40 +00:00
showAtStart bool
2017-08-09 12:33:47 +00:00
}
2017-08-21 15:47:15 +00:00
// GetSelectedSuggestion returns the selected item.
2017-08-09 12:33:47 +00:00
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)
2017-08-11 09:24:44 +00:00
c.selected = -1
2017-08-09 12:33:47 +00:00
return Suggest{}, false
}
return c.tmp[c.selected], true
}
2018-02-13 16:14:22 +00:00
// GetSuggestions returns the list of suggestion.
2017-08-09 12:33:47 +00:00
func (c *CompletionManager) GetSuggestions() []Suggest {
return c.tmp
2017-08-04 11:19:12 +00:00
}
2017-08-21 15:47:15 +00:00
// Reset to select nothing.
2017-08-04 11:19:12 +00:00
func (c *CompletionManager) Reset() {
c.selected = -1
2018-02-12 08:40:47 +00:00
c.verticalScroll = 0
c.Update(*NewDocument())
2017-08-04 11:19:12 +00:00
return
}
2017-08-21 15:47:15 +00:00
// Update to update the suggestions.
func (c *CompletionManager) Update(in Document) {
2017-08-09 12:33:47 +00:00
c.tmp = c.completer(in)
2017-08-04 11:19:12 +00:00
return
}
2017-08-21 15:47:15 +00:00
// Previous to select the previous suggestion item.
2017-08-04 11:19:12 +00:00
func (c *CompletionManager) Previous() {
2018-02-12 08:40:47 +00:00
if c.verticalScroll == c.selected && c.selected > 0 {
c.verticalScroll--
}
2017-08-04 11:19:12 +00:00
c.selected--
2017-08-09 12:33:47 +00:00
c.update()
2017-08-04 11:19:12 +00:00
return
}
2017-08-21 15:47:15 +00:00
// Next to select the next suggestion item.
2017-08-04 11:19:12 +00:00
func (c *CompletionManager) Next() {
2018-02-12 08:40:47 +00:00
if c.verticalScroll+int(c.max)-1 == c.selected {
c.verticalScroll++
}
2017-08-04 11:19:12 +00:00
c.selected++
2017-08-09 12:33:47 +00:00
c.update()
2017-08-04 11:19:12 +00:00
return
}
2017-08-21 15:47:15 +00:00
// Completing returns whether the CompletionManager selects something one.
2017-08-04 11:19:12 +00:00
func (c *CompletionManager) Completing() bool {
return c.selected != -1
}
2017-08-09 12:33:47 +00:00
func (c *CompletionManager) update() {
2017-08-12 09:59:10 +00:00
max := int(c.max)
2017-08-09 12:33:47 +00:00
if len(c.tmp) < max {
max = len(c.tmp)
2017-08-04 11:19:12 +00:00
}
2018-02-12 08:40:47 +00:00
if c.selected >= len(c.tmp) {
2017-08-04 11:19:12 +00:00
c.Reset()
} else if c.selected < -1 {
2018-02-12 08:40:47 +00:00
c.selected = len(c.tmp) - 1
c.verticalScroll = len(c.tmp) - max
2017-08-04 11:19:12 +00:00
}
}
func deleteBreakLineCharacters(s string) string {
s = strings.Replace(s, "\n", "", -1)
s = strings.Replace(s, "\r", "", -1)
return s
}
2017-08-12 09:55:36 +00:00
func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
l := len(o)
n := make([]string, l)
2018-06-21 16:11:58 +00:00
lenPrefix := runewidth.StringWidth(prefix)
lenSuffix := runewidth.StringWidth(suffix)
lenShorten := runewidth.StringWidth(shortenSuffix)
2017-08-12 09:55:36 +00:00
min := lenPrefix + lenSuffix + lenShorten
for i := 0; i < l; i++ {
o[i] = deleteBreakLineCharacters(o[i])
2018-06-21 16:11:58 +00:00
w := runewidth.StringWidth(o[i])
if width < w {
width = w
2017-08-12 09:55:36 +00:00
}
}
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
}
2017-08-14 16:49:53 +00:00
if lenPrefix+width+lenSuffix > max {
2017-08-12 09:55:36 +00:00
width = max - lenPrefix - lenSuffix
}
for i := 0; i < l; i++ {
2018-06-21 16:11:58 +00:00
x := runewidth.StringWidth(o[i])
2017-08-12 09:55:36 +00:00
if x <= width {
spaces := strings.Repeat(" ", width-x)
n[i] = prefix + o[i] + spaces + suffix
} else if x > width {
2018-06-22 07:41:45 +00:00
x := runewidth.Truncate(o[i], width, shortenSuffix)
// When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
2018-06-22 08:04:55 +00:00
// But the length of this result is 10. So we need fill right using runewidth.FillRight.
n[i] = prefix + runewidth.FillRight(x, width) + suffix
2017-08-12 09:55:36 +00:00
}
}
return n, lenPrefix + width + lenSuffix
}
2017-08-12 09:59:10 +00:00
func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) {
num := len(suggests)
2017-08-09 12:33:47 +00:00
new = make([]Suggest, num)
2017-08-12 09:55:36 +00:00
left := make([]string, num)
2017-08-09 12:33:47 +00:00
for i := 0; i < num; i++ {
2017-08-12 09:59:10 +00:00
left[i] = suggests[i].Text
2017-08-09 12:33:47 +00:00
}
2017-08-12 09:55:36 +00:00
right := make([]string, num)
for i := 0; i < num; i++ {
2017-08-12 09:59:10 +00:00
right[i] = suggests[i].Description
2017-08-09 12:33:47 +00:00
}
2017-08-12 09:55:36 +00:00
left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix)
if leftWidth == 0 {
return []Suggest{}, 0
2017-08-09 12:33:47 +00:00
}
2017-08-14 16:49:53 +00:00
right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix)
2017-08-09 12:33:47 +00:00
for i := 0; i < num; i++ {
2017-08-12 09:55:36 +00:00
new[i] = Suggest{Text: left[i], Description: right[i]}
2017-08-09 12:33:47 +00:00
}
2017-08-12 09:55:36 +00:00
return new, leftWidth + rightWidth
2017-08-09 12:33:47 +00:00
}
2017-08-21 15:47:15 +00:00
// NewCompletionManager returns initialized CompletionManager object.
2017-08-09 12:33:47 +00:00
func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
2017-08-04 11:19:12 +00:00
return &CompletionManager{
2017-08-09 12:33:47 +00:00
selected: -1,
2017-08-12 09:59:10 +00:00
max: max,
2017-08-09 12:33:47 +00:00
completer: completer,
2018-02-12 08:40:47 +00:00
verticalScroll: 0,
2017-08-04 11:19:12 +00:00
}
}