micro/internal/buffer/autocomplete.go

204 lines
4.9 KiB
Go
Raw Normal View History

2019-01-21 01:49:20 +03:00
package buffer
import (
"bytes"
"io/ioutil"
"os"
2019-01-25 06:10:57 +03:00
"sort"
2019-01-21 01:49:20 +03:00
"strings"
2020-05-04 17:16:15 +03:00
"github.com/zyedidia/micro/v2/internal/util"
2019-01-21 01:49:20 +03:00
)
2019-01-25 02:09:57 +03:00
// 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
2019-06-16 22:56:39 +03:00
// suggestion names which can be displayed in an autocomplete box or
2019-01-25 02:09:57 +03:00
// other UI element
type Completer func(*Buffer) ([]string, []string)
2019-01-21 01:49:20 +03:00
func (b *Buffer) GetSuggestions() {
}
2019-06-16 22:56:39 +03:00
// Autocomplete starts the autocomplete process
func (b *Buffer) Autocomplete(c Completer) bool {
2019-01-25 02:09:57 +03:00
b.Completions, b.Suggestions = c(b)
if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {
2019-06-16 22:56:39 +03:00
return false
2019-01-25 02:09:57 +03:00
}
b.CurSuggestion = -1
2019-01-25 06:10:57 +03:00
b.CycleAutocomplete(true)
2019-06-16 22:56:39 +03:00
return true
2019-01-25 02:09:57 +03:00
}
2019-06-16 22:56:39 +03:00
// CycleAutocomplete moves to the next suggestion
2019-01-25 06:10:57 +03:00
func (b *Buffer) CycleAutocomplete(forward bool) {
2019-01-25 02:09:57 +03:00
prevSuggestion := b.CurSuggestion
2019-01-25 06:10:57 +03:00
if forward {
b.CurSuggestion++
} else {
b.CurSuggestion--
}
if b.CurSuggestion >= len(b.Suggestions) {
2019-01-25 02:09:57 +03:00
b.CurSuggestion = 0
2019-01-25 06:10:57 +03:00
} else if b.CurSuggestion < 0 {
b.CurSuggestion = len(b.Suggestions) - 1
2019-01-25 02:09:57 +03:00
}
2019-01-21 01:49:20 +03:00
2019-01-25 02:09:57 +03:00
c := b.GetActiveCursor()
start := c.Loc
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
2020-05-20 23:47:08 +03:00
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
2019-01-25 02:09:57 +03:00
}
b.Replace(start, end, b.Completions[b.CurSuggestion])
2019-06-16 05:23:19 +03:00
if len(b.Suggestions) > 1 {
b.HasSuggestions = true
}
2019-01-21 01:49:20 +03:00
}
2019-06-16 22:56:39 +03:00
// GetWord gets the most recent word separated by any separator
// (whitespace, punctuation, any non alphanumeric character)
func (b *Buffer) GetWord() ([]byte, int) {
2019-06-16 22:56:39 +03:00
c := b.GetActiveCursor()
l := b.LineBytes(c.Y)
l = util.SliceStart(l, c.X)
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {
2019-06-16 22:56:39 +03:00
return []byte{}, -1
}
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) {
2019-06-16 22:56:39 +03:00
return []byte{}, c.X
}
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
2020-05-20 23:47:08 +03:00
return input, c.X - util.CharacterCount(input)
2019-06-16 22:56:39 +03:00
}
// GetArg gets the most recent word (separated by ' ' only)
func (b *Buffer) GetArg() (string, int) {
2019-01-21 01:49:20 +03:00
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
}
2020-05-20 23:47:08 +03:00
argstart += util.CharacterCount(a) + 1
2019-01-21 01:49:20 +03:00
}
2019-01-21 03:38:23 +03:00
return input, argstart
}
// FileComplete autocompletes filenames
2019-01-25 02:09:57 +03:00
func FileComplete(b *Buffer) ([]string, []string) {
2019-01-21 03:38:23 +03:00
c := b.GetActiveCursor()
input, argstart := b.GetArg()
2019-01-21 03:38:23 +03:00
2019-01-21 01:49:20 +03:00
sep := string(os.PathSeparator)
dirs := strings.Split(input, sep)
var files []os.FileInfo
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
directories, _ = util.ReplaceHome(directories)
files, err = ioutil.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
}
if err != nil {
2019-01-25 02:09:57 +03:00
return nil, nil
2019-01-21 01:49:20 +03:00
}
2019-01-25 02:09:57 +03:00
var suggestions []string
2019-01-21 01:49:20 +03:00
for _, f := range files {
name := f.Name()
if f.IsDir() {
name += sep
}
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
suggestions = append(suggestions, name)
}
}
2019-01-25 06:10:57 +03:00
sort.Strings(suggestions)
2019-01-25 02:09:57 +03:00
completions := make([]string, len(suggestions))
for i := range suggestions {
var complete string
2019-01-21 01:49:20 +03:00
if len(dirs) > 1 {
2019-01-25 02:09:57 +03:00
complete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i]
2019-01-21 01:49:20 +03:00
} else {
2019-01-25 02:09:57 +03:00
complete = suggestions[i]
2019-01-21 01:49:20 +03:00
}
2019-01-25 02:09:57 +03:00
completions[i] = util.SliceEndStr(complete, c.X-argstart)
2019-01-21 01:49:20 +03:00
}
2019-01-25 02:09:57 +03:00
return completions, suggestions
2019-01-21 01:49:20 +03:00
}
2019-06-16 22:56:39 +03:00
// BufferComplete autocompletes based on previous words in the buffer
func BufferComplete(b *Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := b.GetWord()
2019-06-16 22:56:39 +03:00
if argstart == -1 {
return []string{}, []string{}
}
2020-05-20 23:47:08 +03:00
inputLen := util.CharacterCount(input)
2019-06-16 22:56:39 +03:00
suggestionsSet := make(map[string]struct{})
var suggestions []string
for i := c.Y; i >= 0; i-- {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
2020-05-20 23:47:08 +03:00
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
2019-06-16 22:56:39 +03:00
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.IsNonAlphaNumeric)
for _, w := range words {
2020-05-20 23:47:08 +03:00
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
2019-06-16 22:56:39 +03:00
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
}