From fa023801cf0eacb5ab0e538d7a7b1eadc6a0d3cf Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Sun, 25 Aug 2024 01:21:11 +0100 Subject: [PATCH 1/3] Adding auto complete support for multi cursors --- internal/buffer/autocomplete.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 8a1c3742a9..9a01fa88a8 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -49,19 +49,26 @@ 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.performAutoComplete(prevSuggestion) - b.Replace(start, end, b.Completions[b.CurSuggestion]) if len(b.Suggestions) > 1 { b.HasSuggestions = true } } +func (b *Buffer) performAutoComplete(prevSuggestion int) { + for _, cur := range b.cursors { + curLoc := cur.Loc + curStart := curLoc + curEnd := curLoc + + if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 { + curStart = curEnd.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b) + } + b.Replace(curStart, curEnd, b.Completions[b.CurSuggestion]) + } +} + // GetWord gets the most recent word separated by any separator // (whitespace, punctuation, any non alphanumeric character) func (b *Buffer) GetWord() ([]byte, int) { From 596764603afea109cdd613e36743556671e87390 Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Sun, 25 Aug 2024 17:32:06 +0100 Subject: [PATCH 2/3] Extracting autocomplete functions... Extracting autocomplete functions, Adding autocomplete checks to each cursor --- internal/action/actions.go | 32 ++++++++++++---------- internal/action/infopane.go | 30 +++++++++++++++------ internal/buffer/autocomplete.go | 47 ++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index cdce96d983..5512c0e493 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -850,25 +850,23 @@ func (h *BufPane) OutdentSelection() bool { func (h *BufPane) Autocomplete() bool { b := h.Buf - if h.Cursor.HasSelection() { + // Don't autocomplete at all if the active cursor cannot be autocomplete + if !buffer.AutocompleteCheck(h.Cursor) { return false } - 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 + if !b.HasSuggestions && !b.StartAutocomplete(buffer.BufferComplete) { return false } - if b.HasSuggestions { - b.CycleAutocomplete(true) - return true + prevSuggestion := b.CycleAutocomplete(true) + for i := 0; i < b.NumCursors(); i++ { + if buffer.AutocompleteCheck(b.GetCursor(i)) { + b.PerformSingleAutocomplete(prevSuggestion, b.GetCursor(i)) + } } - return b.Autocomplete(buffer.BufferComplete) + + return true } // CycleAutocompleteBack cycles back in the autocomplete suggestion list @@ -877,8 +875,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.AutocompleteCheck(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 93dd945386..19fee76835 100644 --- a/internal/action/infopane.go +++ b/internal/action/infopane.go @@ -191,30 +191,44 @@ func (h *InfoPane) HistorySearchDown() { // Autocomplete begins autocompletion func (h *InfoPane) CommandComplete() { b := h.Buf + c := b.GetActiveCursor() + + // Cycling commands + if !buffer.AutocompleteCheck(c) { + return + } if b.HasSuggestions { - b.CycleAutocomplete(true) + 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 9a01fa88a8..06c8596af1 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -23,19 +23,33 @@ func (b *Buffer) GetSuggestions() { } -// Autocomplete starts the autocomplete process -func (b *Buffer) Autocomplete(c Completer) bool { +func AutocompleteCheck(cursor *Cursor) bool { + if cursor.HasSelection() { + return false + } + if cursor.X == 0 { + return false + } + 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 (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,24 +63,25 @@ func (b *Buffer) CycleAutocomplete(forward bool) { b.CurSuggestion = len(b.Suggestions) - 1 } - b.performAutoComplete(prevSuggestion) - if len(b.Suggestions) > 1 { b.HasSuggestions = true } + + return prevSuggestion } -func (b *Buffer) performAutoComplete(prevSuggestion int) { - for _, cur := range b.cursors { - curLoc := cur.Loc - curStart := curLoc - curEnd := curLoc +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) - } - b.Replace(curStart, curEnd, b.Completions[b.CurSuggestion]) + 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 From be32cf3ea36a417f8c3dfb16769d179afd599202 Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Sat, 8 Mar 2025 22:56:23 +0000 Subject: [PATCH 3/3] Allowing no word character once in suggestion mode --- internal/action/actions.go | 12 +++++------- internal/action/infopane.go | 10 +++++++++- internal/buffer/autocomplete.go | 18 +++++++++++------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index 5512c0e493..58c6ca8034 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -849,19 +849,17 @@ 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) // Don't autocomplete at all if the active cursor cannot be autocomplete - if !buffer.AutocompleteCheck(h.Cursor) { - return false - } - - if !b.HasSuggestions && !b.StartAutocomplete(buffer.BufferComplete) { + if !b.HasSuggestions && (!rc || !cc || !b.StartAutocomplete(buffer.BufferComplete)) { return false } prevSuggestion := b.CycleAutocomplete(true) for i := 0; i < b.NumCursors(); i++ { - if buffer.AutocompleteCheck(b.GetCursor(i)) { + if buffer.AutocompleteCursorCheck(b.GetCursor(i)) { b.PerformSingleAutocomplete(prevSuggestion, b.GetCursor(i)) } } @@ -879,7 +877,7 @@ func (h *BufPane) CycleAutocompleteBack() bool { if b.HasSuggestions { prevSuggestion := b.CycleAutocomplete(false) for i := 0; i < b.NumCursors(); i++ { - if buffer.AutocompleteCheck(b.GetCursor(i)) { + if buffer.AutocompleteCursorCheck(b.GetCursor(i)) { b.PerformSingleAutocomplete(prevSuggestion, b.GetCursor(i)) } } diff --git a/internal/action/infopane.go b/internal/action/infopane.go index 19fee76835..848e0221ce 100644 --- a/internal/action/infopane.go +++ b/internal/action/infopane.go @@ -193,11 +193,19 @@ func (h *InfoPane) CommandComplete() { b := h.Buf c := b.GetActiveCursor() + cc := buffer.AutocompleteCursorCheck(c) + rc := buffer.AutocompleteRuneCheck(c) + // Cycling commands - if !buffer.AutocompleteCheck(c) { + if !b.HasSuggestions && !cc && !rc { return } + if b.HasSuggestions { + if !cc { + return + } + prevSuggestion := b.CycleAutocomplete(true) b.PerformSingleAutocomplete(prevSuggestion, c) return diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 06c8596af1..d206cb4533 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -23,13 +23,7 @@ func (b *Buffer) GetSuggestions() { } -func AutocompleteCheck(cursor *Cursor) bool { - if cursor.HasSelection() { - return false - } - if cursor.X == 0 { - return false - } +func AutocompleteRuneCheck(cursor *Cursor) bool { r := cursor.RuneUnder(cursor.X) prev := cursor.RuneUnder(cursor.X - 1) if !util.IsAutocomplete(prev) || util.IsWordChar(r) { @@ -39,6 +33,16 @@ func AutocompleteCheck(cursor *Cursor) bool { 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 {