From 0e4faf108d0549cf6498268ca852a14c8cf17cee Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 24 Jan 2019 18:09:57 -0500 Subject: [PATCH] Finish autocomplete --- cmd/micro/action/infocomplete.go | 47 ++++++++++++------------- cmd/micro/action/infopane.go | 18 ++++------ cmd/micro/buffer/autocomplete.go | 59 ++++++++++++++++++++++++-------- cmd/micro/buffer/buffer.go | 9 ++++- cmd/micro/display/infowindow.go | 11 +++--- cmd/micro/info/infobuffer.go | 7 ---- 6 files changed, 91 insertions(+), 60 deletions(-) diff --git a/cmd/micro/action/infocomplete.go b/cmd/micro/action/infocomplete.go index e4ab2f2c..99a3844a 100644 --- a/cmd/micro/action/infocomplete.go +++ b/cmd/micro/action/infocomplete.go @@ -14,7 +14,7 @@ import ( // for example with `vsplit filename`. // CommandComplete autocompletes commands -func CommandComplete(b *buffer.Buffer) (string, []string) { +func CommandComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := buffer.GetArg(b) @@ -25,15 +25,16 @@ func CommandComplete(b *buffer.Buffer) (string, []string) { } } - var chosen string - if len(suggestions) == 1 { - chosen = util.SliceEndStr(suggestions[0], c.X-argstart) + completions := make([]string, len(suggestions)) + for i := range suggestions { + completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) } - return chosen, suggestions + + return completions, suggestions } // HelpComplete autocompletes help topics -func HelpComplete(b *buffer.Buffer) (string, []string) { +func HelpComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := buffer.GetArg(b) @@ -46,16 +47,16 @@ func HelpComplete(b *buffer.Buffer) (string, []string) { } } - var chosen string - if len(suggestions) == 1 { - chosen = util.SliceEndStr(suggestions[0], c.X-argstart) + completions := make([]string, len(suggestions)) + for i := range suggestions { + completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) } - return chosen, suggestions + return completions, suggestions } -// ColorschemeComplete tab-completes names of colorschemes. +// colorschemeComplete tab-completes names of colorschemes. // This is just a heper value for OptionValueComplete -func ColorschemeComplete(input string) (string, []string) { +func colorschemeComplete(input string) (string, []string) { var suggestions []string files := config.ListRuntimeFiles(config.RTColorscheme) @@ -83,7 +84,7 @@ func contains(s []string, e string) bool { } // OptionComplete autocompletes options -func OptionComplete(b *buffer.Buffer) (string, []string) { +func OptionComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := buffer.GetArg(b) @@ -100,15 +101,15 @@ func OptionComplete(b *buffer.Buffer) (string, []string) { } } - var chosen string - if len(suggestions) == 1 { - chosen = util.SliceEndStr(suggestions[0], c.X-argstart) + completions := make([]string, len(suggestions)) + for i := range suggestions { + completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) } - return chosen, suggestions + return completions, suggestions } // OptionValueComplete completes values for various options -func OptionValueComplete(b *buffer.Buffer) (string, []string) { +func OptionValueComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -167,7 +168,7 @@ func OptionValueComplete(b *buffer.Buffer) (string, []string) { case string: switch inputOpt { case "colorscheme": - _, suggestions = ColorschemeComplete(input) + _, suggestions = colorschemeComplete(input) case "fileformat": if strings.HasPrefix("unix", input) { suggestions = append(suggestions, "unix") @@ -185,11 +186,11 @@ func OptionValueComplete(b *buffer.Buffer) (string, []string) { } } - var chosen string - if len(suggestions) == 1 { - chosen = util.SliceEndStr(suggestions[0], c.X-argstart) + completions := make([]string, len(suggestions)) + for i := range suggestions { + completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart) } - return chosen, suggestions + return completions, suggestions } // // MakeCompletion registers a function from a plugin for autocomplete commands diff --git a/cmd/micro/action/infopane.go b/cmd/micro/action/infopane.go index c83f77d2..406476bc 100644 --- a/cmd/micro/action/infopane.go +++ b/cmd/micro/action/infopane.go @@ -163,8 +163,12 @@ func (h *InfoPane) CursorDown() { h.DownHistory(h.History[h.PromptType]) } func (h *InfoPane) InsertTab() { - // TODO: autocomplete b := h.Buf + if b.HasSuggestions { + b.CycleAutocomplete() + return + } + c := b.GetActiveCursor() l := b.LineBytes(0) l = util.SliceStart(l, c.X) @@ -172,23 +176,15 @@ func (h *InfoPane) InsertTab() { args := bytes.Split(l, []byte{' '}) cmd := string(args[0]) - var ins string - var suggestions []string if len(args) == 1 { - ins, suggestions = CommandComplete(b) + b.Autocomplete(CommandComplete) } else { if action, ok := commands[cmd]; ok { if action.completer != nil { - ins, suggestions = action.completer(b) + b.Autocomplete(action.completer) } } } - - if len(suggestions) == 1 { - b.Insert(c.Loc, ins) - } else if len(suggestions) > 1 { - h.MakeSuggestions(suggestions) - } } func (h *InfoPane) InsertNewline() { if !h.HasYN { diff --git a/cmd/micro/buffer/autocomplete.go b/cmd/micro/buffer/autocomplete.go index e03f07dd..12dbc0b5 100644 --- a/cmd/micro/buffer/autocomplete.go +++ b/cmd/micro/buffer/autocomplete.go @@ -10,14 +10,47 @@ import ( "github.com/zyedidia/micro/cmd/micro/util" ) -type Completer func(*Buffer) (string, []string) +// A Completer is a function that takes a buffer and returns info +// describing what autocompletions should be inserted at the current +// cursor location +// It returns a list of string suggestions which will be inserted at +// the current cursor location if selected as well as a list of +// suggestion names which can be displayed in a autocomplete box or +// other UI element +type Completer func(*Buffer) ([]string, []string) func (b *Buffer) GetSuggestions() { } func (b *Buffer) Autocomplete(c Completer) { + b.Completions, b.Suggestions = c(b) + if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 { + return + } + b.CurSuggestion = -1 + b.CycleAutocomplete() +} +func (b *Buffer) CycleAutocomplete() { + prevSuggestion := b.CurSuggestion + + b.CurSuggestion++ + if b.CurSuggestion >= len(b.Suggestions) || b.CurSuggestion < 0 { + b.CurSuggestion = 0 + } + + c := b.GetActiveCursor() + start := c.Loc + end := c.Loc + if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { + start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b) + } else { + end = start.Move(1, b) + } + + b.Replace(start, end, b.Completions[b.CurSuggestion]) + b.HasSuggestions = true } func GetArg(b *Buffer) (string, int) { @@ -39,7 +72,7 @@ func GetArg(b *Buffer) (string, int) { } // FileComplete autocompletes filenames -func FileComplete(b *Buffer) (string, []string) { +func FileComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() input, argstart := GetArg(b) @@ -57,10 +90,11 @@ func FileComplete(b *Buffer) (string, []string) { files, err = ioutil.ReadDir(".") } - var suggestions []string if err != nil { - return "", suggestions + return nil, nil } + + var suggestions []string for _, f := range files { name := f.Name() if f.IsDir() { @@ -71,19 +105,16 @@ func FileComplete(b *Buffer) (string, []string) { } } - var chosen string - if len(suggestions) == 1 { + completions := make([]string, len(suggestions)) + for i := range suggestions { + var complete string if len(dirs) > 1 { - chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[0] + complete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i] } else { - chosen = suggestions[0] - } - } else { - if len(dirs) > 1 { - chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + complete = suggestions[i] } + completions[i] = util.SliceEndStr(complete, c.X-argstart) } - chosen = util.SliceEndStr(chosen, c.X-argstart) - return chosen, suggestions + return completions, suggestions } diff --git a/cmd/micro/buffer/buffer.go b/cmd/micro/buffer/buffer.go index 6c81a636..83b22549 100644 --- a/cmd/micro/buffer/buffer.go +++ b/cmd/micro/buffer/buffer.go @@ -52,14 +52,19 @@ type SharedBuffer struct { Type BufType isModified bool + // Whether or not suggestions can be autocompleted must be shared because + // it changes based on how the buffer has changed + HasSuggestions bool } func (b *SharedBuffer) insert(pos Loc, value []byte) { b.isModified = true + b.HasSuggestions = false b.LineArray.insert(pos, value) } func (b *SharedBuffer) remove(start, end Loc) []byte { b.isModified = true + b.HasSuggestions = false return b.LineArray.remove(start, end) } @@ -94,7 +99,9 @@ type Buffer struct { // Settings customized by the user Settings map[string]interface{} - Suggestions []string + Suggestions []string + Completions []string + CurSuggestion int Messages []*Message } diff --git a/cmd/micro/display/infowindow.go b/cmd/micro/display/infowindow.go index 4474781d..5fa5c41b 100644 --- a/cmd/micro/display/infowindow.go +++ b/cmd/micro/display/infowindow.go @@ -190,8 +190,7 @@ func (i *InfoWindow) Display() { } } - if i.HasSuggestions { - i.HasSuggestions = false + if i.HasSuggestions && len(i.Suggestions) > 1 { statusLineStyle := config.DefStyle.Reverse(true) if style, ok := config.Colorscheme["statusline"]; ok { statusLineStyle = style @@ -201,9 +200,13 @@ func (i *InfoWindow) Display() { keymenuOffset = len(keydisplay) } x := 0 - for _, s := range i.Suggestions { + for j, s := range i.Suggestions { + style := statusLineStyle + if i.CurSuggestion == j { + style = style.Reverse(true) + } for _, r := range s { - screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, statusLineStyle) + screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style) x++ if x >= i.Width { return diff --git a/cmd/micro/info/infobuffer.go b/cmd/micro/info/infobuffer.go index d1d299c4..aebdd347 100644 --- a/cmd/micro/info/infobuffer.go +++ b/cmd/micro/info/infobuffer.go @@ -16,8 +16,6 @@ type InfoBuf struct { HasError bool HasYN bool - HasSuggestions bool - PromptType string Msg string @@ -161,8 +159,3 @@ func (i *InfoBuf) Reset() { i.HasPrompt, i.HasMessage, i.HasError = false, false, false i.HasGutter = false } - -func (i *InfoBuf) MakeSuggestions(s []string) { - i.HasSuggestions = true - i.Suggestions = s -}