diff --git a/internal/action/actions.go b/internal/action/actions.go index 912f27475b..2d80ff62a9 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -906,27 +906,22 @@ func (h *BufPane) OutdentSelection() bool { // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions func (h *BufPane) Autocomplete() bool { b := h.Buf + cc := buffer.AutocompleteCursorCheck(h.Cursor) + rc := buffer.AutocompleteRuneCheck(h.Cursor) - if h.Cursor.HasSelection() { + // Don't autocomplete at all if the active cursor cannot be autocomplete + if !b.HasSuggestions && (!rc || !cc || !b.StartAutocomplete(buffer.BufferComplete)) { return false } - if b.HasSuggestions { - b.CycleAutocomplete(true) - return true - } - - if h.Cursor.X == 0 { - return false - } - r := h.Cursor.RuneUnder(h.Cursor.X) - prev := h.Cursor.RuneUnder(h.Cursor.X - 1) - if !util.IsAutocomplete(prev) || util.IsWordChar(r) { - // don't autocomplete if cursor is within a word - return false + prevSuggestion := b.CycleAutocomplete(true) + for i := 0; i < b.NumCursors(); i++ { + if buffer.AutocompleteCursorCheck(b.GetCursor(i)) { + b.PerformSingleAutocomplete(prevSuggestion, b.GetCursor(i)) + } } - return b.Autocomplete(buffer.BufferComplete) + return true } // CycleAutocompleteBack cycles back in the autocomplete suggestion list @@ -935,8 +930,14 @@ func (h *BufPane) CycleAutocompleteBack() bool { return false } - if h.Buf.HasSuggestions { - h.Buf.CycleAutocomplete(false) + b := h.Buf + if b.HasSuggestions { + prevSuggestion := b.CycleAutocomplete(false) + for i := 0; i < b.NumCursors(); i++ { + if buffer.AutocompleteCursorCheck(b.GetCursor(i)) { + b.PerformSingleAutocomplete(prevSuggestion, b.GetCursor(i)) + } + } return true } return false diff --git a/internal/action/infopane.go b/internal/action/infopane.go index 4ebb1c0d85..f16e22fef0 100644 --- a/internal/action/infopane.go +++ b/internal/action/infopane.go @@ -191,30 +191,52 @@ func (h *InfoPane) HistorySearchDown() { // Autocomplete begins autocompletion func (h *InfoPane) CommandComplete() { b := h.Buf + c := b.GetActiveCursor() + + cc := buffer.AutocompleteCursorCheck(c) + rc := buffer.AutocompleteRuneCheck(c) + + // Cycling commands + if !b.HasSuggestions && !cc && !rc { + return + } + if b.HasSuggestions { - b.CycleAutocomplete(true) + if !cc { + return + } + + prevSuggestion := b.CycleAutocomplete(true) + b.PerformSingleAutocomplete(prevSuggestion, c) return } - c := b.GetActiveCursor() + // Otherwise start autocomplete l := b.LineBytes(0) l = util.SliceStart(l, c.X) - args := bytes.Split(l, []byte{' '}) cmd := string(args[0]) + var completer buffer.Completer = nil + if h.PromptType == "Command" { if len(args) == 1 { - b.Autocomplete(CommandComplete) + completer = CommandComplete } else if action, ok := commands[cmd]; ok { - if action.completer != nil { - b.Autocomplete(action.completer) - } + completer = action.completer } } else { // by default use filename autocompletion - b.Autocomplete(buffer.FileComplete) + completer = buffer.FileComplete + } + if completer == nil { + return + } + if !b.StartAutocomplete(completer) { + return } + prevSuggestion := b.CycleAutocomplete(true) + b.PerformSingleAutocomplete(prevSuggestion, c) } // ExecuteCommand completes the prompt diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 0cd83eabc9..175fa902a5 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -23,19 +23,37 @@ func (b *Buffer) GetSuggestions() { } -// Autocomplete starts the autocomplete process -func (b *Buffer) Autocomplete(c Completer) bool { +func AutocompleteRuneCheck(cursor *Cursor) bool { + r := cursor.RuneUnder(cursor.X) + prev := cursor.RuneUnder(cursor.X - 1) + if !util.IsAutocomplete(prev) || util.IsWordChar(r) { + // don't autocomplete if cursor is within a word + return false + } + return true +} + +func AutocompleteCursorCheck(cursor *Cursor) bool { + if cursor.HasSelection() { + return false + } + if cursor.X == 0 { + return false + } + return true +} + +func (b *Buffer) StartAutocomplete(c Completer) bool { b.Completions, b.Suggestions = c(b) if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 { return false } b.CurSuggestion = -1 - b.CycleAutocomplete(true) return true } -// CycleAutocomplete moves to the next suggestion -func (b *Buffer) CycleAutocomplete(forward bool) { +// CycleAutocomplete moves to the next suggestion and return the previous suggestion +func (b *Buffer) CycleAutocomplete(forward bool) int { prevSuggestion := b.CurSuggestion if forward { @@ -49,17 +67,25 @@ func (b *Buffer) CycleAutocomplete(forward bool) { b.CurSuggestion = len(b.Suggestions) - 1 } - c := b.GetActiveCursor() - start := c.Loc - end := c.Loc - if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { - start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b) - } - - b.Replace(start, end, b.Completions[b.CurSuggestion]) if len(b.Suggestions) > 1 { b.HasSuggestions = true } + + return prevSuggestion +} + +func (b *Buffer) PerformSingleAutocomplete(prevSuggestion int, cursor *Cursor) { + curLoc := cursor.Loc + curStart := curLoc + curEnd := curLoc + + if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { + curStart = curEnd.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b) + } + + hasSuggestions := b.HasSuggestions + b.Replace(curStart, curEnd, b.Completions[b.CurSuggestion]) + b.HasSuggestions = hasSuggestions } // GetWord gets the most recent word separated by any separator