2022-03-13 17:48:49 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// initList - List display details. Because of the way alternative completions
|
|
|
|
// are handled, MaxLength cannot be set when there are alternative completions.
|
|
|
|
func (g *CompletionGroup) initList(rl *Instance) {
|
|
|
|
|
|
|
|
// We may only ever have two different
|
|
|
|
// columns: (suggestions, and alternatives)
|
|
|
|
g.tcMaxX = 2
|
|
|
|
|
|
|
|
// We make the list anyway, especially if we need to use it later
|
|
|
|
if g.Descriptions == nil {
|
|
|
|
g.Descriptions = make(map[string]string)
|
|
|
|
}
|
|
|
|
if g.Aliases == nil {
|
|
|
|
g.Aliases = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute size of each completion item box. Group independent
|
|
|
|
g.tcMaxLength = rl.getListPad()
|
|
|
|
|
|
|
|
// Same for suggestions alt
|
|
|
|
g.tcMaxLengthAlt = 0
|
|
|
|
for i := range g.Suggestions {
|
|
|
|
if len(g.Suggestions[i]) > g.tcMaxLength {
|
|
|
|
g.tcMaxLength = len([]rune(g.Suggestions[i]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Max values depend on if we have alternative suggestions
|
|
|
|
if len(g.Aliases) == 0 {
|
|
|
|
g.tcMaxX = 1
|
|
|
|
} else {
|
|
|
|
g.tcMaxX = 2
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(g.Suggestions) > g.MaxLength {
|
|
|
|
g.tcMaxY = g.MaxLength
|
|
|
|
} else {
|
|
|
|
g.tcMaxY = len(g.Suggestions)
|
|
|
|
}
|
|
|
|
|
|
|
|
g.tcPosX = 0
|
|
|
|
g.tcPosY = 0
|
|
|
|
g.tcOffset = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// moveTabListHighlight - Moves the highlighting for currently selected completion item (list display)
|
|
|
|
// We don't care about the x, because only can have 2 columns of selectable choices (--long and -s)
|
|
|
|
func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
|
|
|
|
|
|
|
// We dont' pass to x, because not managed by callers
|
|
|
|
g.tcPosY += x
|
|
|
|
g.tcPosY += y
|
|
|
|
|
|
|
|
// Lines
|
|
|
|
if g.tcPosY < 1 {
|
|
|
|
if rl.tabCompletionReverse {
|
|
|
|
if g.tcOffset > 0 {
|
|
|
|
g.tcPosY = 1
|
|
|
|
g.tcOffset--
|
|
|
|
} else {
|
|
|
|
return true, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if g.tcPosY > g.tcMaxY {
|
|
|
|
g.tcPosY--
|
|
|
|
g.tcOffset++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Once we get to the end of choices: check which column we were selecting.
|
|
|
|
if g.tcOffset+g.tcPosY > len(g.Suggestions) {
|
|
|
|
// If we have alternative options and that we are not yet
|
|
|
|
// completing them, start on top of their column
|
|
|
|
if g.tcPosX == 0 && len(g.Aliases) > 0 {
|
|
|
|
g.tcPosX++
|
|
|
|
g.tcPosY = 1
|
|
|
|
g.tcOffset = 0
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Else no alternatives, return for next group.
|
|
|
|
// Reset all values, in case we pass on them again.
|
|
|
|
g.tcPosX = 0 // First column
|
|
|
|
g.tcPosY = 1 // first row
|
|
|
|
g.tcOffset = 0
|
|
|
|
return true, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here we must check, in x == 1, that the current choice
|
|
|
|
// is not empty. Handle for both reverse and forward movements.
|
|
|
|
sugg := g.Suggestions[g.tcPosY-1]
|
|
|
|
_, ok := g.Aliases[sugg]
|
|
|
|
if !ok && g.tcPosX == 1 {
|
|
|
|
if rl.tabCompletionReverse {
|
|
|
|
for i := len(g.Suggestions[:g.tcPosY-1]); i > 0; i-- {
|
|
|
|
su := g.Suggestions[i]
|
|
|
|
if _, ok := g.Aliases[su]; ok {
|
|
|
|
g.tcPosY -= (len(g.Suggestions[:g.tcPosY-1])) - i
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g.tcPosX = 0
|
|
|
|
g.tcPosY = g.tcMaxY
|
|
|
|
|
|
|
|
} else {
|
|
|
|
for i, su := range g.Suggestions[g.tcPosY-1:] {
|
|
|
|
if _, ok := g.Aliases[su]; ok {
|
|
|
|
g.tcPosY += i
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup offset if needs to be.
|
|
|
|
// TODO: should be rewrited to conditionally process rolling menus with alternatives
|
|
|
|
if g.tcOffset+g.tcPosY < 1 && len(g.Suggestions) > 0 {
|
|
|
|
g.tcPosY = g.tcMaxY
|
|
|
|
g.tcOffset = len(g.Suggestions) - g.tcMaxY
|
|
|
|
}
|
|
|
|
if g.tcOffset < 0 {
|
|
|
|
g.tcOffset = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// MIGHT BE NEEDED IF PROBLEMS WIHT ROLLING COMPLETIONS
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
// Once we get to the end of choices: check which column we were selecting.
|
|
|
|
// We use +1 because we may have a single suggestion, and we just want "a ratio"
|
|
|
|
// if g.tcOffset+g.tcPosY > len(g.Suggestions) {
|
|
|
|
//
|
|
|
|
// // If we have alternative options and that we are not yet
|
|
|
|
// // completing them, start on top of their column
|
|
|
|
// if g.tcPosX == 1 && len(g.SuggestionsAlt) > 0 {
|
|
|
|
// g.tcPosX++
|
|
|
|
// g.tcPosY = 1
|
|
|
|
// g.tcOffset = 0
|
|
|
|
// return false
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Else no alternatives, return for next group.
|
|
|
|
// g.tcPosY = 1
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeList - A list completion string
|
|
|
|
func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
|
|
|
|
|
|
|
// Print group title and adjust offset if there is one.
|
|
|
|
if g.Name != "" {
|
|
|
|
comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, g.Name, RESET)
|
|
|
|
rl.tcUsedY++
|
|
|
|
}
|
|
|
|
|
|
|
|
termWidth := GetTermWidth()
|
|
|
|
if termWidth < 20 {
|
|
|
|
// terminal too small. Probably better we do nothing instead of crash
|
|
|
|
// We are more conservative than lmorg, and push it to 20 instead of 10
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Suggestion cells dimensions
|
|
|
|
maxLength := g.tcMaxLength
|
|
|
|
if maxLength > termWidth-9 {
|
|
|
|
maxLength = termWidth - 9
|
|
|
|
}
|
|
|
|
cellWidth := strconv.Itoa(maxLength)
|
|
|
|
|
|
|
|
// Alternative suggestion cells dimensions
|
|
|
|
maxLengthAlt := g.tcMaxLengthAlt + 2
|
|
|
|
if maxLengthAlt > termWidth-9 {
|
|
|
|
maxLengthAlt = termWidth - 9
|
|
|
|
}
|
|
|
|
cellWidthAlt := strconv.Itoa(maxLengthAlt)
|
|
|
|
|
|
|
|
// Descriptions cells dimensions
|
|
|
|
maxDescWidth := termWidth - maxLength - maxLengthAlt - 4
|
|
|
|
|
|
|
|
// function highlights the cell depending on current selector place.
|
|
|
|
highlight := func(y int, x int) string {
|
|
|
|
if y == g.tcPosY && x == g.tcPosX && g.isCurrent {
|
2022-04-18 03:39:53 +00:00
|
|
|
return seqInvert
|
2022-03-13 17:48:49 +00:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each line in completions
|
|
|
|
y := 0
|
|
|
|
for i := g.tcOffset; i < len(g.Suggestions); i++ {
|
|
|
|
y++ // Consider next item
|
|
|
|
if y > g.tcMaxY {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Main suggestion
|
|
|
|
item := g.Suggestions[i]
|
|
|
|
if len(item) > maxLength {
|
|
|
|
item = item[:maxLength-3] + "..."
|
|
|
|
}
|
2022-07-26 23:24:02 +00:00
|
|
|
sugg := fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), fmtEscape(item))
|
2022-03-13 17:48:49 +00:00
|
|
|
|
|
|
|
// Alt suggestion
|
|
|
|
alt, ok := g.Aliases[item]
|
|
|
|
if ok {
|
2022-07-26 23:24:02 +00:00
|
|
|
alt = fmt.Sprintf(" %s%"+cellWidthAlt+"s", highlight(y, 1), fmtEscape(alt))
|
2022-03-13 17:48:49 +00:00
|
|
|
} else {
|
|
|
|
// Else, make an empty cell
|
|
|
|
alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces
|
|
|
|
}
|
|
|
|
|
|
|
|
// Description
|
|
|
|
description := g.Descriptions[g.Suggestions[i]]
|
|
|
|
if len(description) > maxDescWidth {
|
|
|
|
description = description[:maxDescWidth-3] + "..." + RESET + "\n"
|
|
|
|
} else {
|
|
|
|
description += "\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Total completion line
|
|
|
|
comp += sugg + seqReset + alt + " " + seqReset + description
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the equivalent of this group's size to final screen clearing
|
|
|
|
// Can be set and used only if no alterative completions have been given.
|
|
|
|
if len(g.Aliases) == 0 {
|
|
|
|
if len(g.Suggestions) > g.MaxLength {
|
|
|
|
rl.tcUsedY += g.MaxLength
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY += len(g.Suggestions)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rl.tcUsedY += len(g.Suggestions)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rl *Instance) getListPad() (pad int) {
|
|
|
|
for _, group := range rl.tcGroups {
|
|
|
|
if group.DisplayType == TabDisplayList {
|
|
|
|
for i := range group.Suggestions {
|
|
|
|
if len(group.Suggestions[i]) > pad {
|
|
|
|
pad = len([]rune(group.Suggestions[i]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|