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