Premier commit
This commit is contained in:
parent
9ef818bd6c
commit
6334405a48
63
README.md
63
README.md
|
@ -1,3 +1,66 @@
|
||||||
# readline
|
# readline
|
||||||
|
|
||||||
Readline est un prompt Linux qui se veut similaire à C readline mais en ligne de commande.
|
Readline est un prompt Linux qui se veut similaire à C readline mais en ligne de commande.
|
||||||
|
|
||||||
|
## Utilisation typique
|
||||||
|
|
||||||
|
readline peut être utilisé de la façon suivante :
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"gitea.zaclys.net/bvaudour/readline"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
prompt := "\033[1;31m> \033[m"
|
||||||
|
rl := readline.New()
|
||||||
|
defer readline.Close()
|
||||||
|
for {
|
||||||
|
input := rl.Prompt(prompt) // rl.PromptPassword(prompt) pour masquer ce qui est saisi
|
||||||
|
if result, ok := input.Ok(); ok {
|
||||||
|
//@TODO Do some stuff with input
|
||||||
|
if result == "exit" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if err, ok := input.Err(); ok {
|
||||||
|
fmt.Printf("\033[1,31mErreur système : %s\033[m\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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 |
|
||||||
|
| Ctrl+F, Right | Déplacement d’un caractère vers la droite |
|
||||||
|
| Alt+B, Ctrl+Left | Déplacement d’un mot vers la gauche |
|
||||||
|
| Alt+F, Ctrl+Right | Déplacement d’un mot vers la droite |
|
||||||
|
| Ctrl+U | Suppression de tous les caractères du début de la ligne au curseur (curseur non compris) |
|
||||||
|
| Ctrl+K | Suppression de tous les carctères du curseur (compris) à la fin de la ligne |
|
||||||
|
| Ctrl+D (si saisie commencée), Del | Suppression du caractère sous le curseur |
|
||||||
|
| Ctrl+D (si ligne vide) | Termine l’application (EOF) |
|
||||||
|
| Ctrl+H, Backspace | Suppression du caractère avant le curseur |
|
||||||
|
| Alt+D, Alt+Del | Suppression du prochain mot |
|
||||||
|
| Alt+Backspace | Suppression du précédent mot |
|
||||||
|
| Ctrl+C | Termine l’application (avec erreur) |
|
||||||
|
| Ctrl+P, Up (si ligne vide au départ) | Remonte à l’historique précédent |
|
||||||
|
| Ctrl+N, Down (si ligne vide au départ) | Descend dans l’historique |
|
||||||
|
| Ctrl+R, Up (si ligne non vide) | Recherche dans l’historique (par préfixe de ligne) à partir de la fin |
|
||||||
|
| Ctrl+S, Down (si ligne non vide) | Recherche dans l’historique (par préfixe de ligne) à partir du début |
|
||||||
|
| 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) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||||
|
"gitea.zaclys.com/bvaudour/gob/shell/console/atom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Readline struct {
|
||||||
|
st *atom.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Readline {
|
||||||
|
return &Readline{
|
||||||
|
st: atom.NewState(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *Readline) execChar(r atom.Char, isPassword bool) (fix, stop bool) {
|
||||||
|
if stop = r == '\n' || r == '\r'; stop {
|
||||||
|
fix = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if atom.IsPrintable(r) {
|
||||||
|
rl.st.Insert(r)
|
||||||
|
fix = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var noBeep bool
|
||||||
|
switch r {
|
||||||
|
case atom.Bs, atom.C_H: // Back
|
||||||
|
noBeep, fix = rl.st.Back(), true
|
||||||
|
case atom.C_W: // Back word
|
||||||
|
noBeep, fix = rl.st.BackWord(), true
|
||||||
|
case atom.C_D: // Del or reset line
|
||||||
|
l := rl.st.BufferLen()
|
||||||
|
if l == 0 {
|
||||||
|
rl.st.Return()
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
noBeep, fix = rl.st.Del(), true
|
||||||
|
rl.st.Restart()
|
||||||
|
}
|
||||||
|
case atom.C_U: // Remove BOL
|
||||||
|
noBeep, fix = rl.st.RemoveBegin(), true
|
||||||
|
case atom.C_K: // Remove EOL
|
||||||
|
noBeep, fix = rl.st.RemoveEnd(), true
|
||||||
|
case atom.C_A: // Move to BOL
|
||||||
|
noBeep, fix = rl.st.Begin(), true
|
||||||
|
case atom.C_E: // Move to EOL
|
||||||
|
noBeep, fix = rl.st.End(), true
|
||||||
|
case atom.C_B: // Move left
|
||||||
|
noBeep, fix = rl.st.Left(), true
|
||||||
|
case atom.C_F: // Move right
|
||||||
|
noBeep, fix = rl.st.Right(), true
|
||||||
|
case atom.C_T: // Transpose
|
||||||
|
noBeep, fix = rl.st.Transpose(), true
|
||||||
|
case atom.C_P: // Previous history
|
||||||
|
if !isPassword {
|
||||||
|
noBeep = rl.st.HistoryPrev()
|
||||||
|
}
|
||||||
|
case atom.C_N: // Next history
|
||||||
|
if !isPassword {
|
||||||
|
noBeep = rl.st.HistoryNext()
|
||||||
|
}
|
||||||
|
case atom.C_S: // Search history
|
||||||
|
if !isPassword {
|
||||||
|
noBeep = rl.st.SearchHistoryNext()
|
||||||
|
}
|
||||||
|
case atom.C_R: // Reverse search history
|
||||||
|
if !isPassword {
|
||||||
|
noBeep = rl.st.SearchHistoryPrev()
|
||||||
|
}
|
||||||
|
case atom.C_G, atom.Esc: // Cancel history
|
||||||
|
noBeep, fix = rl.st.Cancel(), true
|
||||||
|
case atom.C_Y: // Yank
|
||||||
|
noBeep = rl.st.Yank()
|
||||||
|
case atom.C_C: // Emergency stop
|
||||||
|
rl.st.Return()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !noBeep {
|
||||||
|
rl.st.Beep()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *Readline) execSequence(s atom.Sequence, isPassword bool) (fix, stop bool) {
|
||||||
|
var noBeep bool
|
||||||
|
switch s {
|
||||||
|
case atom.Up: // Previous history
|
||||||
|
if !isPassword {
|
||||||
|
l := rl.st.BufferLen()
|
||||||
|
if l == 0 || rl.st.IsHistoryMode() {
|
||||||
|
noBeep = rl.st.HistoryPrev()
|
||||||
|
} else {
|
||||||
|
noBeep = rl.st.SearchHistoryPrev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case atom.Down: // Next history
|
||||||
|
if !isPassword {
|
||||||
|
l := rl.st.BufferLen()
|
||||||
|
if l == 0 || rl.st.IsHistoryMode() {
|
||||||
|
noBeep = rl.st.HistoryNext()
|
||||||
|
} else {
|
||||||
|
noBeep = rl.st.SearchHistoryNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case atom.Right: // Move right
|
||||||
|
noBeep, fix = rl.st.Right(), true
|
||||||
|
case atom.Left: // Move left
|
||||||
|
noBeep, fix = rl.st.Left(), true
|
||||||
|
case atom.Ins: // Toggle insert
|
||||||
|
rl.st.ToggleReplace()
|
||||||
|
noBeep = true
|
||||||
|
case atom.Del: // Delete under cursor
|
||||||
|
noBeep, fix = rl.st.Del(), true
|
||||||
|
case atom.Home: // Move BOL
|
||||||
|
noBeep, fix = rl.st.Begin(), true
|
||||||
|
case atom.End: // Move EOL
|
||||||
|
noBeep, fix = rl.st.End(), true
|
||||||
|
case atom.A_Bs: // Delete begin of word
|
||||||
|
noBeep, fix = rl.st.BackWord(), true
|
||||||
|
case atom.A_D, atom.A_Del: // Delete end of word
|
||||||
|
noBeep, fix = rl.st.DelWord(), true
|
||||||
|
case atom.C_Right, atom.A_F: // Next word
|
||||||
|
noBeep, fix = rl.st.NextWord(), true
|
||||||
|
case atom.C_Left, atom.A_B: // Begin of word
|
||||||
|
noBeep, fix = rl.st.PrevWord(), true
|
||||||
|
case atom.A_Y: // Previous yank
|
||||||
|
noBeep = rl.st.YankPrev()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !noBeep {
|
||||||
|
rl.st.Beep()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *Readline) prompt(p string, isPassword bool) (result Result[string]) {
|
||||||
|
if err, ok := rl.st.SetPrompt(p).Get(); ok {
|
||||||
|
return Err[string](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.st.Start()
|
||||||
|
defer rl.st.Stop()
|
||||||
|
|
||||||
|
var refresh func() Option[error]
|
||||||
|
if isPassword {
|
||||||
|
refresh = func() Option[error] { return rl.st.Print("", 0) }
|
||||||
|
} else {
|
||||||
|
refresh = func() Option[error] {
|
||||||
|
str, cursor := rl.st.Buffer()
|
||||||
|
return rl.st.Print(str, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
var fix, stop, isErr bool
|
||||||
|
for {
|
||||||
|
n := rl.st.Next()
|
||||||
|
if err, ok := n.Err(); ok {
|
||||||
|
result, isErr = Err[string](err), true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := n.Ok()
|
||||||
|
if s, ok := key.Sequence(); ok {
|
||||||
|
fix, stop = rl.execSequence(s, isPassword)
|
||||||
|
} else if c, ok := key.Char(); ok {
|
||||||
|
fix, stop = rl.execChar(c, isPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := refresh()
|
||||||
|
if e, ok := err.Get(); ok {
|
||||||
|
result, isErr = Err[string](e), true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if fix {
|
||||||
|
rl.st.FixState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isErr {
|
||||||
|
r, _ := rl.st.Buffer()
|
||||||
|
result = Ok(r)
|
||||||
|
if !isPassword && len(r) > 0 {
|
||||||
|
rl.st.AppendHistory(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.st.Clear()
|
||||||
|
rl.st.FixState()
|
||||||
|
rl.st.Return()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt retourne la chaîne saisie.
|
||||||
|
func (rl *Readline) Prompt(p string) Result[string] { return rl.prompt(p, false) }
|
||||||
|
|
||||||
|
// PromptPassword agit comme Prompt mais n’affiche pas à l’écran ce qui est saisi.
|
||||||
|
func (rl *Readline) PromptPassword(p string) Result[string] { return rl.prompt(p, true) }
|
||||||
|
|
||||||
|
// Close ferme proprement le Readline.
|
||||||
|
func (rl *Readline) Close() Option[error] { return rl.st.Close() }
|
Loading…
Reference in New Issue