Corrections + ajout de la possibilité de naviguer dans la complétion avec les touches fléchées
This commit is contained in:
parent
8694cb0d18
commit
bea7084d5b
|
@ -52,7 +52,7 @@ func main() {
|
||||||
## Raccourcis clavier <u>disponibles</u>
|
## Raccourcis clavier <u>disponibles</u>
|
||||||
|
|
||||||
| Raccourci clavier | Action |
|
| Raccourci clavier | Action |
|
||||||
| -------------------------------------- | ------------------------------------------------------------ |
|
| ----------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||||
| Ctrl+A, Home | Retour au début de la saisie |
|
| Ctrl+A, Home | Retour au début de la saisie |
|
||||||
| Ctrl+E, End | Retour à la fin de la saisie |
|
| Ctrl+E, End | Retour à la fin de la saisie |
|
||||||
| Ctrl+B, Left | Déplacement d’un caractère vers la gauche |
|
| Ctrl+B, Left | Déplacement d’un caractère vers la gauche |
|
||||||
|
@ -74,5 +74,7 @@ func main() {
|
||||||
| Ctrl+Y | Copie le dernier élément supprimé |
|
| Ctrl+Y | Copie le dernier élément supprimé |
|
||||||
| Alt+Y | Remonte dans l’historique des éléments supprimés et les copie (implique Ctrl+Y précédemment lancé) |
|
| Alt+Y | Remonte dans l’historique des éléments supprimés et les copie (implique Ctrl+Y précédemment lancé) |
|
||||||
| Ctrl+G, Cancel | Annule les dernières actions temporaires (historique, recherche, copie, historique de copie) |
|
| Ctrl+G, Cancel | Annule les dernières actions temporaires (historique, recherche, copie, historique de copie) |
|
||||||
| Tab | Complétion suivante |
|
| Tab, Up (si complétion commencée) | Complétion suivante |
|
||||||
| Shift+Tab | Complétion précédente |
|
| Shift+Tab, Down (si complétion commencée) | Complétion précédente |
|
||||||
|
| Left (si complétion commencée) | Complétion à gauche |
|
||||||
|
| Right (si complétion commencée) | Complétion à droite |
|
||||||
|
|
144
completer.go
144
completer.go
|
@ -11,20 +11,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func coordToIndex(x, y, c, n int) int {
|
func coordToIndex(x, y, c, n int) int {
|
||||||
|
if x < 0 || x >= c || y < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
l, r := n/c, n%c
|
l, r := n/c, n%c
|
||||||
switch {
|
switch {
|
||||||
|
case (x < r && y >= l+1) || (x >= r && y >= l):
|
||||||
|
return -1
|
||||||
case r == 0:
|
case r == 0:
|
||||||
return x*l + y
|
return x*l + y
|
||||||
case x < r:
|
case x < r:
|
||||||
return x*(l+1) + y
|
return x*(l+1) + y
|
||||||
case y >= l:
|
|
||||||
return -1
|
|
||||||
default:
|
default:
|
||||||
return r*(l+1) + (x-r)*l + y
|
return r*(l+1) + (x-r)*l + y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexToCoord(i, c, n int) (x, y int) {
|
func indexToCoord(i, c, n int) (x, y int) {
|
||||||
|
if i < 0 || i >= n {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
l, r := n/c, n%c
|
l, r := n/c, n%c
|
||||||
switch {
|
switch {
|
||||||
case r == 0:
|
case r == 0:
|
||||||
|
@ -32,7 +40,7 @@ func indexToCoord(i, c, n int) (x, y int) {
|
||||||
case i < r*(l+1):
|
case i < r*(l+1):
|
||||||
x, y = i/(l+1), i%(l+1)
|
x, y = i/(l+1), i%(l+1)
|
||||||
default:
|
default:
|
||||||
j := i - r*l
|
j := i - r*(l+1)
|
||||||
x, y = r+j/l, j%l
|
x, y = r+j/l, j%l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,50 +152,118 @@ func (c *completer) next() bool { return c.choices.Next() && c.set() }
|
||||||
|
|
||||||
func (c *completer) move(index int) bool { return c.choices.SetCursor(index) && c.set() }
|
func (c *completer) move(index int) bool { return c.choices.SetCursor(index) && c.set() }
|
||||||
|
|
||||||
|
func (c *completer) gridInfo(terminalWidth, terminalHeight int) (nbChoices, colWidth, nbCols, nbLines int, choices []string) {
|
||||||
|
nbChoices = c.choices.Len()
|
||||||
|
if nbChoices == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var wordWidth int
|
||||||
|
choices = make([]string, nbChoices)
|
||||||
|
for i := 0; i < nbChoices; i++ {
|
||||||
|
word, _ := c.choices.Index(i).Get()
|
||||||
|
choices[i], wordWidth = word, max(atom.VisibleWidth(word)+1, wordWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
nbCols = min(max(terminalWidth/wordWidth, 1), 5)
|
||||||
|
nbLines = nbChoices / nbCols
|
||||||
|
if nbChoices%nbCols > 0 {
|
||||||
|
nbLines++
|
||||||
|
}
|
||||||
|
|
||||||
|
colWidth = terminalWidth / nbCols
|
||||||
|
if colWidth > wordWidth {
|
||||||
|
colWidth = (colWidth + wordWidth) >> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completer) infoMove(terminalWidth, terminalHeight int) (nbCols, nbLines, nbChoices, x, y, r int) {
|
||||||
|
nbChoices, _, nbCols, nbLines, _ = c.gridInfo(terminalWidth, terminalHeight)
|
||||||
|
|
||||||
|
if nbChoices > 0 {
|
||||||
|
i := c.choices.Cursor()
|
||||||
|
if i < 0 || i >= nbChoices {
|
||||||
|
x, y = -1, -1
|
||||||
|
} else {
|
||||||
|
x, y = indexToCoord(i, nbCols, nbChoices)
|
||||||
|
r = nbChoices % nbCols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completer) left(terminalWidth, terminalHeight int) bool {
|
||||||
|
nbCols, nbLines, nbChoices, x, y, r := c.infoMove(terminalWidth, terminalHeight)
|
||||||
|
if nbChoices == 0 || x < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case x > 0:
|
||||||
|
x--
|
||||||
|
case y > 0:
|
||||||
|
x, y = nbCols-1, y-1
|
||||||
|
case r > 0:
|
||||||
|
x, y = r-1, nbLines-1
|
||||||
|
default:
|
||||||
|
x, y = nbCols-1, nbLines-1
|
||||||
|
}
|
||||||
|
|
||||||
|
i := coordToIndex(x, y, nbCols, nbChoices)
|
||||||
|
|
||||||
|
return i >= 0 && c.move(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completer) right(terminalWidth, terminalHeight int) bool {
|
||||||
|
nbCols, nbLines, nbChoices, x, y, r := c.infoMove(terminalWidth, terminalHeight)
|
||||||
|
if nbChoices == 0 || x < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fmt.Println("debug0:", x, y)
|
||||||
|
|
||||||
|
if x < r-1 || (x < nbCols-1 && (r == 0 || y < nbLines-1)) {
|
||||||
|
x++
|
||||||
|
} else if y < nbLines-1 {
|
||||||
|
x, y = 0, y+1
|
||||||
|
} else {
|
||||||
|
x, y = 0, 0
|
||||||
|
}
|
||||||
|
fmt.Println("debug1:", x, y)
|
||||||
|
|
||||||
|
i := coordToIndex(x, y, nbCols, nbChoices)
|
||||||
|
|
||||||
|
return i >= 0 && c.move(i)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *completer) formatChoices(currentWord string, terminalWidth, terminalHeight int) string {
|
func (c *completer) formatChoices(currentWord string, terminalWidth, terminalHeight int) string {
|
||||||
var buf strings.Builder
|
nbChoices, colWidth, nbCols, nbLines, choices := c.gridInfo(terminalWidth, terminalHeight)
|
||||||
l, cursor := c.choices.Len(), c.choices.Cursor()
|
if nbChoices == 0 {
|
||||||
if l == 0 {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxLen int
|
cursor := c.choices.Cursor()
|
||||||
choices := make([]string, l)
|
lineMin, lineMax := 0, nbLines-1
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
word, _ := c.choices.Index(i).Get()
|
|
||||||
maxLen, choices[i] = max(atom.VisibleWidth(word)+1, maxLen), word
|
|
||||||
}
|
|
||||||
|
|
||||||
maxCol := min(max(terminalWidth/maxLen, 1), 5)
|
|
||||||
lines := l / maxCol
|
|
||||||
if l%maxCol > 0 {
|
|
||||||
lines++
|
|
||||||
}
|
|
||||||
lineMin, lineMax := 0, lines-1
|
|
||||||
var isPartial bool
|
var isPartial bool
|
||||||
if isPartial = terminalHeight < lines; isPartial {
|
if isPartial = terminalHeight < nbLines; isPartial {
|
||||||
_, cursorLine := indexToCoord(cursor, maxCol, l)
|
_, cursorLine := indexToCoord(cursor, nbCols, nbChoices)
|
||||||
if cursorLine < terminalHeight {
|
lineMin = max(0, cursorLine-terminalHeight+1)
|
||||||
lineMax = min(terminalHeight, lines) - 1
|
lineMax = lineMin + terminalHeight - 1
|
||||||
} else {
|
|
||||||
lineMin, lineMax = cursorLine+1-min(terminalHeight, lines), cursorLine
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
colWith := terminalWidth / maxCol
|
var buf strings.Builder
|
||||||
if colWith > maxLen {
|
|
||||||
colWith = (colWith + maxLen) >> 1
|
|
||||||
}
|
|
||||||
for y := lineMin; y <= lineMax; y++ {
|
for y := lineMin; y <= lineMax; y++ {
|
||||||
if y > 0 {
|
if y > 0 {
|
||||||
buf.WriteRune('\n')
|
buf.WriteRune('\n')
|
||||||
}
|
}
|
||||||
for x := 0; x < maxCol; x++ {
|
for x := 0; x < nbCols; x++ {
|
||||||
i := coordToIndex(x, y, maxCol, l)
|
i := coordToIndex(x, y, nbCols, nbChoices)
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
word := choices[i]
|
word := choices[i]
|
||||||
realSize, displaySize := len([]rune(word)), atom.VisibleWidth(word)
|
realSize, displaySize := len([]rune(word)), atom.VisibleWidth(word)
|
||||||
size := maxLen + realSize - displaySize
|
size := colWidth + realSize - displaySize
|
||||||
word = strings.TrimPrefix(format.Left(word, size), currentWord)
|
word = strings.TrimPrefix(format.Left(word, size), currentWord)
|
||||||
if i == cursor {
|
if i == cursor {
|
||||||
buf.WriteString(format.Apply(currentWord, "bg_l_white", "black", "bold", "underline"))
|
buf.WriteString(format.Apply(currentWord, "bg_l_white", "black", "bold", "underline"))
|
||||||
|
@ -201,7 +277,7 @@ func (c *completer) formatChoices(currentWord string, terminalWidth, terminalHei
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPartial {
|
if isPartial {
|
||||||
position := fmt.Sprintf("lignes %d à %d (%d)", lineMin+1, lineMax+1, lines)
|
position := fmt.Sprintf("lignes %d à %d (%d)", lineMin+1, lineMax+1, nbLines)
|
||||||
buf.WriteRune('\n')
|
buf.WriteRune('\n')
|
||||||
buf.WriteString(format.Apply(position, "bg_l_cyan", "black"))
|
buf.WriteString(format.Apply(position, "bg_l_cyan", "black"))
|
||||||
}
|
}
|
||||||
|
|
72
fishline.go
72
fishline.go
|
@ -113,14 +113,45 @@ func (fl *Fishline) completionNext() (ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fl *Fishline) fixState() bool {
|
func (fl *Fishline) getCompletionInfos(buffer ...string) (w, h int) {
|
||||||
ok1 := fl.st.RemoveSavedBuffer()
|
var str string
|
||||||
ok2 := fl.st.RemoveHistorySearch()
|
if len(buffer) > 0 {
|
||||||
ok3 := fl.st.UnfocusHistory()
|
str = buffer[0]
|
||||||
ok4 := fl.st.UnfocusYank()
|
} else {
|
||||||
ok5 := fl.removeCompletion()
|
str, _ = fl.st.Buffer()
|
||||||
|
}
|
||||||
|
|
||||||
return ok1 || ok2 || ok3 || ok4 || ok5
|
w, h = fl.st.Width(), fl.st.Height()-1
|
||||||
|
hb, _, _ := atom.CursorOffset(fl.st.GetPrompt()+str, 0, 0)
|
||||||
|
|
||||||
|
return w, h - hb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *Fishline) completionLeft() (ok bool) {
|
||||||
|
completion := fl.initCompletion()
|
||||||
|
|
||||||
|
if ok = completion.left(fl.getCompletionInfos()); ok {
|
||||||
|
fl.setCompletion(completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *Fishline) completionRight() (ok bool) {
|
||||||
|
completion := fl.initCompletion()
|
||||||
|
|
||||||
|
if ok = completion.right(fl.getCompletionInfos()); ok {
|
||||||
|
fl.setCompletion(completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *Fishline) fixState() bool {
|
||||||
|
ok1 := fl.st.FixState()
|
||||||
|
ok2 := fl.removeCompletion()
|
||||||
|
|
||||||
|
return ok1 || ok2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fl *Fishline) cancel() (ok bool) {
|
func (fl *Fishline) cancel() (ok bool) {
|
||||||
|
@ -286,7 +317,9 @@ func (fl *Fishline) execSequence(s atom.Sequence, isPassword bool) (fix, stop bo
|
||||||
if !isPassword {
|
if !isPassword {
|
||||||
l := fl.st.BufferLen()
|
l := fl.st.BufferLen()
|
||||||
changeProposal = -1
|
changeProposal = -1
|
||||||
if l == 0 || fl.st.IsHistoryMode() {
|
if fl.isCompletionMode() {
|
||||||
|
noBeep = fl.completionPrev()
|
||||||
|
} else if l == 0 || fl.st.IsHistoryMode() {
|
||||||
noBeep = fl.historyPrev()
|
noBeep = fl.historyPrev()
|
||||||
} else {
|
} else {
|
||||||
noBeep = fl.searchHistoryPrev()
|
noBeep = fl.searchHistoryPrev()
|
||||||
|
@ -296,20 +329,30 @@ func (fl *Fishline) execSequence(s atom.Sequence, isPassword bool) (fix, stop bo
|
||||||
if !isPassword {
|
if !isPassword {
|
||||||
l := fl.st.BufferLen()
|
l := fl.st.BufferLen()
|
||||||
changeProposal = -1
|
changeProposal = -1
|
||||||
if l == 0 || fl.st.IsHistoryMode() {
|
if fl.isCompletionMode() {
|
||||||
|
noBeep = fl.completionNext()
|
||||||
|
} else if l == 0 || fl.st.IsHistoryMode() {
|
||||||
noBeep = fl.historyNext()
|
noBeep = fl.historyNext()
|
||||||
} else {
|
} else {
|
||||||
noBeep = fl.searchHistoryNext()
|
noBeep = fl.searchHistoryNext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case atom.Right: // Move right
|
case atom.Right: // Move right
|
||||||
|
if fl.isCompletionMode() && !isPassword {
|
||||||
|
changeProposal, noBeep = -1, fl.completionRight()
|
||||||
|
} else {
|
||||||
noBeep, fix = fl.st.Right(), true
|
noBeep, fix = fl.st.Right(), true
|
||||||
if !noBeep && fl.proposal != "" {
|
if !noBeep && fl.proposal != "" {
|
||||||
noBeep, changeProposal = true, -1
|
noBeep, changeProposal = true, -1
|
||||||
fl.st.SetBuffer(fl.proposal)
|
fl.st.SetBuffer(fl.proposal)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case atom.Left: // Move left
|
case atom.Left: // Move left
|
||||||
|
if fl.isCompletionMode() && !isPassword {
|
||||||
|
changeProposal, noBeep = -1, fl.completionLeft()
|
||||||
|
} else {
|
||||||
noBeep, fix = fl.st.Left(), true
|
noBeep, fix = fl.st.Left(), true
|
||||||
|
}
|
||||||
case atom.Ins: // Toggle insert
|
case atom.Ins: // Toggle insert
|
||||||
fl.st.ToggleReplace()
|
fl.st.ToggleReplace()
|
||||||
noBeep = true
|
noBeep = true
|
||||||
|
@ -387,16 +430,11 @@ func (fl *Fishline) prompt(p string, isPassword bool) (result option.Result[stri
|
||||||
output.WriteString(format.Apply(suffix, "l_black"))
|
output.WriteString(format.Apply(suffix, "l_black"))
|
||||||
}
|
}
|
||||||
if c, ok := fl.completion.Get(); ok {
|
if c, ok := fl.completion.Get(); ok {
|
||||||
w, h := fl.st.Width(), fl.st.Height()-1
|
w, h := fl.getCompletionInfos(buffer + suffix)
|
||||||
wb := atom.VisibleWidth(fl.st.GetPrompt() + buffer + suffix)
|
choices := c.formatChoices(fl.currentWord, w, h)
|
||||||
hb := wb / w
|
|
||||||
if wb%w > 0 {
|
|
||||||
hb++
|
|
||||||
}
|
|
||||||
choices := c.formatChoices(fl.currentWord, w, h-hb)
|
|
||||||
if choices != "" {
|
if choices != "" {
|
||||||
output.WriteString("\n")
|
output.WriteString("\n")
|
||||||
output.WriteString(c.formatChoices(fl.currentWord, w, h-hb))
|
output.WriteString(choices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue