greadline/readline.go

218 lines
4.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package greadline
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 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() }