greadline/readline.go

218 lines
4.7 KiB
Go
Raw Permalink Normal View History

package greadline
2023-11-04 11:43:30 +00:00
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 {
2023-11-04 11:43:30 +00:00
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 naffiche 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() }