mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-22 23:10:20 +09:00
Add much better autocompletion for commands
This commit is contained in:
@@ -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
89
cmd/micro/autocomplete.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user