diff --git a/internal/action/actions.go b/internal/action/actions.go index 491199f76f..9f7f8ca4bf 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1110,8 +1110,10 @@ func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error { h.Buf.LastSearch = str h.Buf.LastSearchRegex = useRegex h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool) + h.Buf.UpdateMatchCount(str, h.Buf.Start(), h.Buf.End(), match[0], useRegex) } else { h.Cursor.ResetSelection() + h.Buf.MatchCount = 0 } return nil } @@ -1132,9 +1134,11 @@ func (h *BufPane) find(useRegex bool) bool { h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] h.GotoLoc(match[1]) + h.Buf.UpdateMatchCount(resp, h.Buf.Start(), h.Buf.End(), match[0], useRegex) } else { h.GotoLoc(h.searchOrig) h.Cursor.ResetSelection() + h.Buf.MatchCount = 0 } } } @@ -1153,9 +1157,11 @@ func (h *BufPane) find(useRegex bool) bool { h.Buf.LastSearch = resp h.Buf.LastSearchRegex = useRegex h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool) + h.Buf.UpdateMatchCount(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), match[0], h.Buf.LastSearchRegex) } else { h.Cursor.ResetSelection() InfoBar.Message("No matches found") + h.Buf.MatchCount = 0 } } else { h.Cursor.ResetSelection() @@ -1187,6 +1193,7 @@ func (h *BufPane) UnhighlightSearch() bool { return false } h.Buf.HighlightSearch = false + h.Buf.MatchCount = 0 return true } @@ -1230,8 +1237,10 @@ func (h *BufPane) FindNext() bool { h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] h.GotoLoc(h.Cursor.CurSelection[1]) + h.Buf.UpdateMatchCount(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), match[0], h.Buf.LastSearchRegex) } else { h.Cursor.ResetSelection() + h.Buf.MatchCount = 0 } return true } @@ -1267,8 +1276,10 @@ func (h *BufPane) FindPrevious() bool { h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] h.GotoLoc(h.Cursor.CurSelection[1]) + h.Buf.UpdateMatchCount(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), match[0], h.Buf.LastSearchRegex) } else { h.Cursor.ResetSelection() + h.Buf.MatchCount = 0 } return true } diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index df6a20f8d8..7754bc850c 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -148,6 +148,7 @@ func BufMapEvent(k Event, action string) { actionfns = append(actionfns, afn) } bufAction := func(h *BufPane, te *tcell.EventMouse) bool { + h.Buf.ShowMatchIdx = false for i, a := range actionfns { var success bool if _, ok := MultiActions[names[i]]; ok { diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 2735ca467c..87c842482c 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -261,6 +261,10 @@ type Buffer struct { LastSearchRegex bool // HighlightSearch enables highlighting all instances of the last successful search HighlightSearch bool + // MatchIdx contains the index of the selected match + MatchIdx int + MatchCount int + ShowMatchIdx bool // OverwriteMode indicates that we are in overwrite mode (toggled by // Insert key by default) i.e. that typing a character shall replace the diff --git a/internal/buffer/search.go b/internal/buffer/search.go index 3f4f0dad3e..7b06031324 100644 --- a/internal/buffer/search.go +++ b/internal/buffer/search.go @@ -189,6 +189,61 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo return l, found, nil } +// UpdateMatchCount updates MatchIdx and MatchCount +func (b *Buffer) UpdateMatchCount(s string, start, end, current Loc, useRegex bool) (error) { + if s == "" { + b.MatchIdx = 0 + b.MatchCount = 0 + return nil + } + + var r *regexp.Regexp + var err error + + if !useRegex { + s = regexp.QuoteMeta(s) + } + + if b.Settings["ignorecase"].(bool) { + r, err = regexp.Compile("(?i)" + s) + } else { + r, err = regexp.Compile(s) + } + + if err != nil { + b.MatchIdx = 0 + b.MatchCount = 0 + return err + } + + i := 0 + b.MatchIdx = 0 + loc := start + for { + match, found := b.findDown(r, loc, end) + if !found { + break + } + i++ + if current.GreaterEqual(match[0]) && current.LessEqual(match[1]) { + b.MatchIdx = i + } + if i >= 1000 { + break + } + if match[0] != match[1] { + loc = match[1] + } else if match[1] != end { + loc = match[1].Move(1, b) + } else { + break + } + } + b.MatchCount = i + b.ShowMatchIdx = true + return nil +} + // ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area // and returns the number of replacements made and the number of characters // added or removed on the last line of the range diff --git a/internal/display/statusline.go b/internal/display/statusline.go index 3500355b13..718120259e 100644 --- a/internal/display/statusline.go +++ b/internal/display/statusline.go @@ -59,6 +59,22 @@ var statusInfo = map[string]func(*buffer.Buffer) string{ "percentage": func(b *buffer.Buffer) string { return strconv.Itoa((b.GetActiveCursor().Y + 1) * 100 / b.LinesNum()) }, + "matches": func(b *buffer.Buffer) string { + if b.MatchCount == 0 { + return "" + } + var matchCount string + if b.MatchCount >= 1000 { + matchCount = "999+" + } else { + matchCount = strconv.Itoa(b.MatchCount) + } + + if b.ShowMatchIdx && b.MatchIdx > 0 { + return "[" + strconv.Itoa(b.MatchIdx) + "/" + matchCount + "] " + } + return "[" + matchCount + " matches] " + }, } func SetStatusInfoFnLua(fn string) {