]> git.lizzy.rs Git - micro.git/commitdiff
Add autocomplete
authorZachary Yedidia <zyedidia@gmail.com>
Sun, 16 Jun 2019 19:56:39 +0000 (15:56 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Wed, 25 Dec 2019 22:05:10 +0000 (17:05 -0500)
internal/action/actions.go
internal/action/bufpane.go
internal/action/infopane.go
internal/buffer/autocomplete.go
internal/display/statusline.go
internal/util/util.go

index 720817183d4592473d7c957480cfe7caaec3b085..bacb04ad366270c569074cd64e1876feab582e8d 100644 (file)
@@ -560,10 +560,22 @@ func (h *BufPane) OutdentSelection() bool {
 
 // InsertTab inserts a tab or spaces
 func (h *BufPane) InsertTab() bool {
-       indent := h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))
-       tabBytes := len(indent)
-       bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
-       h.Buf.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+       b := h.Buf
+       if b.HasSuggestions {
+               b.CycleAutocomplete(true)
+               return true
+       }
+
+       l := b.LineBytes(h.Cursor.Y)
+       l = util.SliceStart(l, h.Cursor.X)
+       hasComplete := b.Autocomplete(buffer.BufferComplete)
+       if !hasComplete {
+               indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
+               tabBytes := len(indent)
+               bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
+               b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+               return true
+       }
        return true
 }
 
