Module shell (3)

This commit is contained in:
Benjamin VAUDOUR 2023-10-07 21:13:39 +02:00
parent 8e5ad41b11
commit 75f0c1350b
13 changed files with 2644 additions and 0 deletions

View file

@ -0,0 +1,307 @@
package atom
import (
"gitea.zaclys.com/bvaudour/gob/option"
)
// Buffer stocke lensemble des caractère saisis et la position du curseur.
type Buffer struct {
data []rune
cursor int
}
// Len retourne le nombre de caractères du buffer.
func (b *Buffer) Len() int {
return len(b.data)
}
// Insert ajoute les caractères donnés à la position du curseur.
func (b *Buffer) Insert(data ...rune) {
l1, l2, c := b.Len(), len(data), b.cursor
newData := make([]rune, l1+l2)
if c > 0 {
copy(newData[:c], b.data[:c])
}
copy(newData[c:c+l2], data)
if c < l1 {
copy(newData[c+l2:], b.data[c:])
}
b.data = newData
b.cursor += l2
}
// Replace remplace les caractères du buffer à partir de la position du curseur
// par les caractères donnés.
// Si le buffer nest pas assez grand, le buffer est agrandi.
func (b *Buffer) Replace(data ...rune) {
l1, l2, c := b.Len(), len(data), b.cursor
l := max(l1, c+l2)
newData := make([]rune, l)
if c > 0 {
copy(newData[:c], b.data[:c])
}
copy(newData[c:c+l2], data)
if c+l2 < l1 {
copy(newData[c+l2:], b.data[c+l2:])
}
b.data = newData
b.cursor += l2
}
// Transpose transpose le caractère sous le curseur avec le caractère précédent.
// Si la transposition na pu se faire, il retourne faux.
func (b *Buffer) Transpose() (ok bool) {
l := b.Len()
if ok = b.cursor > 0 && l >= 2; ok {
c := b.cursor
if c == l {
c--
}
b.data[c-1], b.data[c] = b.data[c], b.data[c-1]
}
return
}
// Move déplace le curseur avec lincrément donné.
// Si lincrément est négatif, le déplacement se fait vers la gauche.
// Sinon, il se fait vers la droite.
// Si la position du curseur sort du buffer, retourne faux.
func (b *Buffer) Move(inc int) (ok bool) {
c, l := b.cursor+inc, b.Len()
if ok = c >= 0 && c <= l; ok {
b.cursor = c
}
return
}
// Left déplace le curseur dun caractère vers la gauche.
func (b *Buffer) Left() (ok bool) {
return b.Move(-1)
}
// Right déplace le curseur dun caractère vers la droite.
func (b *Buffer) Right() (ok bool) {
return b.Move(1)
}
// Begin déplace le curseur au début du buffer.
func (b *Buffer) Begin() (ok bool) {
if ok = b.cursor > 0; ok {
b.cursor = 0
}
return
}
// End déplace le curseur à la fin du buffer.
func (b *Buffer) End() (ok bool) {
l := b.Len()
if ok = b.cursor < l; ok {
b.cursor = l
}
return
}
// PrevWord déplace le curseur au début du mot,
// ou au début du précédent mot si le curseur nest
// pas positionné sur un mot.
func (b *Buffer) PrevWord() (ok bool) {
l := b.Len()
if l == 0 || b.cursor == 0 {
return
}
i := b.cursor - 1
for i == l || IsSpace(b.data[i]) {
i--
}
for i > 0 && !IsSpace(b.data[i-1]) {
i--
}
return b.Move(i - b.cursor)
}
// NextWord déplace le curseur au début du prochain mot.
func (b *Buffer) NextWord() (ok bool) {
i, l := b.cursor, b.Len()
if i == l {
return
}
for i < l && !IsSpace(b.data[i]) {
i++
}
for i < l && IsSpace(b.data[i]) {
i++
}
return b.Move(i - b.cursor)
}
// Backn supprime n caractères à gauche du curseur.
// Si la suppression a bien été effectuée, il retourne
// la chaîne supprimée.
func (b *Buffer) Backn(n int) (shifted option.Option[string]) {
l, c := b.Len(), b.cursor
c2 := c - n
if n <= 0 || c2 < 0 {
return
}
shifted = option.Some(string(b.data[c2:c]))
newData := make([]rune, l-n)
copy(newData[:c2], b.data[:c2])
if c < l {
copy(newData[c2:], b.data[c:])
}
b.data = newData
b.cursor = c2
return
}
// Deln supprime n caractères à partir du curseur.
// Si la suppression a bien été effectuée, il retourne
// la chaîne supprimée.
func (b *Buffer) Deln(n int) (shifted option.Option[string]) {
l, c := b.Len(), b.cursor
c2 := c + n
if n <= 0 || c2 > l {
return
}
shifted = option.Some(string(b.data[c:c2]))
newData := make([]rune, l-n)
copy(newData[:c], b.data[:c])
if c2 < l {
copy(newData[c:], b.data[c2:])
}
b.data = newData
return
}
// Back supprime le caractère avant le curseur.
func (b *Buffer) Back() option.Option[string] {
return b.Backn(1)
}
// Del supprime le caractère sous le curseur.
func (b *Buffer) Del() option.Option[string] {
return b.Deln(1)
}
// BackWord supprime le mot avant le caractère.
func (b *Buffer) BackWord() (shifted option.Option[string]) {
l := b.Len()
if l == 0 || b.cursor == 0 {
return
}
i := b.cursor - 1
for i == l || IsSpace(b.data[i]) {
i--
}
for i > 0 && !IsSpace(b.data[i-1]) {
i--
}
return b.Backn(b.cursor - i)
}
// DelWord supprime le mot après le caractère.
func (b *Buffer) DelWord() (shifted option.Option[string]) {
i, l := b.cursor, b.Len()
if i == l {
return
}
for i < l || IsSpace(b.data[i]) {
i++
}
for i < l && !IsSpace(b.data[i]) {
i++
}
return b.Deln(i - b.cursor)
}
// RemoveBegin supprime les caractères avant le curseur.
func (b *Buffer) RemoveBegin() (shifted option.Option[string]) {
if b.cursor > 0 {
shifted = option.Some(string(b.data[:b.cursor]))
b.data = b.data[b.cursor:]
b.cursor = 0
}
return
}
// RemoveEnd supprime les caractères après le curseur, curseur compris.
func (b *Buffer) RemoveEnd() (shifted option.Option[string]) {
if b.cursor < b.Len() {
shifted = option.Some(string(b.data[b.cursor:]))
b.data = b.data[:b.cursor]
}
return
}
// Clear efface tout le buffer.
func (b *Buffer) Clear() (shifted option.Option[string]) {
if b.Len() > 0 {
shifted = option.Some(string(b.data))
b.data, b.cursor = b.data[:0], 0
}
return
}
// String retourne le contenu du buffer sous forme de chaîne de caractères.
func (b *Buffer) String() string {
return string(b.data)
}
// Cursor retourne la position du curseur.
func (b *Buffer) Cursor() int {
return b.cursor
}
// Clone effectue une copie du buffer.
func (b *Buffer) Clone() *Buffer {
l, c := len(b.data), cap(b.data)
cb := Buffer{
data: make([]rune, l, c),
cursor: b.cursor,
}
copy(cb.data, b.data)
return &cb
}

