Module atom: correction + ajout de méthodes pour Buffer et State; Module scanner: ajout d’une fonction pour scanner les champs de manière brute
This commit is contained in:
parent
974a160bfb
commit
3307dec97c
|
@ -305,3 +305,11 @@ func (b *Buffer) Clone() *Buffer {
|
||||||
|
|
||||||
return &cb
|
return &cb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) SetCursor(cursor int) (ok bool) {
|
||||||
|
if ok = cursor > 0 && cursor <= b.Len() && cursor != b.cursor; ok {
|
||||||
|
b.cursor = cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func CheckUnicode(str string) (err option.Option[error]) {
|
||||||
if i == len(runes)-1 || runes[i+1] != '[' {
|
if i == len(runes)-1 || runes[i+1] != '[' {
|
||||||
return option.Some(ErrInvalidUnicode)
|
return option.Some(ErrInvalidUnicode)
|
||||||
}
|
}
|
||||||
} else if unicode.Is(unicode.C, r) {
|
} else if unicode.Is(unicode.C, r) && r != '\n' {
|
||||||
return option.Some(ErrInvalidUnicode)
|
return option.Some(ErrInvalidUnicode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,7 @@ func (st *State) Print(str string, cursor int) (err option.Option[error]) {
|
||||||
px, py, n := CursorOffset(str, cursor+VisibleWidth(st.prompt), st.dim.Width())
|
px, py, n := CursorOffset(str, cursor+VisibleWidth(st.prompt), st.dim.Width())
|
||||||
|
|
||||||
RestoreCursorPosition()
|
RestoreCursorPosition()
|
||||||
|
ClearEndOfScreen()
|
||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
NewLines(n)
|
NewLines(n)
|
||||||
|
@ -137,7 +138,6 @@ func (st *State) Print(str string, cursor int) (err option.Option[error]) {
|
||||||
SaveCursorPosition()
|
SaveCursorPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearEndOfScreen()
|
|
||||||
fmt.Print(str)
|
fmt.Print(str)
|
||||||
RestoreCursorPosition()
|
RestoreCursorPosition()
|
||||||
|
|
||||||
|
@ -295,11 +295,25 @@ func (st *State) BufferLen() int {
|
||||||
return st.buffer.Len()
|
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.
|
// Width retourne la largeur du terminal.
|
||||||
func (st *State) Width() int {
|
func (st *State) Width() int {
|
||||||
return st.dim.Width()
|
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
|
// ToggleReplace se place en mode remplacement si on était on mode insertion
|
||||||
// et vice-versa.
|
// et vice-versa.
|
||||||
func (st *State) ToggleReplace() {
|
func (st *State) ToggleReplace() {
|
||||||
|
@ -355,6 +369,11 @@ func (st *State) NextWord() bool {
|
||||||
return st.buffer.NextWord()
|
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) {
|
func (st *State) rem(cb func() option.Option[string]) (ok bool) {
|
||||||
var yank string
|
var yank string
|
||||||
if yank, ok = cb().Get(); ok {
|
if yank, ok = cb().Get(); ok {
|
||||||
|
@ -480,6 +499,79 @@ func (st *State) HistoryNext() (ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProposal retourne une suggestion de saisie pour la saisie en cours
|
||||||
|
// basée sur l’historique.
|
||||||
|
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 l’historique de recherche.
|
||||||
|
// L’historique 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 l’historique de recherche.
|
||||||
|
// L’historique 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 l’historique de recherche.
|
// SearchHistoryNext remonte dans l’historique de recherche.
|
||||||
// L’historique de recherche est initialisée avec le préfixe du buffer.
|
// L’historique de recherche est initialisée avec le préfixe du buffer.
|
||||||
func (st *State) SearchHistoryNext(insensitive ...bool) (ok bool) {
|
func (st *State) SearchHistoryNext(insensitive ...bool) (ok bool) {
|
||||||
|
@ -553,3 +645,8 @@ func (st *State) Cancel() (ok bool) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPrompt retourne le prompt configuré.
|
||||||
|
func (st *State) GetPrompt() string {
|
||||||
|
return st.prompt
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ type tk struct {
|
||||||
quote bool
|
quote bool
|
||||||
escape bool
|
escape bool
|
||||||
spaces []rune
|
spaces []rune
|
||||||
|
raw bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runeToBytes(r rune) []byte {
|
func runeToBytes(r rune) []byte {
|
||||||
|
@ -27,6 +28,64 @@ func runeToBytes(r rune) []byte {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t tk) spl(q, e, s collection.Set[rune], start int, data []byte) (advance int, token []byte, esc, quote rune, done bool) {
|
||||||
|
// Scan until space, marking end of word.
|
||||||
|
for width, i := 0, start; i < len(data); i += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[i:])
|
||||||
|
if s.Contains(r) && !q.Contains(quote) && !e.Contains(esc) {
|
||||||
|
return i + width, token, esc, quote, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Contains(esc) {
|
||||||
|
if q.Contains(quote) && !e.Contains(r) && r != quote {
|
||||||
|
token = append(token, runeToBytes(esc)...)
|
||||||
|
}
|
||||||
|
token = append(token, runeToBytes(r)...)
|
||||||
|
esc = 0
|
||||||
|
} else if e.Contains(r) {
|
||||||
|
esc = r
|
||||||
|
} else if q.Contains(r) {
|
||||||
|
if !q.Contains(quote) {
|
||||||
|
quote = r
|
||||||
|
} else if quote == r {
|
||||||
|
quote = 0
|
||||||
|
} else {
|
||||||
|
token = append(token, runeToBytes(r)...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = append(token, runeToBytes(r)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t tk) rawSpl(q, e, s collection.Set[rune], start int, data []byte) (advance int, token []byte, esc, quote rune, done bool) {
|
||||||
|
// Scan until space, marking end of word.
|
||||||
|
for width, i := 0, start; i < len(data); i += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[i:])
|
||||||
|
if s.Contains(r) && !q.Contains(quote) && !e.Contains(esc) {
|
||||||
|
return i + width, token, esc, quote, true
|
||||||
|
}
|
||||||
|
token = append(token, runeToBytes(r)...)
|
||||||
|
if e.Contains(esc) {
|
||||||
|
esc = 0
|
||||||
|
} else if e.Contains(r) {
|
||||||
|
esc = r
|
||||||
|
} else if q.Contains(quote) {
|
||||||
|
if quote == r {
|
||||||
|
quote = 0
|
||||||
|
}
|
||||||
|
} else if q.Contains(r) {
|
||||||
|
quote = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (t tk) Split(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
func (t tk) Split(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
q, e := collection.NewSet[rune](), collection.NewSet[rune]()
|
q, e := collection.NewSet[rune](), collection.NewSet[rune]()
|
||||||
s := collection.NewSet(t.spaces...)
|
s := collection.NewSet(t.spaces...)
|
||||||
|
@ -47,35 +106,19 @@ func (t tk) Split(data []byte, atEOF bool) (advance int, token []byte, err error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var quote, esc rune
|
|
||||||
|
|
||||||
// Scan until space, marking end of word.
|
// Scan until space, marking end of word.
|
||||||
for width, i := 0, start; i < len(data); i += width {
|
var ok bool
|
||||||
var r rune
|
var esc, quote rune
|
||||||
r, width = utf8.DecodeRune(data[i:])
|
if t.raw {
|
||||||
if s.Contains(r) && !q.Contains(quote) && !e.Contains(esc) {
|
advance, token, esc, quote, ok = t.rawSpl(q, e, s, start, data)
|
||||||
return i + width, token, nil
|
} else {
|
||||||
}
|
advance, token, esc, quote, ok = t.spl(q, e, s, start, data)
|
||||||
if e.Contains(esc) {
|
|
||||||
if q.Contains(quote) && !e.Contains(r) && r != quote {
|
|
||||||
token = append(token, runeToBytes(esc)...)
|
|
||||||
}
|
|
||||||
token = append(token, runeToBytes(r)...)
|
|
||||||
esc = 0
|
|
||||||
} else if e.Contains(r) {
|
|
||||||
esc = r
|
|
||||||
} else if q.Contains(r) {
|
|
||||||
if !q.Contains(quote) {
|
|
||||||
quote = r
|
|
||||||
} else if quote == r {
|
|
||||||
quote = 0
|
|
||||||
} else {
|
|
||||||
token = append(token, runeToBytes(r)...)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
token = append(token, runeToBytes(r)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
||||||
if atEOF && len(data) > start {
|
if atEOF && len(data) > start {
|
||||||
if e.Contains(esc) || q.Contains(quote) {
|
if e.Contains(esc) || q.Contains(quote) {
|
||||||
|
@ -97,8 +140,8 @@ func (t tk) Split(data []byte, atEOF bool) (advance int, token []byte, err error
|
||||||
//
|
//
|
||||||
// Le résultat va être décomposé en 3 éléments :
|
// Le résultat va être décomposé en 3 éléments :
|
||||||
// - unmot
|
// - unmot
|
||||||
// - une\ phrase
|
// - une phrase
|
||||||
// - "une deuxième\" phrase"
|
// - une deuxième phrase
|
||||||
func NewTokenizer(quote, escape bool, spaces ...rune) Tokenizer {
|
func NewTokenizer(quote, escape bool, spaces ...rune) Tokenizer {
|
||||||
if len(spaces) == 0 {
|
if len(spaces) == 0 {
|
||||||
spaces = []rune(" \t\n\v\f\r")
|
spaces = []rune(" \t\n\v\f\r")
|
||||||
|
@ -110,6 +153,30 @@ func NewTokenizer(quote, escape bool, spaces ...rune) Tokenizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRawTokenizer retourne un tokenizer d’arguments de ligne de commande.
|
||||||
|
//
|
||||||
|
// Le split s’effectue au niveau des caractères spaces fourni (tous les espaces
|
||||||
|
// si aucun de fourni) sauf si ces caractères sont échappés
|
||||||
|
// (si escape) ou entre guillemets (si quote)
|
||||||
|
// Par exemple, prenons la chaîne suivante :
|
||||||
|
// unmot une\ phrase "une deuxième\" phrase"
|
||||||
|
//
|
||||||
|
// Le résultat va être décomposé en 3 éléments :
|
||||||
|
// - unmot
|
||||||
|
// - une\ phrase
|
||||||
|
// - "une deuxième\" phrase"
|
||||||
|
func NewRawTokenizer(quote, escape bool, spaces ...rune) Tokenizer {
|
||||||
|
if len(spaces) == 0 {
|
||||||
|
spaces = []rune(" \t\n\v\f\r")
|
||||||
|
}
|
||||||
|
return tk{
|
||||||
|
quote: quote,
|
||||||
|
escape: escape,
|
||||||
|
spaces: spaces,
|
||||||
|
raw: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewScanner retourne un nouveau scanner utilisant le tokenizer spécifié
|
// NewScanner retourne un nouveau scanner utilisant le tokenizer spécifié
|
||||||
// pour la fonction de splitage (NewTokenizer(true, true) si aucun tokenizer fourni).
|
// pour la fonction de splitage (NewTokenizer(true, true) si aucun tokenizer fourni).
|
||||||
func NewScanner(r io.Reader, t ...Tokenizer) *bufio.Scanner {
|
func NewScanner(r io.Reader, t ...Tokenizer) *bufio.Scanner {
|
||||||
|
@ -123,3 +190,8 @@ func NewScanner(r io.Reader, t ...Tokenizer) *bufio.Scanner {
|
||||||
sc.Split(s)
|
sc.Split(s)
|
||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRawScanner retourne un scanner utilisant RawTokenizer avec détection des quotes et de l’échappement.
|
||||||
|
func NewRawScanner(r io.Reader) *bufio.Scanner {
|
||||||
|
return NewScanner(r, NewRawTokenizer(true, true))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue