]> git.lizzy.rs Git - micro.git/blob - internal/buffer/autocomplete.go
Add autoretab
[micro.git] / internal / buffer / autocomplete.go
1 package buffer
2
3 import (
4         "bytes"
5         "io/ioutil"
6         "os"
7         "sort"
8         "strings"
9
10         "github.com/zyedidia/micro/v2/internal/util"
11 )
12
13 // A Completer is a function that takes a buffer and returns info
14 // describing what autocompletions should be inserted at the current
15 // cursor location
16 // It returns a list of string suggestions which will be inserted at
17 // the current cursor location if selected as well as a list of
18 // suggestion names which can be displayed in an autocomplete box or
19 // other UI element
20 type Completer func(*Buffer) ([]string, []string)
21
22 func (b *Buffer) GetSuggestions() {
23
24 }
25
26 // Autocomplete starts the autocomplete process
27 func (b *Buffer) Autocomplete(c Completer) bool {
28         b.Completions, b.Suggestions = c(b)
29         if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {
30                 return false
31         }
32         b.CurSuggestion = -1
33         b.CycleAutocomplete(true)
34         return true
35 }
36
37 // CycleAutocomplete moves to the next suggestion
38 func (b *Buffer) CycleAutocomplete(forward bool) {
39         prevSuggestion := b.CurSuggestion
40
41         if forward {
42                 b.CurSuggestion++
43         } else {
44                 b.CurSuggestion--
45         }
46         if b.CurSuggestion >= len(b.Suggestions) {
47                 b.CurSuggestion = 0
48         } else if b.CurSuggestion < 0 {
49                 b.CurSuggestion = len(b.Suggestions) - 1
50         }
51
52         c := b.GetActiveCursor()
53         start := c.Loc
54         end := c.Loc
55         if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
56                 start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
57         }
58
59         b.Replace(start, end, b.Completions[b.CurSuggestion])
60         if len(b.Suggestions) > 1 {
61                 b.HasSuggestions = true
62         }
63 }
64
65 // GetWord gets the most recent word separated by any separator
66 // (whitespace, punctuation, any non alphanumeric character)
67 func GetWord(b *Buffer) ([]byte, int) {
68         c := b.GetActiveCursor()
69         l := b.LineBytes(c.Y)
70         l = util.SliceStart(l, c.X)
71
72         if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {
73                 return []byte{}, -1
74         }
75
76         if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) {
77                 return []byte{}, c.X
78         }
79
80         args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
81         input := args[len(args)-1]
82         return input, c.X - util.CharacterCount(input)
83 }
84
85 // GetArg gets the most recent word (separated by ' ' only)
86 func GetArg(b *Buffer) (string, int) {
87         c := b.GetActiveCursor()
88         l := b.LineBytes(c.Y)
89         l = util.SliceStart(l, c.X)
90
91         args := bytes.Split(l, []byte{' '})
92         input := string(args[len(args)-1])
93         argstart := 0
94         for i, a := range args {
95                 if i == len(args)-1 {
96                         break
97                 }
98                 argstart += util.CharacterCount(a) + 1
99         }
100
101         return input, argstart
102 }
103
104 // FileComplete autocompletes filenames
105 func FileComplete(b *Buffer) ([]string, []string) {
106         c := b.GetActiveCursor()
107         input, argstart := GetArg(b)
108
109         sep := string(os.PathSeparator)
110         dirs := strings.Split(input, sep)
111
112         var files []os.FileInfo
113         var err error
114         if len(dirs) > 1 {
115                 directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
116
117                 directories, _ = util.ReplaceHome(directories)
118                 files, err = ioutil.ReadDir(directories)
119         } else {
120                 files, err = ioutil.ReadDir(".")
121         }
122
123         if err != nil {
124                 return nil, nil
125         }
126
127         var suggestions []string
128         for _, f := range files {
129                 name := f.Name()
130                 if f.IsDir() {
131                         name += sep
132                 }
133                 if strings.HasPrefix(name, dirs[len(dirs)-1]) {
134                         suggestions = append(suggestions, name)
135                 }
136         }
137
138         sort.Strings(suggestions)
139         completions := make([]string, len(suggestions))
140         for i := range suggestions {
141                 var complete string
142                 if len(dirs) > 1 {
143                         complete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i]
144                 } else {
145                         complete = suggestions[i]
146                 }
147                 completions[i] = util.SliceEndStr(complete, c.X-argstart)
148         }
149
150         return completions, suggestions
151 }
152
153 // BufferComplete autocompletes based on previous words in the buffer
154 func BufferComplete(b *Buffer) ([]string, []string) {
155         c := b.GetActiveCursor()
156         input, argstart := GetWord(b)
157
158         if argstart == -1 {
159                 return []string{}, []string{}
160         }
161
162         inputLen := util.CharacterCount(input)
163
164         suggestionsSet := make(map[string]struct{})
165
166         var suggestions []string
167         for i := c.Y; i >= 0; i-- {
168                 l := b.LineBytes(i)
169                 words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
170                 for _, w := range words {
171                         if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
172                                 strw := string(w)
173                                 if _, ok := suggestionsSet[strw]; !ok {
174                                         suggestionsSet[strw] = struct{}{}
175                                         suggestions = append(suggestions, strw)
176                                 }
177                         }
178                 }
179         }
180         for i := c.Y + 1; i < b.LinesNum(); i++ {
181                 l := b.LineBytes(i)
182                 words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
183                 for _, w := range words {
184                         if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
185                                 strw := string(w)
186                                 if _, ok := suggestionsSet[strw]; !ok {
187                                         suggestionsSet[strw] = struct{}{}
188                                         suggestions = append(suggestions, strw)
189                                 }
190                         }
191                 }
192         }
193         if len(suggestions) > 1 {
194                 suggestions = append(suggestions, string(input))
195         }
196
197         completions := make([]string, len(suggestions))
198         for i := range suggestions {
199                 completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
200         }
201
202         return completions, suggestions
203 }