View file

@ -0,0 +1,98 @@
package atom
import (
"gitea.zaclys.com/bvaudour/gob/option"
)
// Cycler stocke les données possibles de lautocomplétion
type Cycler struct {
data []string
cursor int
cycled bool
}
func isCursorValid(c, l int) bool {
return c >= 0 && c < l
}
// Len retourne le nombre de propositions du cycler.
func (c *Cycler) Len() int {
return len(c.data)
}
// Cursor retourne la position de lélément pointé.
func (c *Cycler) Cursor() int {
return c.cursor
}
// Index retourne lélément selon son index.
func (c *Cycler) Index(i int) (value option.Option[string]) {
l := c.Len()
if i < 0 {
i += l
}
if isCursorValid(i, l) {
value = option.Some(c.data[i])
}
return
}
// SetCursor positionne le pointeur à lindex donné.
func (c *Cycler) SetCursor(n int) (ok bool) {
l := c.Len()
if n < 0 {
n += l
}
if ok = isCursorValid(n, l); ok {
c.cursor = n
}
return
}
// Append ajoute une proposition au cycler.
func (c *Cycler) Append(data string) {
c.data = append(c.data, data)
}
// Clear efface le cycler.
func (c *Cycler) Clear() {
c.data = c.data[:0]
c.cursor = -1
}
// Next incrémente le pointeur et retourne vrai
// si le pointeur pointe sur un élément.
func (c *Cycler) Next() (ok bool) {
n := c.cursor + 1
if ok = c.SetCursor(n); !ok && c.cycled {
ok = c.SetCursor(0)
}
return
}
// Prev décrémente le pointeur et retourne vrai
// si le pointeur pointe sur un élément.
func (c *Cycler) Prev() (ok bool) {
n := c.cursor - 1
if ok = n >= 0 && c.SetCursor(n); !ok && c.cycled {
ok = c.SetCursor(-1)
}
return
}
// NewCycler crée un nouveau cycler avec les données fournies.
// Si cycled est vrai, le cycler revient au début sil a atteint la
// fin et inversement.
func NewCycler(cycled bool, data ...string) *Cycler {
return &Cycler{
data: data,
cycled: cycled,
cursor: -1,
}
}

View file

@ -0,0 +1,117 @@
package atom
import (
"bufio"
"fmt"
"io"
"unicode/utf8"
"gitea.zaclys.com/bvaudour/gob/option"
)
// History stocke lhistorique des saisies.
type History struct {
Cycler
}
// Append ajoute une entrée dans lhistorique.
func (h *History) Append(data string) {
h.Cycler.Append(data)
h.cursor = h.Len()
}
// Clear efface lhistorique.
func (h *History) Clear() {
h.Cycler.Clear()
h.cursor = 0
}
// SetCursor positionne le pointeur de lhistorique.
func (h *History) SetCursor(n int) (ok bool) {
l := h.Len()
if ok = n >= -1 && n <= l; ok {
h.cursor = n
}
return
}
// Next incrémente le pointeur de lhistorique
// et retourne vrai si le pointeur pointe vers un élément.
func (h *History) Next() (ok bool) {
if ok = h.SetCursor(h.cursor + 1); ok {
ok = isCursorValid(h.cursor, h.Len())
} else if h.cycled {
ok = h.SetCursor(0)
}
return
}
// Prev décrémente le pointeur de lhistorique
// et retourne vrai si le pointeur pointe vers un élément.
func (h *History) Prev() (ok bool) {
if ok = h.SetCursor(h.cursor - 1); ok {
ok = isCursorValid(h.cursor, h.Len())
} else if l := h.Len(); h.cycled && l > 0 {
ok = h.SetCursor(l - 1)
}
return
}
// Read charge lhistorique à partir dun fichier
// et retourne le nombre dentrées ajoutées.
func (h *History) Read(r io.Reader) option.Result[int] {
var n int
buf := bufio.NewReader(r)
for {
line, part, err := buf.ReadLine()
if err == nil {
if part {
err = fmt.Errorf("line %d is too long", n+1)
} else if !utf8.Valid(line) {
err = fmt.Errorf("invalid string at line %d", n+1)
} else {
h.Append(string(line))
n++
}
}
if err != nil {
if err == io.EOF {
break
}
return option.Err[int](err)
}
}
return option.Ok(n)
}
// Write persiste lhistorique dans un fichier
// et retourne le nombre dentrées ajoutées.
func (h *History) Write(w io.Writer) option.Result[int] {
var n int
for _, item := range h.data {
_, err := fmt.Fprintln(w, item)
if err != nil {
return option.Err[int](err)
}
n++
}
return option.Ok(n)
}
// NewHistory retourne un nouvel historique.
// Si cycled est faux lhistorique ne peut être parcouru en boucle.
func NewHistory(cycled bool) *History {
var h History
h.cycled = cycled
return &h
}

338
shell/console/atom/input.go Normal file
View file

@ -0,0 +1,338 @@
package atom
import (
"bufio"
"io"
"strconv"
"time"
)
func timeout() <-chan time.Time {
return time.After(50 * time.Millisecond)
}
type input struct {
buf *bufio.Reader
next <-chan nchar
pending []Char
}
func (in *input) shift() (r Char) {
if len(in.pending) > 0 {
r, in.pending = in.pending[0], in.pending[1:]
}
return
}
func (in *input) push(r Char) Char {
in.pending = append(in.pending, r)
return r
}
func (in *input) clear() {
in.pending = in.pending[:0]
}
func (in *input) readRune() nchar {
if r, _, err := in.buf.ReadRune(); err != nil {
return ec(err)
} else {
return nc(r)
}
}
func newInput(r io.Reader) *input {
return &input{
buf: bufio.NewReader(r),
}
}
func (in *input) restart() {
next := make(chan nchar, 200)
go func() {
for {
n := in.readRune()
next <- n
needClose := !n.IsOk()
if !needClose {
r, ok := n.Ok()
needClose = ok && (r == Lf || r == Cr || r == C_C || r == C_D)
}
if needClose {
close(next)
return
}
}
}()
in.next = next
}
func (in *input) isWaiting() bool {
return len(in.next) > 0
}
func (in *input) nextc() (n nchar) {
var ok bool
select {
case n, ok = <-in.next:
if !ok {
return ec(ErrInternal)
}
if r, ok := n.Ok(); ok {
in.push(r)
return nc(r)
}
}
return
}
func (in *input) nextt() (n nchar) {
var ok bool
select {
case n, ok = <-in.next:
if !ok {
return ec(ErrInternal)
if r, ok := n.Ok(); ok {
in.push(r)
return nc(r)
}
}
case <-timeout():
return ec(ErrTimeout)
}
return
}
func (in *input) escO() (key nkey) {
key = nk(KeyNil)
n := in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return nk(keyS(AS_O))
}
return ek(err)
}
r, ok := n.Ok()
if !ok {
return
}
var s Sequence
if r >= '0' && r < '9' {
s = Sequence(r << 8)
n = in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, _ = n.Ok()
}
s |= Sequence(r)
return nk(keyS(s))
}
func (in *input) escBracket() (key nkey) {
key = nk(KeyNil)
n := in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, ok := n.Ok()
if !ok {
return
}
var s Sequence
switch r {
case 'A':
s = Up
case 'B':
s = Down
case 'C':
s = Right
case 'D':
s = Left
case 'F':
s = End
case 'H':
s = Home
case 'Z':
s = S_Tab
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return in.escNumberBracket(r)
default:
}
return nk(keyS(s))
}
func (in *input) escNumberBracket(r Char) (key nkey) {
key = nk(KeyNil)
num := []Char{r}
for {
n := in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, ok := n.Ok()
if !ok {
return
}
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
num = append(num, r)
case '~':
return in.escTildeBracket(num)
case ';':
return in.escSemiColonBracket(num)
default:
return
}
}
return
}
func (in *input) escTildeBracket(num []Char) (key nkey) {
x, _ := strconv.ParseInt(string(num), 10, 32)
s := Sequence(x)
return nk(keyS(s))
}
func (in *input) escSemiColonBracket(num []Char) (key nkey) {
key = nk(KeyNil)
x, _ := strconv.ParseInt(string(num), 10, 32)
seqn := x != 1
var s Sequence
if seqn {
s = Sequence(x)
}
n := in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, ok := n.Ok()
if !ok {
return
}
s |= Sequence(r << 8)
n = in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, ok = n.Ok()
if !ok {
return
}
if r == '~' {
if !seqn {
return nk(KeyNil)
}
} else if !seqn {
s |= Sequence(r)
} else {
return
}
return nk(keyS(s))
}
func (in *input) nextChar() (key nkey) {
key = nk(KeyNil)
if len(in.pending) > 0 {
r := in.shift()
return nk(keyC(r))
}
n := in.nextc()
if err, ok := n.Err(); ok {
in.shift()
return ek(err)
}
r, ok := n.Ok()
if !ok {
return
}
if r != Esc {
in.shift()
return nk(keyC(r))
}
n = in.nextt()
if err, ok := n.Err(); ok {
if err == ErrTimeout {
return
}
return ek(err)
}
r, ok = n.Ok()
if !ok {
return
}
var s Sequence
switch r {
case Bs:
s = A_Bs
case 'O':
return in.escO()
case '[':
return in.escBracket()
case 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z':
s = Sequence(r << 16)
default:
return
}
in.clear()
return nk(keyS(s))
}

