gob/shell/console/atom/state.go

653 lines
14 KiB
Go
Raw Normal View History

2023-10-07 19:13:39 +00:00
package atom
import (
"fmt"
"io"
"os"
"gitea.zaclys.com/bvaudour/gob/option"
"gitea.zaclys.com/bvaudour/gob/shell/console"
)
// State représente létat dun terminal.
type State struct {
origTerm termios
defaultTerm termios
in *input
buffer Buffer
history console.History
historySearch option.Option[console.History]
yank console.History
savedBuffer option.Option[*Buffer]
dim TerminalSize
prompt string
replace bool
}
// NewState retourne un nouvel état de terminal.
func NewState(hist ...console.History) *State {
var h console.History
if len(hist) > 0 {
h = hist[0]
} else {
h = NewHistory(false)
}
st := State{
in: newInput(os.Stdin),
history: h,
yank: NewHistory(false),
}
if !terminalSupported() {
panic("Unsupported terminal")
}
m := newTermios(stdin)
if err, ok := m.Err(); ok {
panic(fmt.Sprintf("Unsupported terminal: %s", err))
}
if t, ok := m.Ok(); ok {
st.origTerm = *t
}
if err, ok := newTermios(stdout).Err(); ok {
panic(fmt.Sprintf("Unsupported terminal: %s", err))
}
st.origTerm.Iflag &^= icrnl | inpck | istrip | ixon
st.origTerm.Cflag |= cs8
st.origTerm.Lflag &^= echo | icanon | iexten
st.origTerm.Cc[vmin] = 1
st.origTerm.Cc[vtime] = 0
st.origTerm.applyMode()
var ok bool
ts := GetTerminalSize()
if st.dim, ok = ts.Get(); !ok {
panic("Unsupported terminal: unknown dimensions")
}
return &st
}
// Start lance linitialisation du terminal.
func (st *State) Start() {
m, _ := newTermios(stdin).Ok()
st.defaultTerm = *m
st.defaultTerm.Lflag &^= isig
st.defaultTerm.applyMode()
st.Restart()
}
// Restart redémarre la possibilité de saisir des caractères.
func (st *State) Restart() {
st.in.restart()
}
// Stop retourne à létat originel du terminal.
func (st *State) Stop() {
st.defaultTerm.applyMode()
}
// Next retourne la prochaine saisie de touche ou de combinaison de touches.
func (st *State) Next() option.Result[Key] {
return st.in.nextChar()
}
// Close retourne à létat originel du terminal.
func (st *State) Close() option.Option[error] {
return st.origTerm.applyMode()
}
// InputWaiting retourne vrai si une saisie est en attente de traitement.
func (st *State) InputWaiting() bool {
return st.in.isWaiting()
}
// SetPrompt enregistre le prompt à utiiser.
// Si le prompt contient des caractères non imprimables, retourne une erreur.
func (st *State) SetPrompt(p string) (err option.Option[error]) {
if err := CheckUnicode(p); err.IsDefined() {
return err
}
st.prompt = p
SaveCursorPosition()
return
}
// Print imprime le prompt suivie de la chaîne spécifié et place
// le curseur à la position indiquée (commence à 0 à partir du début de la chaîne).
func (st *State) Print(str string, cursor int) (err option.Option[error]) {
if err = CheckUnicode(str); err.IsDefined() {
return
}
st.dim, _ = GetTerminalSize().Get()
str = st.prompt + str
px, py, n := CursorOffset(str, cursor+VisibleWidth(st.prompt), st.dim.Width())
RestoreCursorPosition()
ClearEndOfScreen()
2023-10-07 19:13:39 +00:00
if n > 0 {
NewLines(n)
MoveLineUp(n)
SaveCursorPosition()
}
fmt.Print(str)
RestoreCursorPosition()
if py > 0 {
MoveDown(py)
}
if px > 0 {
MoveRight(px)
}
return
}
// Return ajoute une nouvelle ligne.
func (st *State) Return() {
NewLines(1)
}
// Beep émet un bip.
func (st *State) Beep() {
Beep()
}
// SaveBuffer enregistre le buffer actuel.
// Si force, force lenregistrement du buffer même sil y a une sauvegarde existante.
func (st *State) SaveBuffer(force ...bool) (ok bool) {
f := false
if len(force) > 0 {
f = force[0]
}
if ok = f || !st.savedBuffer.IsDefined(); ok {
st.savedBuffer = option.Some(st.buffer.Clone())
}
return
}
// RestoreBuffer restaure le buffer à partir de sa sauvegarde précédente.
func (st *State) RestoreBuffer() (ok bool) {
var sb *Buffer
if sb, ok = st.savedBuffer.Get(); ok {
b := sb.Clone()
st.buffer = *b
}
return
}
// RemoveSavedBuffer supprime le buffer de sauvegarde.
func (st *State) RemoveSavedBuffer() (ok bool) {
if ok = st.savedBuffer.IsDefined(); ok {
st.savedBuffer = option.None[*Buffer]()
}
return
}
// RemoveHistorySearch supprime la recherche dhistorique.
func (st *State) RemoveHistorySearch() (ok bool) {
if ok = st.historySearch.IsDefined(); ok {
st.historySearch = option.None[console.History]()
}
return
}
// UnfocusHistory supprime le pointage vers un élément de lhistorique.
func (st *State) UnfocusHistory() (ok bool) {
if ok = console.IsFocused(st.history); ok {
console.Unfocus(st.history)
}
return
}
// UnfocusYank supprime le pointage vers un élément copiable.
func (st *State) UnfocusYank() (ok bool) {
if ok = console.IsFocused(st.yank); ok {
console.Unfocus(st.yank)
}
return
}
// FixState sassure que le buffer est désormais fixé.
func (st *State) FixState() (ok bool) {
ok1 := st.RemoveSavedBuffer()
ok2 := st.RemoveHistorySearch()
ok3 := st.UnfocusHistory()
ok4 := st.UnfocusYank()
return ok1 || ok2 || ok3 || ok4
}
// IsHistoryMode retourne vrai si on est en mode parcours de lhistorique.
func (st *State) IsHistoryMode() bool {
return console.IsFocused(st.history)
}
// IsYankMode retourne vrai si on est en mode collage.
func (st *State) IsYankMode() bool {
return console.IsFocused(st.yank)
}
// IsHistorySearchMode retourne vrai si on est en mode recherche dans lhistorique.
func (st *State) IsHistorySearchMode() bool {
return st.historySearch.IsDefined()
}
// IsTmpMode retourne vrai si le buffer peut être restauré à un état précédent.
func (st *State) IsTmpMode() bool {
return st.savedBuffer.IsDefined()
}
// IsReplaceMode retourne vrai si on est en mode remplacement.
func (st *State) IsReplaceMode() (ok bool) {
return st.replace
}
// LoadHistory charge lhistorique à partir dun fichier.
func (st *State) LoadHistory(r io.Reader) option.Result[int] {
return st.history.Read(r)
}
// SaveHistory persiste lhistorique dans un fichier.
func (st *State) SaveHistory(w io.Writer) option.Result[int] {
return st.history.Write(w)
}
// ClearHistory efface lhistorique.
func (st *State) ClearHistory() {
st.history.Clear()
}
// AppendHistory ajoute une entrée dans lhistorique.
func (st *State) AppendHistory(line string) {
st.history.Append(line)
}
// AppendYank ajoute une entrée dans la liste des éléments copiables.
func (st *State) AppendYank(yank string) {
st.yank.Append(yank)
}
// Buffer retourne la représentation du buffer et la position du curseur dans le buffer.
func (st *State) Buffer() (string, int) {
return st.buffer.String(), st.buffer.Cursor()
}
// BufferLen retourne le nombre de caractères du buffer.
func (st *State) BufferLen() int {
return st.buffer.Len()
}
// SavedBuffer retouren la représentation du buffer sauvegardé, si celui-ci existe.
func (st *State) SavedBuffer() (out option.Option[string]) {
if buf, ok := st.savedBuffer.Get(); ok {
out = option.Some(buf.String())
}
return
}
2023-10-07 19:13:39 +00:00
// Width retourne la largeur du terminal.
func (st *State) Width() int {
return st.dim.Width()
}
// Height retourn la hauteur du terminal.
func (st *State) Height() int {
return st.dim.Height()
}
2023-10-07 19:13:39 +00:00
// ToggleReplace se place en mode remplacement si on était on mode insertion
// et vice-versa.
func (st *State) ToggleReplace() {
st.replace = !st.replace
}
// Insert insert (ou remplace, suivant le mode actuel) les caractères donnés
// dans le buffer.
func (st *State) Insert(data ...Char) (ok bool) {
if ok = len(data) > 0; ok {
if st.replace {
st.buffer.Replace(data...)
} else {
st.buffer.Insert(data...)
}
}
return
}
// Transpose transpose le caractère sous le curseur avec le caractère précédent.
func (st *State) Transpose() bool {
return st.buffer.Transpose()
}
// Left déplace le curseur vers la gauche.
func (st *State) Left() bool {
return st.buffer.Left()
}
// Right déplace le curseur vers la droite.
func (st *State) Right() bool {
return st.buffer.Right()
}
// Begin déplace le curseur au début du buffer.
func (st *State) Begin() bool {
return st.buffer.Begin()
}
// End déplace le curseur à la fin du buffer.
func (st *State) End() bool {
return st.buffer.End()
}
// PrevWord déplace le curseur au début du mot précédent.
func (st *State) PrevWord() bool {
return st.buffer.PrevWord()
}
// NextWord déplace le curseur au début du mot suivant.
func (st *State) NextWord() bool {
return st.buffer.NextWord()
}
// SetCursor déplace le curseur à la position donnée
func (st *State) SetCursor(cursor int) bool {
return st.buffer.SetCursor(cursor)
}
2023-10-07 19:13:39 +00:00
func (st *State) rem(cb func() option.Option[string]) (ok bool) {
var yank string
if yank, ok = cb().Get(); ok {
st.AppendYank(yank)
}
return
}
// Back supprime le caractère situé avant le curseur.
func (st *State) Back() bool {
return st.rem(st.buffer.Back)
}
// Del supprime le caractère sous le curseur.
func (st *State) Del() bool {
return st.rem(st.buffer.Del)
}
// BackWord supprime le mot avant le curseur.
func (st *State) BackWord() bool {
return st.rem(st.buffer.BackWord)
}
// DelWord supprime le mot après le curseur.
func (st *State) DelWord() bool {
return st.rem(st.buffer.DelWord)
}
// RemoveBegin supprime les caractères avant le curseur.
func (st *State) RemoveBegin() bool {
return st.rem(st.buffer.RemoveBegin)
}
// RemoveEnd supprime les caractères après le curseur, curseur compris.
func (st *State) RemoveEnd() bool {
return st.rem(st.buffer.RemoveEnd)
}
// Clear efface le buffer.
func (st *State) Clear() bool {
return st.buffer.Clear().IsDefined()
}
// SetBuffer réinitialise le buffer avec la chaîne donnée.
func (st *State) SetBuffer(value string) bool {
st.buffer.Clear()
st.buffer.Insert([]rune(value)...)
return true
}
// YankPrev colle dans le buffer le précédent élément copié.
func (st *State) YankPrev() (ok bool) {
if ok = st.yank.Prev() && st.RestoreBuffer(); ok {
st.UnfocusHistory()
st.RemoveHistorySearch()
var yank string
if yank, ok = console.FocusedElement(st.yank).Get(); ok {
ok = st.Insert([]rune(yank)...)
}
}
return
}
// Yank colle le dernier élément copié.
func (st *State) Yank() (ok bool) {
st.yank.SetCursor(st.yank.Len())
if ok = st.yank.Prev(); ok {
st.UnfocusHistory()
st.RemoveHistorySearch()
st.SaveBuffer()
var yank string
if yank, ok = console.FocusedElement(st.yank).Get(); ok {
ok = st.Insert([]rune(yank)...)
}
}
return
}
// HistoryPrev remonte dans lhistorique.
func (st *State) HistoryPrev() (ok bool) {
if ok = st.history.Prev(); !ok {
ok = st.history.SetCursor(0)
}
if ok {
st.UnfocusYank()
st.RemoveHistorySearch()
st.SaveBuffer()
var h string
if h, ok = console.FocusedElement(st.history).Get(); ok {
ok = st.SetBuffer(h)
}
}
return
}
// HistoryNext redescend dans lhistorique.
func (st *State) HistoryNext() (ok bool) {
if ok = st.history.Next(); ok {
st.UnfocusYank()
st.RemoveHistorySearch()
st.SaveBuffer()
var h string
h, ok = console.FocusedElement(st.history).Get()
if ok {
ok = st.SetBuffer(h)
} else {
ok = st.RestoreBuffer()
}
}
return
}
// GetProposal retourne une suggestion de saisie pour la saisie en cours
// basée sur lhistorique.
func (st *State) GetProposal(insensitive ...bool) (proposal string) {
candidates := console.HistoryFilterPrefix(st.history, st.buffer.String(), insensitive...)
if l := len(candidates); l > 0 {
proposal = candidates[l-1]
}
return
}
// SearchMotiveNext remonte dans lhistorique de recherche.
// Lhistorique de recherche est initialisée avec le motif du buffer.
func (st *State) SearchMotiveNext(insensitive ...bool) (ok bool) {
var hs console.History
if hs, ok = st.historySearch.Get(); !ok {
motive := st.buffer.String()
hs = NewHistory(false)
candidates := console.HistoryFilterMotive(st.history, motive, insensitive...)
for _, h := range candidates {
hs.Append(h)
}
hs.SetCursor(-1)
st.historySearch = option.Some(hs)
}
if ok = hs.Next(); ok {
st.UnfocusYank()
st.UnfocusHistory()
st.SaveBuffer()
var h string
if h, ok = console.FocusedElement(hs).Get(); ok {
st.SetBuffer(h)
}
}
return
}
// SearchMotivePrev redescend dans lhistorique de recherche.
// Lhistorique de recherche est initialisée avec le motif du buffer.
func (st *State) SearchMotivePrev(insensitive ...bool) (ok bool) {
var hs console.History
if hs, ok = st.historySearch.Get(); !ok {
prefix := st.buffer.String()
hs = NewHistory(false)
candidates := console.HistoryFilterMotive(st.history, prefix, insensitive...)
for _, h := range candidates {
hs.Append(h)
}
st.historySearch = option.Some(hs)
}
if ok = hs.Prev(); ok {
st.UnfocusYank()
st.UnfocusHistory()
st.SaveBuffer()
var h string
if h, ok = console.FocusedElement(hs).Get(); ok {
st.SetBuffer(h)
}
}
return
}
2023-10-07 19:13:39 +00:00
// SearchHistoryNext remonte dans lhistorique de recherche.
// Lhistorique de recherche est initialisée avec le préfixe du buffer.
func (st *State) SearchHistoryNext(insensitive ...bool) (ok bool) {
var hs console.History
if hs, ok = st.historySearch.Get(); !ok {
prefix := st.buffer.String()
hs = NewHistory(false)
candidates := console.HistoryFilterPrefix(st.history, prefix, insensitive...)
for _, h := range candidates {
hs.Append(h)
}
hs.SetCursor(-1)
st.historySearch = option.Some(hs)
}
if ok = hs.Next(); ok {
st.UnfocusYank()
st.UnfocusHistory()
st.SaveBuffer()
var h string
if h, ok = console.FocusedElement(hs).Get(); ok {
st.SetBuffer(h)
}
}
return
}
// SearchHistoryPrev redescend dans lhistorique de recherche.
// Lhistorique de recherche est initialisée avec le préfixe du buffer.
func (st *State) SearchHistoryPrev(insensitive ...bool) (ok bool) {
var hs console.History
if hs, ok = st.historySearch.Get(); !ok {
prefix := st.buffer.String()
hs = NewHistory(false)
candidates := console.HistoryFilterPrefix(st.history, prefix, insensitive...)
for _, h := range candidates {
hs.Append(h)
}
st.historySearch = option.Some(hs)
}
if ok = hs.Prev(); ok {
st.UnfocusYank()
st.UnfocusHistory()
st.SaveBuffer()
var h string
if h, ok = console.FocusedElement(hs).Get(); ok {
st.SetBuffer(h)
}
}
return
}
// Cancel annule lhistorique, la recherche ou la copie en cours.
func (st *State) Cancel() (ok bool) {
var sb *Buffer
if sb, ok = st.savedBuffer.Get(); ok {
b := sb.Clone()
st.buffer = *b
st.FixState()
}
return
}
// GetPrompt retourne le prompt configuré.
func (st *State) GetPrompt() string {
return st.prompt
}