From b22bb410103c841312b5d6ab8e137ec38d577444 Mon Sep 17 00:00:00 2001 From: redsti Date: Sun, 1 Mar 2026 14:45:54 +0100 Subject: [PATCH 1/4] Autocomplete support for multiple cursors --- internal/buffer/autocomplete.go | 36 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 6dcda4da51..9c00b435aa 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -49,23 +49,43 @@ 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 { + continue + } + + activeWord, _ := b.GetWord(b.GetActiveCursor()) + word, _ := b.GetWord(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 - } } // GetWord 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) GetWord(c *Cursor) ([]byte, int) { l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -153,7 +173,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.GetWord(c) if argstart == -1 { return []string{}, []string{} From 13893cc39399ebac43d1eb9305a9ecfdc40671ff Mon Sep 17 00:00:00 2001 From: redsti Date: Sun, 1 Mar 2026 15:25:53 +0100 Subject: [PATCH 2/4] Don't autocomplete multicursor when in a word --- internal/action/actions.go | 10 ---------- internal/buffer/autocomplete.go | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 11 deletions(-) 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 9c00b435aa..8a2bd57d71 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 @@ -51,7 +54,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) { // cycle autocomplete for all except active cursors for i, c := range b.cursors { - if i == b.curCursor { + if i == b.curCursor || !c.CanAutocomplete() { continue } @@ -83,6 +86,20 @@ func (b *Buffer) AutocompleteSingle(c *Cursor, prevSuggestion int) { b.Replace(start, end, b.Completions[b.CurSuggestion]) } +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 // (whitespace, punctuation, any non alphanumeric character) func (b *Buffer) GetWord(c *Cursor) ([]byte, int) { From 031361e37ffe7852fef5125830534eceb46f5aa8 Mon Sep 17 00:00:00 2001 From: redsti Date: Tue, 3 Mar 2026 08:27:07 +0100 Subject: [PATCH 3/4] fix plugin support --- internal/buffer/autocomplete.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 8a2bd57d71..f69e731f89 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -58,8 +58,8 @@ func (b *Buffer) CycleAutocomplete(forward bool) { continue } - activeWord, _ := b.GetWord(b.GetActiveCursor()) - word, _ := b.GetWord(c); + activeWord, _ := b.GetWordCursor(b.GetActiveCursor()) + word, _ := b.GetWordCursor(c); if !bytes.Equal(word, activeWord) { continue } @@ -100,9 +100,9 @@ func (c *Cursor) CanAutocomplete() bool { 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(c *Cursor) ([]byte, int) { +func (b *Buffer) GetWordCursor(c *Cursor) ([]byte, int) { l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -119,6 +119,10 @@ func (b *Buffer) GetWord(c *Cursor) ([]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() @@ -190,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(c) + input, argstart := b.GetWordCursor(c) if argstart == -1 { return []string{}, []string{} From 325e85c4fcc532261955653fd1f5fa796497b1de Mon Sep 17 00:00:00 2001 From: redsti Date: Tue, 3 Mar 2026 20:50:25 +0100 Subject: [PATCH 4/4] remove trailing whitespace --- internal/buffer/autocomplete.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index f69e731f89..25360e547c 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -63,7 +63,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) { if !bytes.Equal(word, activeWord) { continue } - + b.AutocompleteSingle(c, prevSuggestion) } @@ -82,7 +82,7 @@ func (b *Buffer) AutocompleteSingle(c *Cursor, prevSuggestion int) { if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b) } - + b.Replace(start, end, b.Completions[b.CurSuggestion]) }