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