2022-03-13 17:48:49 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2022-11-25 22:39:18 +00:00
|
|
|
"github.com/rivo/uniseg"
|
|
|
|
)
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// initGrid - Grid display details. Called each time we want to be sure to have
|
|
|
|
// a working completion group either immediately, or later on. Generally defered.
|
|
|
|
func (g *CompletionGroup) initGrid(rl *Instance) {
|
|
|
|
|
|
|
|
// Compute size of each completion item box
|
|
|
|
tcMaxLength := 1
|
|
|
|
for i := range g.Suggestions {
|
2022-11-25 22:39:18 +00:00
|
|
|
if uniseg.GraphemeClusterCount(g.Suggestions[i]) > tcMaxLength {
|
|
|
|
tcMaxLength = uniseg.GraphemeClusterCount(g.Suggestions[i])
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g.tcPosX = 0
|
|
|
|
g.tcPosY = 1
|
|
|
|
g.tcOffset = 0
|
|
|
|
|
|
|
|
// Max number of columns
|
|
|
|
g.tcMaxX = GetTermWidth() / (tcMaxLength + 2)
|
|
|
|
if g.tcMaxX < 1 {
|
|
|
|
g.tcMaxX = 1 // avoid a divide by zero error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maximum number of lines
|
|
|
|
maxY := len(g.Suggestions) / g.tcMaxX
|
|
|
|
rest := len(g.Suggestions) % g.tcMaxX
|
|
|
|
if rest != 0 {
|
|
|
|
// if rest != 0 && maxY != 1 {
|
|
|
|
maxY++
|
|
|
|
}
|
|
|
|
if maxY > g.MaxLength {
|
|
|
|
g.tcMaxY = g.MaxLength
|
|
|
|
} else {
|
|
|
|
g.tcMaxY = maxY
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// moveTabGridHighlight - Moves the highlighting for currently selected completion item (grid display)
|
|
|
|
func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
|
|
|
|
|
|
|
g.tcPosX += x
|
|
|
|
g.tcPosY += y
|
|
|
|
|
|
|
|
// Columns
|
|
|
|
if g.tcPosX < 1 {
|
|
|
|
if g.tcPosY == 1 && rl.tabCompletionReverse {
|
|
|
|
g.tcPosX = 1
|
|
|
|
g.tcPosY = 0
|
|
|
|
} else {
|
|
|
|
// This is when multiple ligns, not yet on first one.
|
|
|
|
g.tcPosX = g.tcMaxX
|
|
|
|
g.tcPosY--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if g.tcPosY > g.tcMaxY {
|
|
|
|
g.tcPosY = 1
|
|
|
|
return true, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we must move to next line in same group
|
|
|
|
if g.tcPosX > g.tcMaxX {
|
|
|
|
g.tcPosX = 1
|
|
|
|
g.tcPosY++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Real max number of suggestions.
|
|
|
|
max := g.tcMaxX * g.tcMaxY
|
|
|
|
if max > len(g.Suggestions) {
|
|
|
|
max = len(g.Suggestions)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We arrived at the end of suggestions. This condition can never be triggered
|
|
|
|
// while going in the reverse order, only forward, so no further checks in it.
|
|
|
|
if (g.tcMaxX*(g.tcPosY-1))+g.tcPosX > max {
|
|
|
|
return true, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// In case we are reverse cycling and currently selecting the first item,
|
|
|
|
// we adjust the coordinates to point to the last item and return
|
|
|
|
// We set g.tcPosY because the printer needs to get the a candidate nonetheless.
|
|
|
|
if rl.tabCompletionReverse && g.tcPosX == 1 && g.tcPosY == 0 {
|
|
|
|
g.tcPosY = 1
|
|
|
|
return true, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// By default, come back to this group for next item.
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeGrid - A grid completion string
|
|
|
|
func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
|
|
|
|
|
|
|
// If group title, print it and adjust offset.
|
|
|
|
if g.Name != "" {
|
2022-07-26 23:24:02 +00:00
|
|
|
comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, fmtEscape(g.Name), RESET)
|
2022-03-13 17:48:49 +00:00
|
|
|
rl.tcUsedY++
|
|
|
|
}
|
|
|
|
|
2022-11-25 22:39:18 +00:00
|
|
|
cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 4)
|
2022-03-13 17:48:49 +00:00
|
|
|
x := 0
|
|
|
|
y := 1
|
|
|
|
|
|
|
|
for i := range g.Suggestions {
|
|
|
|
x++
|
|
|
|
if x > g.tcMaxX {
|
|
|
|
x = 1
|
|
|
|
y++
|
|
|
|
if y > g.tcMaxY {
|
|
|
|
y--
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
comp += "\r\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x == g.tcPosX && y == g.tcPosY) && (g.isCurrent) {
|
2022-04-18 03:39:53 +00:00
|
|
|
comp += seqInvert
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
2022-11-25 22:39:18 +00:00
|
|
|
sugg := g.Suggestions[i]
|
|
|
|
if len(sugg) > GetTermWidth() {
|
|
|
|
sugg = sugg[:GetTermWidth() - 4] + "..."
|
|
|
|
}
|
|
|
|
formatStr := "%-"+cellWidth+"s%s "
|
|
|
|
if g.tcMaxX == 1 {
|
|
|
|
formatStr = "%s%s"
|
|
|
|
}
|
|
|
|
comp += fmt.Sprintf(formatStr, fmtEscape(sugg), seqReset)
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Always add a newline to the group if the end if not punctuated with one
|
|
|
|
if !strings.HasSuffix(comp, "\n") {
|
|
|
|
comp += "\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the equivalent of this group's size to final screen clearing.
|
|
|
|
// This is either the max allowed print size for this group, or its actual size if inferior.
|
|
|
|
if g.MaxLength < y {
|
|
|
|
rl.tcUsedY += g.MaxLength
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY += y
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|