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
					
				
					 4 changed files with 194 additions and 78 deletions
				
			
		|  | @ -52,7 +52,7 @@ func main() { | |||
| ## Raccourcis clavier <u>disponibles</u> | ||||
| 
 | ||||
| | Raccourci clavier                         | Action                                                                                             | | ||||
| | -------------------------------------- | ------------------------------------------------------------ | | ||||
| | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | | ||||
| | Ctrl+A, Home                              | Retour au début de la saisie                                                                       | | ||||
| | Ctrl+E, End                               | Retour à la fin de la saisie                                                                       | | ||||
| | 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é                                                                  | | ||||
| | 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)       | | ||||
| | Tab                                    | Complétion suivante                                          | | ||||
| | Shift+Tab                              | Complétion précédente                                        | | ||||
| | Tab, Up (si complétion commencée)         | Complétion suivante                                                                                | | ||||
| | 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 { | ||||
| 	if x < 0 || x >= c || y < 0 { | ||||
| 		return -1 | ||||
| 	} | ||||
| 
 | ||||
| 	l, r := n/c, n%c | ||||
| 	switch { | ||||
| 	case (x < r && y >= l+1) || (x >= r && y >= l): | ||||
| 		return -1 | ||||
| 	case r == 0: | ||||
| 		return x*l + y | ||||
| 	case x < r: | ||||
| 		return x*(l+1) + y | ||||
| 	case y >= l: | ||||
| 		return -1 | ||||
| 	default: | ||||
| 		return r*(l+1) + (x-r)*l + y | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func indexToCoord(i, c, n int) (x, y int) { | ||||
| 	if i < 0 || i >= n { | ||||
| 		return -1, -1 | ||||
| 	} | ||||
| 
 | ||||
| 	l, r := n/c, n%c | ||||
| 	switch { | ||||
| 	case r == 0: | ||||
|  | @ -32,7 +40,7 @@ func indexToCoord(i, c, n int) (x, y int) { | |||
| 	case i < r*(l+1): | ||||
| 		x, y = i/(l+1), i%(l+1) | ||||
| 	default: | ||||
| 		j := i - r*l | ||||
| 		j := i - r*(l+1) | ||||
| 		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) 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 { | ||||
| 	var buf strings.Builder | ||||
| 	l, cursor := c.choices.Len(), c.choices.Cursor() | ||||
| 	if l == 0 { | ||||
| 	nbChoices, colWidth, nbCols, nbLines, choices := c.gridInfo(terminalWidth, terminalHeight) | ||||
| 	if nbChoices == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 
 | ||||
| 	var maxLen int | ||||
| 	choices := make([]string, l) | ||||
| 	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 | ||||
| 	cursor := c.choices.Cursor() | ||||
| 	lineMin, lineMax := 0, nbLines-1 | ||||
| 	var isPartial bool | ||||
| 	if isPartial = terminalHeight < lines; isPartial { | ||||
| 		_, cursorLine := indexToCoord(cursor, maxCol, l) | ||||
| 		if cursorLine < terminalHeight { | ||||
| 			lineMax = min(terminalHeight, lines) - 1 | ||||
| 		} else { | ||||
| 			lineMin, lineMax = cursorLine+1-min(terminalHeight, lines), cursorLine | ||||
| 		} | ||||
| 	if isPartial = terminalHeight < nbLines; isPartial { | ||||
| 		_, cursorLine := indexToCoord(cursor, nbCols, nbChoices) | ||||
| 		lineMin = max(0, cursorLine-terminalHeight+1) | ||||
| 		lineMax = lineMin + terminalHeight - 1 | ||||
| 	} | ||||
| 
 | ||||
| 	colWith := terminalWidth / maxCol | ||||
| 	if colWith > maxLen { | ||||
| 		colWith = (colWith + maxLen) >> 1 | ||||
| 	} | ||||
| 	var buf strings.Builder | ||||
| 	for y := lineMin; y <= lineMax; y++ { | ||||
| 		if y > 0 { | ||||
| 			buf.WriteRune('\n') | ||||
| 		} | ||||
| 		for x := 0; x < maxCol; x++ { | ||||
| 			i := coordToIndex(x, y, maxCol, l) | ||||
| 		for x := 0; x < nbCols; x++ { | ||||
| 			i := coordToIndex(x, y, nbCols, nbChoices) | ||||
| 			if i >= 0 { | ||||
| 				word := choices[i] | ||||
| 				realSize, displaySize := len([]rune(word)), atom.VisibleWidth(word) | ||||
| 				size := maxLen + realSize - displaySize | ||||
| 				size := colWidth + realSize - displaySize | ||||
| 				word = strings.TrimPrefix(format.Left(word, size), currentWord) | ||||
| 				if i == cursor { | ||||
| 					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 { | ||||
| 		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.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 | ||||
| } | ||||
| 
 | ||||
| func (fl *Fishline) fixState() bool { | ||||
| 	ok1 := fl.st.RemoveSavedBuffer() | ||||
| 	ok2 := fl.st.RemoveHistorySearch() | ||||
| 	ok3 := fl.st.UnfocusHistory() | ||||
| 	ok4 := fl.st.UnfocusYank() | ||||
| 	ok5 := fl.removeCompletion() | ||||
| func (fl *Fishline) getCompletionInfos(buffer ...string) (w, h int) { | ||||
| 	var str string | ||||
| 	if len(buffer) > 0 { | ||||
| 		str = buffer[0] | ||||
| 	} else { | ||||
| 		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) { | ||||
|  | @ -286,7 +317,9 @@ func (fl *Fishline) execSequence(s atom.Sequence, isPassword bool) (fix, stop bo | |||
| 		if !isPassword { | ||||
| 			l := fl.st.BufferLen() | ||||
| 			changeProposal = -1 | ||||
| 			if l == 0 || fl.st.IsHistoryMode() { | ||||
| 			if fl.isCompletionMode() { | ||||
| 				noBeep = fl.completionPrev() | ||||
| 			} else if l == 0 || fl.st.IsHistoryMode() { | ||||
| 				noBeep = fl.historyPrev() | ||||
| 			} else { | ||||
| 				noBeep = fl.searchHistoryPrev() | ||||
|  | @ -296,20 +329,30 @@ func (fl *Fishline) execSequence(s atom.Sequence, isPassword bool) (fix, stop bo | |||
| 		if !isPassword { | ||||
| 			l := fl.st.BufferLen() | ||||
| 			changeProposal = -1 | ||||
| 			if l == 0 || fl.st.IsHistoryMode() { | ||||
| 			if fl.isCompletionMode() { | ||||
| 				noBeep = fl.completionNext() | ||||
| 			} else if l == 0 || fl.st.IsHistoryMode() { | ||||
| 				noBeep = fl.historyNext() | ||||
| 			} else { | ||||
| 				noBeep = fl.searchHistoryNext() | ||||
| 			} | ||||
| 		} | ||||
| 	case atom.Right: // Move right | ||||
| 		if fl.isCompletionMode() && !isPassword { | ||||
| 			changeProposal, noBeep = -1, fl.completionRight() | ||||
| 		} else { | ||||
| 			noBeep, fix = fl.st.Right(), true | ||||
| 			if !noBeep && fl.proposal != "" { | ||||
| 				noBeep, changeProposal = true, -1 | ||||
| 				fl.st.SetBuffer(fl.proposal) | ||||
| 			} | ||||
| 		} | ||||
| 	case atom.Left: // Move left | ||||
| 		if fl.isCompletionMode() && !isPassword { | ||||
| 			changeProposal, noBeep = -1, fl.completionLeft() | ||||
| 		} else { | ||||
| 			noBeep, fix = fl.st.Left(), true | ||||
| 		} | ||||
| 	case atom.Ins: // Toggle insert | ||||
| 		fl.st.ToggleReplace() | ||||
| 		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")) | ||||
| 				} | ||||
| 				if c, ok := fl.completion.Get(); ok { | ||||
| 					w, h := fl.st.Width(), fl.st.Height()-1 | ||||
| 					wb := atom.VisibleWidth(fl.st.GetPrompt() + buffer + suffix) | ||||
| 					hb := wb / w | ||||
| 					if wb%w > 0 { | ||||
| 						hb++ | ||||
| 					} | ||||
| 					choices := c.formatChoices(fl.currentWord, w, h-hb) | ||||
| 					w, h := fl.getCompletionInfos(buffer + suffix) | ||||
| 					choices := c.formatChoices(fl.currentWord, w, h) | ||||
| 					if choices != "" { | ||||
| 						output.WriteString("\n") | ||||
| 						output.WriteString(c.formatChoices(fl.currentWord, w, h-hb)) | ||||
| 						output.WriteString(choices) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -1,4 +1,4 @@ | |||
| module gitea.zaclys.com/bvaudour/fishline | ||||
| module gitea.zaclys.com/bvaudour/gfishline | ||||
| 
 | ||||
| go 1.22.0 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Benjamin VAUDOUR
						Benjamin VAUDOUR