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
|
2018-06-23 19:29:58 +00:00
|
|
|
wordSeparator string
|
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
|
2017-08-13 07:41:49 +00:00
|
|
|
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.
|
2017-08-13 07:41:49 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-24 05:19:00 +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++ {
|
2018-06-24 05:19:00 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|