Something went wrong. Try again.
Fork of micro-editor/micro
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240package buffer
import ( "bytes" "io/fs" "os" "sort" "strings"
"github.com/micro-editor/micro/v2/internal/util")
// A Completer is a function that takes a buffer and returns info// describing what autocompletions should be inserted at the current// cursor location// It returns a list of string suggestions which will be inserted at// the current cursor location if selected as well as a list of// suggestion names which can be displayed in an autocomplete box or// other UI elementtype Completer func(*Buffer) ([]string, []string)
func (b *Buffer) GetSuggestions() {
}
// Autocomplete starts the autocomplete processfunc (b *Buffer) Autocomplete(c Completer) bool { return b.AutocompleteCB(c, nil)}
func (b *Buffer) AutocompleteCB(c Completer, checkCB func(*Cursor) bool) bool { if checkCB != nil && !checkCB(b.GetActiveCursor()) { return false } b.Completions, b.Suggestions = c(b)
if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 { return false } b.CurSuggestion = -1 b.CycleAutocompleteCB(true, checkCB) return true}
// CycleAutocomplete moves to the next suggestionfunc (b *Buffer) CycleAutocomplete(forward bool) { b.CycleAutocompleteCB(forward, nil)}
func (b *Buffer) CycleAutocompleteCB(forward bool, checkCB func(*Cursor) bool) { prevSuggestion := b.CurSuggestion
if forward { b.CurSuggestion++ } else { b.CurSuggestion-- } if b.CurSuggestion >= len(b.Suggestions) { b.CurSuggestion = 0 } else if b.CurSuggestion < 0 { b.CurSuggestion = len(b.Suggestions) - 1 }
tmpWord, _ := b.GetWord() activeWord := make([]byte, len(tmpWord)) copy(activeWord, tmpWord) for _, c := range b.cursors { if checkCB != nil && !checkCB(c) { continue }
word, _ := c.GetWord() if !bytes.Equal(word, activeWord) { continue }
c.autocomplete(prevSuggestion) }
if len(b.Suggestions) > 1 { b.HasSuggestions = true }}
func (c *Cursor) autocomplete(prevSuggestion int) { b := c.buf start := c.Loc end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b) }
b.Replace(start, end, b.Completions[b.CurSuggestion])}
// GetWord gets the most recent word separated by any separator// (whitespace, punctuation, any non alphanumeric character)func (c *Cursor) GetWord() ([]byte, int) { b := c.buf l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X)
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) { return []byte{}, -1 }
if util.IsNonWordChar(b.RuneAt(c.Loc.Move(-1, b))) { return []byte{}, c.X }
args := bytes.FieldsFunc(l, util.IsNonWordChar) input := args[len(args)-1] return input, c.X - util.CharacterCount(input)}
func (b *Buffer) GetWord() ([]byte, int) { return b.GetActiveCursor().GetWord()}
// GetArg gets the most recent word (separated by ' ' only)func (b *Buffer) GetArg() (string, int) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X)
args := bytes.Split(l, []byte{' '}) input := string(args[len(args)-1]) argstart := 0 for i, a := range args { if i == len(args)-1 { break } argstart += util.CharacterCount(a) + 1 }
return input, argstart}
// FileComplete autocompletes filenamesfunc FileComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := b.GetArg()
sep := string(os.PathSeparator) dirs := strings.Split(input, sep)
var files []fs.DirEntry var err error if len(dirs) > 1 { directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories) files, err = os.ReadDir(directories) } else { files, err = os.ReadDir(".") }
if err != nil { return nil, nil }
var suggestions []string for _, f := range files { name := f.Name() if f.IsDir() { name += sep } if strings.HasPrefix(name, dirs[len(dirs)-1]) { suggestions = append(suggestions, name) } }
sort.Strings(suggestions) completions := make([]string, len(suggestions)) for i := range suggestions { var complete string if len(dirs) > 1 { complete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i] } else { complete = suggestions[i] } completions[i] = util.SliceEndStr(complete, c.X-argstart) }
return completions, suggestions}
// BufferComplete autocompletes based on previous words in the bufferfunc BufferComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := c.GetWord()
if argstart == -1 { return []string{}, []string{} }
inputLen := util.CharacterCount(input)
suggestionsSet := make(map[string]struct{})
var suggestions []string for i := c.Y; i >= 0; i-- { l := b.LineBytes(i) words := bytes.FieldsFunc(l, util.IsNonWordChar) for _, w := range words { if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen { strw := string(w) if _, ok := suggestionsSet[strw]; !ok { suggestionsSet[strw] = struct{}{} suggestions = append(suggestions, strw) } } } } for i := c.Y + 1; i < b.LinesNum(); i++ { l := b.LineBytes(i) words := bytes.FieldsFunc(l, util.IsNonWordChar) for _, w := range words { if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen { strw := string(w) if _, ok := suggestionsSet[strw]; !ok { suggestionsSet[strw] = struct{}{} suggestions = append(suggestions, strw) } } } } if len(suggestions) > 1 { suggestions = append(suggestions, string(input)) }
completions := make([]string, len(suggestions)) for i := range suggestions { completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) }
return completions, suggestions}