401
shell/console/atom/key.go Normal file
View file

@ -0,0 +1,401 @@
package atom
import (
"errors"
"gitea.zaclys.com/bvaudour/gob/option"
)
// Char représente un caractère UTF-8 saisi
// via une touche ou une combinaison de touches.
type Char = rune
// Sequence représente une séquence de caractères UTF-8
// saisis via une touche ou une combinaison de touche.
type Sequence rune
type nchar = option.Result[Char]
type nseq = option.Result[Sequence]
type nkey = option.Result[Key]
func nc(c Char) option.Result[Char] { return option.Ok(c) }
func ns(c Sequence) option.Result[Sequence] { return option.Ok(c) }
func nk(c Key) option.Result[Key] { return option.Ok(c) }
func ec(err error) option.Result[Char] { return option.Err[Char](err) }
func es(err error) option.Result[Sequence] { return option.Err[Sequence](err) }
func ek(err error) option.Result[Key] { return option.Err[Key](err) }
// Key représente un caractère ou une séquence de caractères.
type Key struct {
option.Choice[Char, Sequence]
}
func keyC(c Char) Key {
return Key{option.Left[Char, Sequence](c)}
}
func keyS(c Sequence) Key {
return Key{option.Right[Char, Sequence](c)}
}
// Char retourne un caractère.
func (k Key) Char() (Char, bool) {
return k.Left()
}
// Sequence retourne une séquence.
func (k Key) Sequence() (Sequence, bool) {
return k.Right()
}
// IsChar retourne vrai si k représente un caractère.
func (k Key) IsChar() bool {
return k.IsLeft()
}
// IsSequence retourne vrai si k représente une séquence.
func (k Key) IsSequence() bool {
return k.IsRight()
}
var (
KeyNil Key
)
const (
Bs = '\u007f' // \?
Sp = '\u0020'
)
const (
C_A rune = iota + 1
C_B
C_C
C_D
C_E
C_F
C_G
C_H
C_I // \t
C_J // \n
C_K
C_L
C_M // \r
C_N
C_O
C_P
C_Q
C_R
C_S
C_T
C_U
C_V
C_W
C_X
C_Y
C_Z
Esc // \e
)
const (
Tab = C_I
Lf = C_J
Cr = C_M
)
const (
shift Sequence = (iota + '2') << 8
alt
altShit
ctrl
ctrlShift
ctrlAlt
ctrlAltShift
)
const (
MaskKey Sequence = 127 // Permet, en appliquant &, de récupérer la séquence sans les touches modificatrices
)
const (
Up Sequence = iota + 'A' // ^[[A
Down // ^[[B
Right // ^[[C
Left // ^[[D
)
const (
Ins Sequence = iota + 2 // ^[[2~
Del // ^[[3~
_
PgUp Sequence = 5 // ^[[5~
PgDown = 6 // ^[[6~
End Sequence = 'F' // ^[[F
Home Sequence = 'H' // ^[[H
)
const (
F1 Sequence = iota + 'P' // ^[OP
F2 // ^[OQ
F3 // ^[OR
F4 // ^[OS
)
const (
F5 Sequence = iota + 15 // ^[[15~
_
F6 // ^[[17~
F7 // ^[[18~
F8 // ^[[19~
F9 // ^[[20~
F10 // ^[[21~
_
F11 // ^[[23~
F12 // ^[[24~
)
const (
S_Up = shift | Up // ^[[1;2A
S_down = shift | Down // ^[[1;2B
S_Right = shift | Right // ^[[1;2C
S_Left = shift | Left // ^[[1;2D
S_Ins = shift | Ins // ^[[2;2~
S_Del = shift | Del // ^[[3;2~
S_PgUp = shift | PgUp // ^[[5;2~
S_PgDown = shift | PgDown // ^[[6;2~
S_End = shift | End // ^[[1;2F
S_Home = shift | Home // ^[[1;2H
S_F1 = shift | F1 // ^[O2P
S_F2 = shift | F2 // ^[O2Q
S_F3 = shift | F3 // ^[O2R
S_F4 = shift | F4 // ^[O2S
S_F5 = shift | F5 // ^[[15;2~
S_F6 = shift | F6 // ^[[17;2~
S_F7 = shift | F7 // ^[[18;2~
S_F8 = shift | F8 // ^[[19;2~
S_F9 = shift | F9 // ^[[20;2~
S_F10 = shift | F10 // ^[[21;2~
S_F11 = shift | F11 // ^[[23;2~
S_F12 = shift | F12 // ^[[24;2~
S_Tab = shift | Sequence(Tab) // ^[[Z
A_Up = alt | Up // ^[[1;3A
A_down = alt | Down // ^[[1;3B
A_Right = alt | Right // ^[[1;3C
A_Left = alt | Left // ^[[1;3D
A_Ins = alt | Ins // ^[[2;3~
A_Del = alt | Del // ^[[3;3~
A_PgUp = alt | PgUp // ^[[5;3~
A_PgDown = alt | PgDown // ^[[6;3~
A_End = alt | End // ^[[1;3F
A_Home = alt | Home // ^[[1;3H
A_F1 = alt | F1 // ^[O3P
A_F2 = alt | F2 // ^[O3Q
A_F3 = alt | F3 // ^[O3R
A_F4 = alt | F4 // ^[O3S
A_F5 = alt | F5 // ^[[15;3~
A_F6 = alt | F6 // ^[[17;3~
A_F7 = alt | F7 // ^[[18;3~
A_F8 = alt | F8 // ^[[19;3~
A_F9 = alt | F9 // ^[[20;3~
A_F10 = alt | F10 // ^[[21;3~
A_F11 = alt | F11 // ^[[23;3~
A_F12 = alt | F12 // ^[[24;3~
A_Bs = alt | Sequence(Bs) // ^[<BACK>
AS_Up = altShit | Up // ^[[1;4A
AS_down = altShit | Down // ^[[1;4B
AS_Right = altShit | Right // ^[[1;4C
AS_Left = altShit | Left // ^[[1;4D
AS_Ins = altShit | Ins // ^[[2;4~
AS_Del = altShit | Del // ^[[3;4~
AS_PgUp = altShit | PgUp // ^[[5;4~
AS_PgDown = altShit | PgDown // ^[[6;4~
AS_End = altShit | End // ^[[1;4F
AS_Home = altShit | Home // ^[[1;4H
AS_F1 = altShit | F1 // ^[O4P
AS_F2 = altShit | F2 // ^[O4Q
AS_F3 = altShit | F3 // ^[O4R
AS_F4 = altShit | F4 // ^[O4S
AS_F5 = altShit | F5 // ^[[15;4~
AS_F6 = altShit | F6 // ^[[17;4~
AS_F7 = altShit | F7 // ^[[18;4~
AS_F8 = altShit | F8 // ^[[19;4~
AS_F9 = altShit | F9 // ^[[20;4~
AS_F10 = altShit | F10 // ^[[21;4~
AS_F11 = altShit | F11 // ^[[23;4~
AS_F12 = altShit | F12 // ^[[24;4~
C_Up = ctrl | Up // ^[[1;5A
C_down = ctrl | Down // ^[[1;5B
C_Right = ctrl | Right // ^[[1;5C
C_Left = ctrl | Left // ^[[1;5D
C_Ins = ctrl | Ins // ^[[2;5~
C_Del = ctrl | Del // ^[[3;5~
C_PgUp = ctrl | PgUp // ^[[5;5~
C_PgDown = ctrl | PgDown // ^[[6;5~
C_End = ctrl | End // ^[[1;5F
C_Home = ctrl | Home // ^[[1;5H
C_F1 = ctrl | F1 // ^[O5P
C_F2 = ctrl | F2 // ^[O5Q
C_F3 = ctrl | F3 // ^[O5R
C_F4 = ctrl | F4 // ^[O5S
C_F5 = ctrl | F5 // ^[[15;5~
C_F6 = ctrl | F6 // ^[[17;5~
C_F7 = ctrl | F7 // ^[[18;5~
C_F8 = ctrl | F8 // ^[[19;5~
C_F9 = ctrl | F9 // ^[[20;5~
C_F10 = ctrl | F10 // ^[[21;5~
C_F11 = ctrl | F11 // ^[[23;5~
C_F12 = ctrl | F12 // ^[[34;5~
CS_Up = ctrlShift | Up // ^[[1;6A
CS_down = ctrlShift | Down // ^[[1;6B
CS_Right = ctrlShift | Right // ^[[1;6C
CS_Left = ctrlShift | Left // ^[[1;6D
CS_Ins = ctrlShift | Ins // ^[[2;6~
CS_Del = ctrlShift | Del // ^[[3;6~
CS_PgUp = ctrlShift | PgUp // ^[[5;6~
CS_PgDown = ctrlShift | PgDown // ^[[6;6~
CS_End = ctrlShift | End // ^[[1;6F
CS_Home = ctrlShift | Home // ^[[1;6H
CS_F1 = ctrlShift | F1 // ^[O6P
CS_F2 = ctrlShift | F2 // ^[O6Q
CS_F3 = ctrlShift | F3 // ^[O6R
CS_F4 = ctrlShift | F4 // ^[O6S
CS_F5 = ctrlShift | F5 // ^[[15;6~
CS_F6 = ctrlShift | F6 // ^[[17;6~
CS_F7 = ctrlShift | F7 // ^[[18;6~
CS_F8 = ctrlShift | F8 // ^[[19;6~
CS_F9 = ctrlShift | F9 // ^[[20;6~
CS_F10 = ctrlShift | F10 // ^[[21;6~
CS_F11 = ctrlShift | F11 // ^[[23;6~
CS_F12 = ctrlShift | F12 // ^[[24;6~
CA_Up = ctrlAlt | Up // ^[[1;7A
CA_down = ctrlAlt | Down // ^[[1;7B
CA_Right = ctrlAlt | Right // ^[[1;7C
CA_Left = ctrlAlt | Left // ^[[1;7D
CA_Ins = ctrlAlt | Ins // ^[[2;7~
CA_Del = ctrlAlt | Del // ^[[3;7~
CA_PgUp = ctrlAlt | PgUp // ^[[5;7~
CA_PgDown = ctrlAlt | PgDown // ^[[6;7~
CA_End = ctrlAlt | End // ^[[1;7F
CA_Home = ctrlAlt | Home // ^[[1;7H
CA_F1 = ctrlAlt | F1 // ^[O7P
CA_F2 = ctrlAlt | F2 // ^[O7Q
CA_F3 = ctrlAlt | F3 // ^[O7R
CA_F4 = ctrlAlt | F4 // ^[O7S
CA_F5 = ctrlAlt | F5 // ^[[15;7~
CA_F6 = ctrlAlt | F6 // ^[[17;7~
CA_F7 = ctrlAlt | F7 // ^[[18;7~
CA_F8 = ctrlAlt | F8 // ^[[19;7~
CA_F9 = ctrlAlt | F9 // ^[[20;7~
CA_F10 = ctrlAlt | F10 // ^[[21;7~
CA_F11 = ctrlAlt | F11 // ^[[23;7~
CA_F12 = ctrlAlt | F12 // ^[[24;7~
CAS_Up = ctrlAltShift | Up // ^[[1;8A
CAS_down = ctrlAltShift | Down // ^[[1;8B
CAS_Right = ctrlAltShift | Right // ^[[1;8C
CAS_Left = ctrlAltShift | Left // ^[[1;8D
CAS_Ins = ctrlAltShift | Ins // ^[[2;8~
CAS_Del = ctrlAltShift | Del // ^[[3;8~
CAS_PgUp = ctrlAltShift | PgUp // ^[[5;8~
CAS_PgDown = ctrlAltShift | PgDown // ^[[6;8~
CAS_End = ctrlAltShift | End // ^[[1;8F
CAS_Home = ctrlAltShift | Home // ^[[1;8H
CAS_F1 = ctrlAltShift | F1 // ^[O8P
CAS_F2 = ctrlAltShift | F2 // ^[O8Q
CAS_F3 = ctrlAltShift | F3 // ^[O8R
CAS_F4 = ctrlAltShift | F4 // ^[O8S
CAS_F5 = ctrlAltShift | F5 // ^[[15;8~
CAS_F6 = ctrlAltShift | F6 // ^[[17;8~
CAS_F7 = ctrlAltShift | F7 // ^[[18;8~
CAS_F8 = ctrlAltShift | F8 // ^[[19;8~
CAS_F9 = ctrlAltShift | F9 // ^[[20;8~
CAS_F10 = ctrlAltShift | F10 // ^[[21;8~
CAS_F11 = ctrlAltShift | F11 // ^[[23;8~
CAS_F12 = ctrlAltShift | F12 // ^[[24;8~
)
const (
A_A Sequence = ('a' + iota) << 16 // ^[a
A_B // ^[b
A_C // ^[c
A_D // ^[d
A_E // ^[e
A_F // ^[f
A_G // ^[g
A_H // ^[h
A_I // ^[i
A_J // ^[j
A_K // ^[k
A_L // ^[l
A_M // ^[m
A_N // ^[n
A_O // ^[o
A_P // ^[p
A_Q // ^[q
A_R // ^[r
A_S // ^[s
A_T // ^[t
A_U // ^[u
A_V // ^[v
A_W // ^[w
A_X // ^[x
A_Y // ^[y
A_Z // ^[z
)
const (
AS_A Sequence = ('A' + iota) << 16 // ^[A
AS_B // ^[B
AS_C // ^[C
AS_D // ^[D
AS_E // ^[E
AS_F // ^[F
AS_G // ^[G
AS_H // ^[H
AS_I // ^[I
AS_J // ^[J
AS_K // ^[K
AS_L // ^[L
AS_M // ^[M
AS_N // ^[N
AS_O // ^[O
AS_P // ^[P
AS_Q // ^[Q
AS_R // ^[R
AS_S // ^[S
AS_T // ^[T
AS_U // ^[U
AS_V // ^[V
AS_W // ^[W
AS_X // ^[X
AS_Y // ^[Y
AS_Z // ^[Z
)
var (
ErrInternal = errors.New("internal error")
ErrTimeout = errors.New("timeout")
evChar = map[int64]Sequence{
2: Ins,
3: Del,
4: End,
5: PgUp,
6: PgDown,
7: Home,
8: End,
15: F5,
17: F6,
18: F7,
19: F8,
20: F9,
21: F10,
23: F11,
24: F12,
}
)

View file

@ -0,0 +1,195 @@
package atom
import (
"errors"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"gitea.zaclys.com/bvaudour/gob/option"
)
var (
ErrInvalidUnicode = errors.New("Not unicode")
)
// NextUnescape enlève le caractère déchapement en début de chaîne.
func NextUnescape(str string) (result option.Option[string]) {
if !strings.HasPrefix(str, "\033[") {
result = option.Some(str)
} else if i := strings.Index(str, "m"); i >= 0 {
result = option.Some(str[i+1:])
}
return
}
// IsPrintable retourne vrai si le caractère donné est imprimable.
func IsPrintable(r Char) bool {
return r == '\n' || unicode.IsPrint(r)
}
// IsSpace retourne vrai si le caractère donné est un caractère blanc.
func IsSpace(r Char) bool {
return unicode.IsSpace(r)
}
// CheckUnicode vérifie si la chaîne de caractère
// ne contient que des caractères imprimables.
func CheckUnicode(str string) (err option.Option[error]) {
runes := []rune(str)
for i, r := range runes {
if r == Esc {
if i == len(runes)-1 || runes[i+1] != '[' {
return option.Some(ErrInvalidUnicode)
}
} else if unicode.Is(unicode.C, r) {
return option.Some(ErrInvalidUnicode)
}
}
return
}
// VisibleWith indique le nombre de colonnes nécessaires
// pour afficher dune chaîne passée à echo.
func VisibleWidth(str string) int {
w := 0
for len(str) > 0 {
cleaned := NextUnescape(str)
if s, ok := cleaned.Get(); ok {
str = s
} else {
break
}
r, i := utf8.DecodeRuneInString(str)
if IsPrintable(r) {
w++
}
str = str[i:]
}
return w
}
// CursorOffset calcule la position relative du curseur
// par rapport au début de la chaîne, ainsi que le
// le n° de la dernière ligne (en commençant de 0).
func CursorOffset(str string, p, w int) (px, py, l int) {
if len(str) == 0 {
return
}
var (
n, c int
)
for len(str) > 0 {
cleaned := NextUnescape(str)
if s, ok := cleaned.Get(); ok {
str = s
} else {
break
}
r, i := utf8.DecodeRuneInString(str)
str = str[i:]
if IsPrintable(r) {
if p == n {
px, py = c, l
}
n++
if r == '\n' {
c, l = 0, l+1
} else {
c++
if c == w {
c, l = 0, l+1
}
}
}
}
if p >= n {
px, py = c, l
}
return
}
// ClearBeginOfLine efface le début de la ligne, curseur compris
func ClearBeginOfLine() { fmt.Print("\033[1K") }
// ClearEndOfLine efface la fin de la ligne, curseur compris.
func ClearEndOfLine() { fmt.Print("\033[0K") }
// ClearLine efface toute la ligne.
func ClearLine() { fmt.Print("\033[2K") }
// ClearBeginOfScreen efface lécran jusquau curseur, curseur compris.
func ClearBeginOfScreen() { fmt.Print("\033[1J") }
// ClearBeginOfScreen efface lécran à partir du curseur, curseur compris.
func ClearEndOfScreen() { fmt.Print("\033[0J") }
// ClearAllScreen efface tout lécran.
func ClearAllScreen() { fmt.Print("\033[2J") }
// ClearAllScreenAndHistory efface tout lécran, ainsi que lhistorique de laffichage.
func ClearAllScreenAndHistory() { fmt.Print("\033[3J") }
// MoveUp déplace le curseur de c lignes vers le haut.
func MoveUp(c int) { fmt.Printf("\033[%dA", c) }
// MoveDown déplace le curseur de c lignes vers le bas.
func MoveDown(c int) { fmt.Printf("\033[%dB", c) }
// MoveLeft déplace le curseur de c colonnes vers la gauche.
func MoveLeft(c int) { fmt.Printf("\033[%dD", c) }
// MoveRight déplace le curseur de c colonnes vers la droite.
func MoveRight(c int) { fmt.Printf("\033[%dC", c) }
// MoveLineUp se déplace de c lignes vers le haut et revient en début de ligne.
func MoveLineUp(c int) { fmt.Printf("\033[%dF", c) }
// MoveLineDown se déplace de c lignes vers le bas et revient en début de ligne.
func MoveLineDown(c int) { fmt.Printf("\033[%dE", c) }
// MoveToColumn se déplace à la colonne c. La colonne commence à 1.
func MoveToColumn(c int) { fmt.Printf("\033[%dG", c) }
// MoveToScreen se déplace à la ligne l et la colonne c de lécran.
// l et c commencent à 1.
func MoveToScreen(l, c int) { fmt.Print("\033[%d;%dH", l, c) }
// MoveTopLeft se déplace au tout début de lécran
func MoveTopLeft() { MoveToScreen(1, 1) }
// SaveCursorPosition enregistre la position du curseur
// pour pouvoir y revenir plus tard.
func SaveCursorPosition() { fmt.Print("\033[s") }
// RestoreCursorPosition revient à la position précédemment enregistrée.
func RestoreCursorPosition() { fmt.Print("\033[u") }
// Beep émet un beep.
func Beep() { fmt.Print("\a") }
// CarriageReturn revient en début de ligne.
func CarriageReturn() { fmt.Print("\r") }
// NewLines rajoute c lignes.
func NewLines(c int) {
for c > 0 {
fmt.Print("\n")
c--
}
}