index 39b6c145148770e930884d6034ae289f439f2a5d..669e2de42adcd6c46b3422894b6b2432f70e9c4d 100644 (file)
@@ -227,6 +227,9 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
 func (h *BufPane) DoKeyEvent(e Event) bool {
        if action, ok := BufKeyBindings[e]; ok {
                estr := BufKeyStrings[e]
+               if estr != "InsertTab" {
+                       h.Buf.HasSuggestions = false
+               }
                for _, s := range MultiActions {
                        if s == estr {
                                cursors := h.Buf.GetCursors()
index 1ab7c2812acb6263e74b76a1f076745abbf95e79..cc0ef609cb489fd3363156df0c118d7c3a35aceb 100644 (file)
@@ -157,12 +157,17 @@ var InfoOverrides = map[string]InfoKeyAction{
        "QuitAll":       (*InfoPane).QuitAll,
 }
 
+// CursorUp cycles history up
 func (h *InfoPane) CursorUp() {
        h.UpHistory(h.History[h.PromptType])
 }
+
+// CursorDown cycles history down
 func (h *InfoPane) CursorDown() {
        h.DownHistory(h.History[h.PromptType])
 }
+
+// InsertTab begins autocompletion
 func (h *InfoPane) InsertTab() {
        b := h.Buf
        if b.HasSuggestions {
@@ -187,22 +192,32 @@ func (h *InfoPane) InsertTab() {
                }
        }
 }
+
+// CycleBack cycles back in the autocomplete suggestion list
 func (h *InfoPane) CycleBack() {
        if h.Buf.HasSuggestions {
                h.Buf.CycleAutocomplete(false)
        }
 }
+
+// InsertNewline completes the prompt
 func (h *InfoPane) InsertNewline() {
        if !h.HasYN {
                h.DonePrompt(false)
        }
 }
+
+// Quit cancels the prompt
 func (h *InfoPane) Quit() {
        h.DonePrompt(true)
 }
+
+// QuitAll cancels the prompt
 func (h *InfoPane) QuitAll() {
        h.DonePrompt(true)
 }
+
+// Escape cancels the prompt
 func (h *InfoPane) Escape() {
        h.DonePrompt(true)
 }
index d143f329cdfa876359fe8e16b74edeea0b157164..1e5a7fc3b5647e1c059fffea41ee2c2083b48eea 100644 (file)
@@ -16,7 +16,7 @@ import (
 // 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
+// suggestion names which can be displayed in an autocomplete box or
 // other UI element
 type Completer func(*Buffer) ([]string, []string)
 
@@ -24,15 +24,18 @@ func (b *Buffer) GetSuggestions() {
 
 }
 
-func (b *Buffer) Autocomplete(c Completer) {
+// Autocomplete starts the autocomplete process
+func (b *Buffer) Autocomplete(c Completer) bool {
        b.Completions, b.Suggestions = c(b)
        if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {
-               return
+               return false
        }
        b.CurSuggestion = -1
        b.CycleAutocomplete(true)
+       return true
 }
 
+// CycleAutocomplete moves to the next suggestion
 func (b *Buffer) CycleAutocomplete(forward bool) {
        prevSuggestion := b.CurSuggestion
 
@@ -53,7 +56,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
        if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
                start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
        } else {
-               end = start.Move(1, b)
+               // end = start.Move(1, b)
        }
 
        b.Replace(start, end, b.Completions[b.CurSuggestion])
@@ -62,6 +65,27 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
        }
 }
 
+// GetWord gets the most recent word separated by any separator
+// (whitespace, punctuation, any non alphanumeric character)
+func GetWord(b *Buffer) ([]byte, int) {
+       c := b.GetActiveCursor()
+       l := b.LineBytes(c.Y)
+       l = util.SliceStart(l, c.X)
+
+       if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
+               return []byte{}, -1
+       }
+
+       if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
+               return []byte{}, c.X
+       }
+
+       args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+       input := args[len(args)-1]
+       return input, c.X - utf8.RuneCount(input)
+}
+
+// GetArg gets the most recent word (separated by ' ' only)
 func GetArg(b *Buffer) (string, int) {
        c := b.GetActiveCursor()
        l := b.LineBytes(c.Y)
@@ -128,3 +152,55 @@ func FileComplete(b *Buffer) ([]string, []string) {
 
        return completions, suggestions
 }
+
+// BufferComplete autocompletes based on previous words in the buffer
+func BufferComplete(b *Buffer) ([]string, []string) {
+       c := b.GetActiveCursor()
+       input, argstart := GetWord(b)
+
+       if argstart == -1 {
+               return []string{}, []string{}
+       }
+
+       inputLen := utf8.RuneCount(input)
+
+       suggestionsSet := make(map[string]struct{})
+
+       var suggestions []string
+       for i := c.Y; i >= 0; i-- {
+               l := b.LineBytes(i)
+               words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+               for _, w := range words {
+                       if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
+                               strw := string(w)
+                               if _, ok := suggestionsSet[strw]; !ok {
+                                       suggestionsSet[strw] = struct{}{}
+                                       suggestions = append(suggestions, strw)
+                               }
+                       }
+               }
+       }
+       for i := c.Y + 1; i < b.LinesNum(); i++ {
+               l := b.LineBytes(i)
+               words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
+               for _, w := range words {
+                       if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
+                               strw := string(w)
+                               if _, ok := suggestionsSet[strw]; !ok {
+                                       suggestionsSet[strw] = struct{}{}
+                                       suggestions = append(suggestions, strw)
+                               }
+                       }
+               }
+       }
+       if len(suggestions) > 1 {
+               suggestions = append(suggestions, string(input))
+       }
+
+       completions := make([]string, len(suggestions))
+       for i := range suggestions {
+               completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
+       }
+
+       return completions, suggestions
+}
index beede173d30b259cb35eeec9cd4e77bcbcad0e06..6731c2c8c995341a66a020086fd4504075a6b79d 100644 (file)
@@ -69,6 +69,43 @@ func (s *StatusLine) Display() {
        // We'll draw the line at the lowest line in the window
        y := s.win.Height + s.win.Y - 1
 
+       b := s.win.Buf
+       if b.HasSuggestions && len(b.Suggestions) > 1 {
+               statusLineStyle := config.DefStyle.Reverse(true)
+               if style, ok := config.Colorscheme["statusline"]; ok {
+                       statusLineStyle = style
+               }
+               keymenuOffset := 0
+               if config.GetGlobalOption("keymenu").(bool) {
+                       keymenuOffset = len(keydisplay)
+               }
+               x := 0
+               for j, sug := range b.Suggestions {
+                       style := statusLineStyle
+                       if b.CurSuggestion == j {
+                               style = style.Reverse(true)
+                       }
+                       for _, r := range sug {
+                               screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
+                               x++
+                               if x >= s.win.Width {
+                                       return
+                               }
+                       }
+                       screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
+                       x++
+                       if x >= s.win.Width {
+                               return
+                       }
+               }
+
+               for x < s.win.Width {
+                       screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
+                       x++
+               }
+               return
+       }
+
        formatter := func(match []byte) []byte {
                name := match[2 : len(match)-1]
                if bytes.HasPrefix(name, []byte("opt")) {
index b7a7bb31209df1b64a67f880d931ed52ee612019..bdcf11eef471bd7e0a7cca38d2b1276340a80b34 100644 (file)
@@ -10,6 +10,7 @@ import (
        "strconv"
        "strings"
        "time"
+       "unicode"
        "unicode/utf8"
 
        "github.com/blang/semver"
@@ -403,3 +404,7 @@ func Clamp(val, min, max int) int {
        }
        return val
 }
+
+func IsNonAlphaNumeric(c rune) bool {
+       return !unicode.IsLetter(c) && !unicode.IsNumber(c)
+}