diff --git a/internal/action/actions.go b/internal/action/actions.go index 491199f76f..ebd5189986 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -916,16 +916,6 @@ func (h *BufPane) Autocomplete() bool { 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 - } - return b.Autocomplete(buffer.BufferComplete) } diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 6dcda4da51..25360e547c 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -25,6 +25,9 @@ func (b *Buffer) GetSuggestions() { // Autocomplete starts the autocomplete process func (b *Buffer) Autocomplete(c Completer) bool { + if !b.GetActiveCursor().CanAutocomplete() { + return false + } b.Completions, b.Suggestions = c(b) if len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 { return false @@ -49,23 +52,57 @@ func (b *Buffer) CycleAutocomplete(forward bool) { b.CurSuggestion = len(b.Suggestions) - 1 } - c := b.GetActiveCursor() + // cycle autocomplete for all except active cursors + for i, c := range b.cursors { + if i == b.curCursor || !c.CanAutocomplete() { + continue + } + + activeWord, _ := b.GetWordCursor(b.GetActiveCursor()) + word, _ := b.GetWordCursor(c); + if !bytes.Equal(word, activeWord) { + continue + } + + b.AutocompleteSingle(c, prevSuggestion) + } + + // cycle autocomplete for active cursor + b.AutocompleteSingle(b.GetActiveCursor(), prevSuggestion) + + if len(b.Suggestions) > 1 { + b.HasSuggestions = true + } +} + +func (b *Buffer) AutocompleteSingle(c *Cursor, prevSuggestion int) { 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 +} + +func (c *Cursor) CanAutocomplete() bool { + if c.X == 0 { + return false } + + r := c.RuneUnder(c.X) + prev := c.RuneUnder(c.X - 1) + if !util.IsAutocomplete(prev) || util.IsWordChar(r) { + // don't autocomplete if cursor is within a word + return false + } + return true } -// GetWord gets the most recent word separated by any separator +// GetWordCursor gets the most recent word separated by any separator // (whitespace, punctuation, any non alphanumeric character) -func (b *Buffer) GetWord() ([]byte, int) { - c := b.GetActiveCursor() +func (b *Buffer) GetWordCursor(c *Cursor) ([]byte, int) { l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -82,6 +119,10 @@ func (b *Buffer) GetWord() ([]byte, int) { return input, c.X - util.CharacterCount(input) } +func (b *Buffer) GetWord() ([]byte, int) { + return b.GetWordCursor(b.GetActiveCursor()) +} + // GetArg gets the most recent word (separated by ' ' only) func (b *Buffer) GetArg() (string, int) { c := b.GetActiveCursor() @@ -153,7 +194,7 @@ func FileComplete(b *Buffer) ([]string, []string) { // BufferComplete autocompletes based on previous words in the buffer func BufferComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := b.GetWord() + input, argstart := b.GetWordCursor(c) if argstart == -1 { return []string{}, []string{}