Add much better autocompletion for commands

This commit is contained in:
Zachary Yedidia
2016-08-17 13:49:37 -04:00
parent 932370f597
commit 8a58506c72
6 changed files with 160 additions and 61 deletions

View File

@@ -1119,7 +1119,7 @@ func (v *View) CommandMode() bool {
return false
}
input, canceled := messenger.Prompt("> ", "Command", FileCompletion)
input, canceled := messenger.Prompt("> ", "Command", CommandCompletion)
if !canceled {
HandleCommand(input)
PostActionCall("CommandMode")

89
cmd/micro/autocomplete.go Normal file
View File

@@ -0,0 +1,89 @@
package main
import (
"io/ioutil"
"os"
"strings"
)
func FileComplete(input string) (string, []string) {
dirs := strings.Split(input, "/")
var files []os.FileInfo
var err error
if len(dirs) > 1 {
files, err = ioutil.ReadDir(strings.Join(dirs[:len(dirs)-1], "/"))
} else {
files, err = ioutil.ReadDir(".")
}
var suggestions []string
if err != nil {
return "", suggestions
}
for _, f := range files {
name := f.Name()
if f.IsDir() {
name += "/"
}
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
suggestions = append(suggestions, name)
}
}
var chosen string
if len(suggestions) == 1 {
if len(dirs) > 1 {
chosen = strings.Join(dirs[:len(dirs)-1], "/") + "/" + suggestions[0]
} else {
chosen = suggestions[0]
}
}
return chosen, suggestions
}
func CommandComplete(input string) (string, []string) {
var suggestions []string
for cmd := range commands {
if strings.HasPrefix(cmd, input) {
suggestions = append(suggestions, cmd)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func HelpComplete(input string) (string, []string) {
var suggestions []string
for _, topic := range helpFiles {
if strings.HasPrefix(topic, input) {
suggestions = append(suggestions, topic)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}
func OptionComplete(input string) (string, []string) {
var suggestions []string
for option := range settings {
if strings.HasPrefix(option, input) {
suggestions = append(suggestions, option)
}
}
var chosen string
if len(suggestions) == 1 {
chosen = suggestions[0]
}
return chosen, suggestions
}

View File

@@ -12,7 +12,17 @@ import (
"github.com/mitchellh/go-homedir"
)
var commands map[string]func([]string)
type Command struct {
action func([]string)
completions []Completion
}
type StrCommand struct {
action string
completions []Completion
}
var commands map[string]Command
var commandActions = map[string]func([]string){
"Set": Set,
@@ -29,21 +39,21 @@ var commandActions = map[string]func([]string){
// InitCommands initializes the default commands
func InitCommands() {
commands = make(map[string]func([]string))
commands = make(map[string]Command)
defaults := DefaultCommands()
parseCommands(defaults)
}
func parseCommands(userCommands map[string]string) {
func parseCommands(userCommands map[string]StrCommand) {
for k, v := range userCommands {
MakeCommand(k, v)
MakeCommand(k, v.action, v.completions...)
}
}
// MakeCommand is a function to easily create new commands
// This can be called by plugins in Lua so that plugins can define their own commands
func MakeCommand(name, function string) {
func MakeCommand(name, function string, completions ...Completion) {
action := commandActions[function]
if _, ok := commandActions[function]; !ok {
// If the user seems to be binding a function that doesn't exist
@@ -51,22 +61,22 @@ func MakeCommand(name, function string) {
action = LuaFunctionCommand(function)
}
commands[name] = action
commands[name] = Command{action, completions}
}
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]string {
return map[string]string{
"set": "Set",
"bind": "Bind",
"run": "Run",
"quit": "Quit",
"save": "Save",
"replace": "Replace",
"vsplit": "VSplit",
"hsplit": "HSplit",
"tab": "Tab",
"help": "Help",
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": StrCommand{"Set", []Completion{OptionCompletion, NoCompletion}},
"bind": StrCommand{"Bind", []Completion{NoCompletion}},
"run": StrCommand{"Run", []Completion{NoCompletion}},
"quit": StrCommand{"Quit", []Completion{NoCompletion}},
"save": StrCommand{"Save", []Completion{NoCompletion}},
"replace": StrCommand{"Replace", []Completion{NoCompletion}},
"vsplit": StrCommand{"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": StrCommand{"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": StrCommand{"Tab", []Completion{FileCompletion, NoCompletion}},
"help": StrCommand{"Help", []Completion{HelpCompletion, NoCompletion}},
}
}
@@ -129,7 +139,7 @@ func HSplit(args []string) {
}
}
// Tab opens the given file in a new tab
// NewTab opens the given file in a new tab
func NewTab(args []string) {
if len(args) == 0 {
CurView().AddTab()
@@ -370,6 +380,6 @@ func HandleCommand(input string) {
if _, ok := commands[inputCmd]; !ok {
messenger.Error("Unkown command ", inputCmd)
} else {
commands[inputCmd](args)
commands[inputCmd].action(args)
}
}

View File

@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
@@ -129,11 +128,14 @@ type Completion int
const (
NoCompletion Completion = iota
FileCompletion
CommandCompletion
HelpCompletion
OptionCompletion
)
// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, historyType string, completionType Completion) (string, bool) {
func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
m.hasPrompt = true
m.Message(prompt)
if _, ok := m.history[historyType]; !ok {
@@ -163,42 +165,40 @@ func (m *Messenger) Prompt(prompt, historyType string, completionType Completion
response, canceled = m.response, false
m.history[historyType][len(m.history[historyType])-1] = response
case tcell.KeyTab:
args := strings.Split(m.response, " ")
currentArgNum := len(args) - 1
currentArg := args[currentArgNum]
var completionType Completion
if completionTypes[0] == CommandCompletion && len(completionTypes) == 1 && currentArgNum > 0 {
if command, ok := commands[args[0]]; ok {
completionTypes = append(completionTypes, command.completions...)
}
}
if currentArgNum >= len(completionTypes) {
completionType = completionTypes[len(completionTypes)-1]
} else {
completionType = completionTypes[currentArgNum]
}
var chosen string
if completionType == FileCompletion {
args := strings.Split(m.response, " ")
currentArg := args[len(args)-1]
dirs := strings.Split(currentArg, "/")
var files []os.FileInfo
var err error
if len(dirs) > 1 {
files, err = ioutil.ReadDir(strings.Join(dirs[:len(dirs)-1], "/"))
} else {
files, err = ioutil.ReadDir(".")
}
if err != nil {
continue
}
var suggestions []string
for _, f := range files {
name := f.Name()
if f.IsDir() {
name += "/"
}
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
suggestions = append(suggestions, name)
}
}
if len(suggestions) == 1 {
if len(dirs) > 1 {
currentArg = strings.Join(dirs[:len(dirs)-1], "/") + "/" + suggestions[0]
} else {
currentArg = suggestions[0]
}
if len(args) > 1 {
currentArg = " " + currentArg
}
m.response = strings.Join(args[:len(args)-1], " ") + currentArg
m.cursorx = Count(m.response)
chosen, _ = FileComplete(currentArg)
} else if completionType == CommandCompletion {
chosen, _ = CommandComplete(currentArg)
} else if completionType == HelpCompletion {
chosen, _ = HelpComplete(currentArg)
} else if completionType == OptionCompletion {
chosen, _ = OptionComplete(currentArg)
}
if chosen != "" {
if len(args) > 1 {
chosen = " " + chosen
}
m.response = strings.Join(args[:len(args)-1], " ") + chosen
m.cursorx = Count(m.response)
}
}
}

View File

@@ -431,7 +431,7 @@ func runtimePluginsAutocloseAutocloseLua() (*asset, error) {
return a, nil
}
var _runtimePluginsGoGoLua = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xa4\x52\x5d\x8b\xd4\x30\x14\x7d\xef\xaf\x38\x04\x84\x44\xbb\xfd\x01\x85\x3e\xe8\x82\xfb\x24\x2b\xba\xf8\x22\x0a\xb1\xbd\x6d\x83\x69\x52\x92\xdb\x1d\x45\xfc\xef\xd2\x4e\xb6\xd3\xe9\x8c\x8a\x98\x97\x42\x72\xee\xf9\xb8\x3d\xa6\xc5\x1d\xf1\xfd\xc8\xc6\x3b\x29\x3a\x6f\x86\xd1\x07\x8e\x42\xa1\xaa\xe0\x8c\x05\xf7\xe4\x32\x00\x78\xd9\x34\x97\xb0\x1c\xad\xb6\x91\x54\x46\xae\xc9\xf6\x5c\xed\xc0\x7f\xe3\x99\x21\x39\x38\x4c\x89\x22\x7b\xa3\xbf\xd2\xad\x1f\x06\xed\x9a\x73\x1d\xd1\xf9\x22\xea\x47\x12\x6a\x8f\x39\x72\x6c\xde\xb3\x76\x72\xf5\xac\x00\xef\xde\xeb\x47\x92\x6a\x11\x36\x2d\x6e\xa7\xf0\xc1\xd0\x41\xaa\xe2\xd5\xd4\x16\xaf\x8d\xa5\x87\xef\x23\xcd\x16\xc5\x9d\x17\x27\x8f\x09\xfe\x9b\xcd\x9c\xc1\xe6\xb3\x3e\x26\xa5\xf9\x90\x8d\x74\x7d\x21\x57\xc6\xdb\x81\xb7\xa3\xae\xc9\x9e\xbe\xcb\x52\xd6\x3c\x5b\xa4\xf5\xb5\xb6\xe8\xb5\x6b\x2c\xa1\x82\xf1\xc5\xe8\x47\x7a\xd2\xc1\xcd\x01\x02\x45\xb1\x8b\xfc\x56\x73\xbf\x1d\x0f\x14\x27\xcb\xa8\x12\x4f\x19\x48\x37\x52\x3c\xd7\xe2\x08\x4a\xb7\xb5\xf5\x71\xde\xe2\x72\xb7\x12\x96\xef\xe8\x7e\x16\x54\x17\x26\xcf\xb7\xf1\x07\xa3\x09\xf8\xaf\x66\xe3\x68\x0d\xcb\x0b\xcb\x39\x44\xf9\x3f\xc6\xe3\xa9\x2b\x37\x37\x78\xe8\x4d\xc4\xc1\x58\x0b\x0e\xa6\xeb\x28\xa4\x36\x41\xbb\x06\xb5\x9e\x22\x1d\x7f\x07\x7c\x38\x45\x06\x7b\x84\xc9\xed\xf4\x52\x09\x77\x6a\x4b\x88\xc8\x21\x47\xa4\xf1\x6a\xce\x1f\x3f\xcf\x6e\x3b\xfa\x86\x0a\x52\xc8\x8f\x9f\x9f\xc5\x4f\x2f\x94\x50\x65\xeb\xc3\xa0\x59\xae\x04\xad\x0f\x20\x5d\xf7\x30\x0e\x91\x43\xd9\x0d\x9a\xeb\x5e\x2e\xb3\x0a\x8d\x5f\x3b\xc6\xfa\x8b\xa5\xc2\xb8\x48\x81\xe5\x51\x30\x5f\x26\x55\xb6\xad\x60\x20\x9e\x82\x4b\x8e\x96\x00\xbf\x02\x00\x00\xff\xff\x4f\x90\xa7\x78\x31\x04\x00\x00")
var _runtimePluginsGoGoLua = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xa4\x52\x5d\x8b\xd4\x30\x14\x7d\xef\xaf\x38\x04\x84\x44\x3b\xc5\xe7\x81\x3e\xe8\x82\xfb\x24\x2b\xba\xf8\x22\x0a\xb1\xbd\x6d\x83\x69\x52\x92\xdb\x1d\x45\xfc\xef\xd2\x4e\xb6\x33\xed\x8c\x8a\x6c\x5e\x06\xee\x9c\x7b\x3e\x6e\x8f\x69\x70\x4b\x7c\x37\xb0\xf1\x4e\x8a\xd6\x9b\x7e\xf0\x81\xa3\x50\x28\x4b\x38\x63\xc1\x1d\xb9\x0c\x00\x5e\xd5\xf5\x25\x2c\x47\xa3\x6d\x24\x95\x91\xab\xb3\x2d\x57\xd3\xf3\xbf\x78\x26\x48\x0e\x0e\x63\xa2\xc8\xde\xea\x6f\x74\xe3\xfb\x5e\xbb\x7a\xad\x23\x5a\x5f\x44\xfd\x40\x22\xc7\x4b\xb5\x85\x1d\x69\xd6\x90\xac\x19\x5d\x35\xe9\xc0\xbb\x0f\xfa\x81\xa4\x9a\xe5\x4d\x83\x9b\x31\x7c\x34\x74\x90\xaa\x78\x3d\x36\xc5\x1b\x63\xe9\xfe\xc7\x40\x93\x51\x71\xeb\xc5\xc9\x69\x82\xff\xe1\x3e\x2b\xd8\xf4\x96\x3f\x93\xd2\xf4\xc8\x46\xba\x7e\x96\x2b\xeb\x4d\xcf\xe7\xab\xae\xce\x1e\x7f\xe7\xd3\x2c\x79\xce\x91\xd6\x57\xda\xa2\xd3\xae\xb6\x84\x12\xc6\x17\x83\x1f\xe8\x51\x07\xbb\x03\x04\x8a\x62\x13\xf9\x9d\xe6\xee\x7c\x3d\x50\x1c\x2d\xa3\x4c\x3c\xfb\x40\xba\x96\xe2\xb9\x16\x47\x50\x9a\x56\xd6\xc7\xe9\x8a\xf3\x6c\x21\xdc\xbf\xa7\xbb\x49\x50\x5d\x98\x5c\x5f\xe3\x2f\x46\x13\xf0\x7f\xcd\xc6\xc1\x1a\x96\x17\x96\x73\x88\xfd\x53\x8c\xc7\x53\x57\x76\x3b\xdc\x77\x26\xe2\x60\xac\x05\x07\xd3\xb6\x14\x52\x9b\xa0\x5d\x8d\x4a\x8f\x91\x8e\x9f\x03\x3e\x9c\x22\x83\x3d\xc2\xe8\x36\x7a\xa9\x84\x1b\xb5\x39\x44\xe4\x90\x23\xd2\x70\x35\xe7\xcf\x5f\xab\x69\x4b\xdf\x51\x42\x0a\xf9\xe9\xcb\xb3\xf8\xf9\x85\x12\x6a\xdf\xf8\xd0\x6b\x96\x0b\x41\xe3\x03\x48\x57\x1d\x8c\x43\xe4\xb0\x6f\x7b\xcd\x55\x27\xe7\x5d\x85\xda\x2f\x1d\x63\xfd\xd5\x52\x61\x5c\xa4\xc0\xf2\x28\x98\xcf\x9b\x2a\x3b\xaf\x60\x20\x1e\x83\x4b\x8e\xe6\x00\xbf\x03\x00\x00\xff\xff\xe8\x9d\x9e\x71\x37\x04\x00\x00")
func runtimePluginsGoGoLuaBytes() ([]byte, error) {
return bindataRead(
@@ -446,7 +446,7 @@ func runtimePluginsGoGoLua() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "runtime/plugins/go/go.lua", size: 1073, mode: os.FileMode(420), modTime: time.Unix(1471450014, 0)}
info := bindataFileInfo{name: "runtime/plugins/go/go.lua", size: 1079, mode: os.FileMode(420), modTime: time.Unix(1471455442, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}

View File

@@ -5,8 +5,8 @@ if GetOption("gofmt") == nil then
AddOption("gofmt", true)
end
MakeCommand("goimports", "go.save")
MakeCommand("gofmt", "go.save")
MakeCommand("goimports", "go.save", 0)
MakeCommand("gofmt", "go.save", 0)
function onSave()
if CurView().Buf.FileType == "Go" then