-
Notifications
You must be signed in to change notification settings - Fork 4
/
cmdManager.go
218 lines (205 loc) · 5.53 KB
/
cmdManager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/stretto-editor/gocui"
)
// CmdHandler is the handler used for the action of a command
type CmdHandler func(g *gocui.Gui, cmd []string) error
// AutocompleteHandler is the handler called when autocompleting command arguments
type AutocompleteHandler func(prefix string, posArg int) string
// Command is the struct describing a command
type Command struct {
name string
action CmdHandler
minArg int
maxArg int
errMin error
autocomplete AutocompleteHandler
}
var commands map[string]*Command
func validateCmd(g *gocui.Gui, v *gocui.View) error {
var err error
if v.Name() != "cmdline" {
panic("Cmdline is not the current view")
}
cmdBuff := v.Buffer()
if cmdBuff == "" {
return nil
}
cmdBuff = cmdBuff[:len(cmdBuff)-1]
cmd := strings.Fields(cmdBuff)
if cmdCour := commands[cmd[0]]; cmdCour != nil {
nbArgs := len(cmd) - 1
if cmdCour.minArg > nbArgs {
err = cmdCour.errMin
} else if cmdCour.maxArg < nbArgs {
err = ErrUnexpectedArgument
} else {
err = cmdCour.action(g, cmd)
}
} else {
err = fmt.Errorf("unknown command : \"%s\"", cmd[0])
}
clearView(v)
if err == gocui.ErrQuit {
return err
}
if err != nil {
displayError(g, err)
}
return nil
}
//AutocompleteCmd autocomplete the current command input by completing the command itself or the argument
func AutocompleteCmd(g *gocui.Gui, v *gocui.View) error {
cmdBuff := v.Buffer()
if cmdBuff == "" {
return nil
}
ox, _ := v.Origin()
cx, _ := v.Cursor()
// the prefix ends at the cursor position
cmdBuff = cmdBuff[:ox+cx]
// if the prefix (i.e. the word behind the cursor) is not a space or nothing
if ox+cx == 0 || cmdBuff[ox+cx-1] == ' ' {
return nil
}
cmd := strings.Fields(cmdBuff)
posInWord := 0
posWord := 0
i := 0
/* the position of the cursor in the current word is needed to know exactly
* the prefix. The for loop determines the position of the word and
* the position of the cursor in the word
*/
for {
//if the current position is a space we go to the next position
if cmdBuff[posWord] == ' ' {
posWord++
continue
}
// here posWord is the position of the first letter of a word
// if the cursor is between the beginning and the end of the current word
// we found the word in which the prefix is, we can leave
if posWord+len(cmd[i])+1 >= ox+cx {
posInWord = ox + cx - posWord
break
}
//else we moove to the end of the word
posWord += len(cmd[i])
i++
}
//if the prefix is contained in the first word, this word is a command
if i == 0 {
if cmdName := GetAutocompleteCmd(cmd[0][:posInWord], i); cmdName != "" {
writeAutocomplete(v, cmd[0], cmdName)
}
return nil
}
command := commands[cmd[0]]
if command == nil || command.maxArg < i || command.autocomplete == nil {
return nil
}
if argumentName := command.autocomplete(cmd[i][:posInWord], i); argumentName != "" {
writeAutocomplete(v, cmd[i][:posInWord], argumentName)
}
return nil
}
func writeAutocomplete(v *gocui.View, prefix, word string) {
for i, c := range word {
if i >= len(prefix) {
v.EditWrite(c)
}
}
}
// GetAutocompleteCmd returns the command beginning by the prefix in argument
func GetAutocompleteCmd(prefix string, posArg int) string {
firstWord := true
output := ""
for cmd := range commands {
//if the current command begin with the prefix and the current
//command is not a shortcut from another word for the command
if strings.HasPrefix(cmd, prefix) && cmd == commands[cmd].name {
//if this is not the first command which can complete the prefix,
// it intersects the currentcommand and the previous prediction
if !firstWord {
output = intersectionString(output, cmd)
} else {
output = cmd
firstWord = false
}
}
}
return output
}
// GetAutocompleteFile returns the file beginning by the prefix in argument
func GetAutocompleteFile(prefix string, posArg int) string {
currentdir := "."
// if the filepath contains a directory
if index := strings.LastIndex(prefix, "/"); index != -1 {
currentdir = prefix[:index+1]
}
files, err := ioutil.ReadDir(currentdir)
if err != nil {
return ""
}
output := ""
firstWord := true
isDir := true
//used for the currentfilepath after
if currentdir == "." {
currentdir = ""
}
currentFilePath := ""
for _, file := range files {
currentFilePath = currentdir + file.Name()
if strings.HasPrefix(currentFilePath, prefix) {
//if this is not the first command which can complete the prefix,
// it intersects the currentcommand and the previous prediction
if !firstWord {
output = intersectionString(output, currentFilePath)
// if there is an intersection, the current prediction can't be a directory anymore
isDir = false
} else {
output = currentFilePath
firstWord = false
}
}
}
//if the autocompleted word is a directory, add a "/" to the completion
if isDir {
f, err := os.Open(output)
if err != nil {
return output
}
defer f.Close()
fi, err := f.Stat()
if err != nil || !fi.Mode().IsDir() {
return output
}
output += "/"
}
return output
}
// GetAutocompleteBoolean returns the boolean beginning by the prefix in argument
func GetAutocompleteBoolean(prefix string, posArg int) string {
if strings.HasPrefix("true", prefix) {
return "true"
}
if strings.HasPrefix("false", prefix) {
return "false"
}
return ""
}
func intersectionString(s1, s2 string) string {
if len(s1) > len(s2) {
s1, s2 = s2, s1
}
output := ""
for i := 0; i < len(s1) && s1[i] == s2[i]; i++ {
output += string(s1[i])
}
return output
}