gob/shell/console/atom/state.go

653 lines
14 KiB
Go
Raw 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 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()
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
}
// 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()
}
// 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)
}
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
}
// 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
}