From 031ea74eb20ab985102a9892bba33dbad2daa2d4 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Tue, 30 Dec 2025 20:51:00 +0800 Subject: [PATCH 1/2] feat: nextMatchFromCursor,prevSearchMatchFromCursor --- gui.go | 14 +++++++++++--- view.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/gui.go b/gui.go index 39ba4743..1d4f8559 100644 --- a/gui.go +++ b/gui.go @@ -190,9 +190,11 @@ type Gui struct { OnSearchEscape func() error // these keys must either be of type Key of rune - SearchEscapeKey any - NextSearchMatchKey any - PrevSearchMatchKey any + SearchEscapeKey any + NextSearchMatchKey any + PrevSearchMatchKey any + NextSearchMatchFromCursorKey any + PrevSearchMatchFromCursorKey any ErrorHandler func(error) error @@ -269,6 +271,8 @@ func NewGui(opts NewGuiOpts) (*Gui, error) { g.SearchEscapeKey = KeyEsc g.NextSearchMatchKey = 'n' g.PrevSearchMatchKey = 'N' + g.NextSearchMatchFromCursorKey = "" + g.PrevSearchMatchFromCursorKey = "" g.playRecording = opts.PlayRecording @@ -1521,6 +1525,10 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error { return v.gotoNextMatch() } else if eventMatchesKey(ev, g.PrevSearchMatchKey) { return v.gotoPreviousMatch() + } else if eventMatchesKey(ev, g.NextSearchMatchFromCursorKey) { + return v.gotoNextMatchFromCursor() + } else if eventMatchesKey(ev, g.PrevSearchMatchFromCursorKey) { + return v.gotoPreviousMatchFromCursor() } else if eventMatchesKey(ev, g.SearchEscapeKey) { v.searcher.clearSearch() if g.OnSearchEscape != nil { diff --git a/view.go b/view.go index e5b5c046..a4ab787c 100644 --- a/view.go +++ b/view.go @@ -250,6 +250,60 @@ func (v *View) gotoPreviousMatch() error { return v.SelectSearchResult(v.searcher.currentSearchIndex) } +// gotoNextMatchFromCursor finds the next match after the current cursor position +func (v *View) gotoNextMatchFromCursor() error { + if len(v.searcher.searchPositions) == 0 { + return nil + } + + // Get current line position + currentLine := v.SelectedLineIdx() + + // Find the next match after the current line + nextIndex := -1 + for i, pos := range v.searcher.searchPositions { + if pos.Y > currentLine { + nextIndex = i + break + } + } + + // If no match found after current line, wrap around to first match + if nextIndex == -1 { + nextIndex = 0 + } + + v.searcher.currentSearchIndex = nextIndex + return v.SelectSearchResult(v.searcher.currentSearchIndex) +} + +// gotoPreviousMatchFromCursor finds the previous match before the current cursor position +func (v *View) gotoPreviousMatchFromCursor() error { + if len(v.searcher.searchPositions) == 0 { + return nil + } + + // Get current line position + currentLine := v.SelectedLineIdx() + + // Find the previous match before the current line (search backwards) + prevIndex := -1 + for i := len(v.searcher.searchPositions) - 1; i >= 0; i-- { + if v.searcher.searchPositions[i].Y < currentLine { + prevIndex = i + break + } + } + + // If no match found before current line, wrap around to last match + if prevIndex == -1 { + prevIndex = len(v.searcher.searchPositions) - 1 + } + + v.searcher.currentSearchIndex = prevIndex + return v.SelectSearchResult(v.searcher.currentSearchIndex) +} + func (v *View) SelectSearchResult(index int) error { itemCount := len(v.searcher.searchPositions) if itemCount == 0 { From c9d90154ac142b2d4cf697a7a034164fec1bcedc Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:44:36 +0800 Subject: [PATCH 2/2] fixup! feat: nextMatchFromCursor,prevSearchMatchFromCursor --- view.go | 54 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/view.go b/view.go index a4ab787c..33a52afc 100644 --- a/view.go +++ b/view.go @@ -7,6 +7,7 @@ package gocui import ( "fmt" "io" + "sort" "strings" "sync" "unicode" @@ -250,58 +251,65 @@ func (v *View) gotoPreviousMatch() error { return v.SelectSearchResult(v.searcher.currentSearchIndex) } -// gotoNextMatchFromCursor finds the next match after the current cursor position func (v *View) gotoNextMatchFromCursor() error { if len(v.searcher.searchPositions) == 0 { return nil } - // Get current line position + positions := v.searcher.searchPositions + currentIdx := v.searcher.currentSearchIndex currentLine := v.SelectedLineIdx() - // Find the next match after the current line - nextIndex := -1 - for i, pos := range v.searcher.searchPositions { - if pos.Y > currentLine { - nextIndex = i - break + // If current match is on same line, check next position + if currentIdx >= 0 && currentIdx < len(positions) && positions[currentIdx].Y == currentLine { + if currentIdx+1 < len(positions) && positions[currentIdx+1].Y == currentLine { + v.searcher.currentSearchIndex = currentIdx + 1 + return v.SelectSearchResult(currentIdx + 1) } } - // If no match found after current line, wrap around to first match - if nextIndex == -1 { + // Find first match after current line + nextIndex := sort.Search(len(positions), func(i int) bool { + return positions[i].Y > currentLine + }) + + if nextIndex >= len(positions) { nextIndex = 0 } v.searcher.currentSearchIndex = nextIndex - return v.SelectSearchResult(v.searcher.currentSearchIndex) + return v.SelectSearchResult(nextIndex) } -// gotoPreviousMatchFromCursor finds the previous match before the current cursor position func (v *View) gotoPreviousMatchFromCursor() error { if len(v.searcher.searchPositions) == 0 { return nil } - // Get current line position + positions := v.searcher.searchPositions + currentIdx := v.searcher.currentSearchIndex currentLine := v.SelectedLineIdx() - // Find the previous match before the current line (search backwards) - prevIndex := -1 - for i := len(v.searcher.searchPositions) - 1; i >= 0; i-- { - if v.searcher.searchPositions[i].Y < currentLine { - prevIndex = i - break + // If current match is on same line, check previous position + if currentIdx >= 0 && currentIdx < len(positions) && positions[currentIdx].Y == currentLine { + if currentIdx-1 >= 0 && positions[currentIdx-1].Y == currentLine { + v.searcher.currentSearchIndex = currentIdx - 1 + return v.SelectSearchResult(currentIdx - 1) } } - // If no match found before current line, wrap around to last match - if prevIndex == -1 { - prevIndex = len(v.searcher.searchPositions) - 1 + // Find first match on or after current line, then go back one + idx := sort.Search(len(positions), func(i int) bool { + return positions[i].Y >= currentLine + }) + + prevIndex := idx - 1 + if prevIndex < 0 { + prevIndex = len(positions) - 1 } v.searcher.currentSearchIndex = prevIndex - return v.SelectSearchResult(v.searcher.currentSearchIndex) + return v.SelectSearchResult(prevIndex) } func (v *View) SelectSearchResult(index int) error {