555
shell/console/atom/state.go Normal file
View file

@ -0,0 +1,555 @@
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()
if n > 0 {
NewLines(n)
MoveLineUp(n)
SaveCursorPosition()
}
ClearEndOfScreen()
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()
}
// Width retourne la largeur du terminal.
func (st *State) Width() int {
return st.dim.Width()
}
// 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()
}
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
}
// 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
}

View file

@ -0,0 +1,87 @@
package atom
import (
"os"
"strings"
"syscall"
"unsafe"
"gitea.zaclys.com/bvaudour/gob/option"
)
const (
cs8 = syscall.CS8
echo = syscall.ECHO
tcgets = syscall.TCGETS
tcsets = syscall.TCSETS
icanon = syscall.ICANON
icrnl = syscall.ICRNL
iexten = syscall.IEXTEN
inpck = syscall.INPCK
ioctl = syscall.SYS_IOCTL
isig = syscall.ISIG
istrip = syscall.ISTRIP
ixon = syscall.IXON
opost = syscall.OPOST
tiocgwinsz = syscall.TIOCGWINSZ
vmin = syscall.VMIN
vtime = syscall.VTIME
)
var (
stdin = syscall.Stdin
stdout = syscall.Stdout
)
type termios struct {
syscall.Termios
}
func (mode *termios) applyMode() (err option.Option[error]) {
_, _, errno := syscall.Syscall(ioctl, uintptr(stdin), tcsets, uintptr(unsafe.Pointer(mode)))
if errno != 0 {
return option.Some[error](errno)
}
return
}
func newTermios(handle int) option.Result[*termios] {
mode := new(termios)
_, _, errno := syscall.Syscall(ioctl, uintptr(handle), tcgets, uintptr(unsafe.Pointer(mode)))
if errno != 0 {
return option.Err[*termios](errno)
}
return option.Ok(mode)
}
func terminalSupported() bool {
bad := map[string]bool{"": true, "dumb": true, "cons25": true}
return !bad[strings.ToLower(os.Getenv("TERM"))]
}
type TerminalSize struct {
row, col uint16
xpixel, ypixel uint16
}
func (ts TerminalSize) Width() int { return int(ts.col) }
func (ts TerminalSize) Height() int { return int(ts.row) }
// GetTerminalSize retourne les dimensions actuelles du terminal.
func GetTerminalSize() (result option.Option[TerminalSize]) {
var ts TerminalSize
sc, _, _ := syscall.Syscall(ioctl, uintptr(stdout), tiocgwinsz, uintptr(unsafe.Pointer(&ts)))
if int(sc) >= 0 {
return option.Some(ts)
}
return
}
func checkOutput() (useCHA bool) {
// xterm is known to support CHA
useCHA = strings.Contains(strings.ToLower(os.Getenv("TERM")), "xterm")
return
}