@@ -90,6 +90,9 @@ type App struct {
9090 statusMessage string // Status bar message
9191 pendingCodeInsert string // Code waiting to be inserted after file creation
9292 buildNumber string // Build number from git commits
93+ searchResults []string // Files found in last search
94+ searchResultIndex int // Current index in searchResults
95+ searchTerms []string // Last search terms used
9396}
9497
9598// New creates a new application instance with the provided configuration.
@@ -171,6 +174,9 @@ func New(config *types.AppConfig, buildNumber string) *App {
171174 showExitConfirmation : false ,
172175 forceQuit : false ,
173176 buildNumber : buildNumber ,
177+ searchResults : []string {},
178+ searchResultIndex : 0 ,
179+ searchTerms : []string {},
174180 }
175181}
176182
@@ -547,36 +553,48 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
547553
548554 case SearchCompleteMsg :
549555 a .aiPane .streaming = false
550- if len (msg .Results ) > 0 {
551- // Open the first matching file
552- fullPath := filepath .Join (a .config .WorkspaceDir , msg .Results [0 ])
553- err := a .editorPane .LoadFile (fullPath )
554- if err != nil {
555- a .statusMessage = "Error opening found file: " + err .Error ()
556- a .aiPane .DisplayNotification ("Found matches for '" + msg .SearchTerm + "', but failed to open " + msg .Results [0 ])
557- } else {
558- a .statusMessage = "Opened: " + msg .Results [0 ]
559- a .aiPane .SetWorkingDir (filepath .Dir (a .editorPane .currentFile .Filepath ))
560-
561- // Switch to editor pane
562- a .activePane = types .EditorPaneType
563- a .editorPane .focused = true
564- a .aiPane .focused = false
556+ totalResults := len (msg .ExactResults ) + len (msg .AltResults )
557+ if totalResults > 0 {
558+ a .searchResults = append (msg .ExactResults , msg .AltResults ... )
559+ a .searchTerms = strings .Split (msg .SearchTerm , ", " )
560+ a .searchResultIndex = 0
561+
562+ a .openSearchResult ()
563+
564+ var chatMsg strings.Builder
565+ chatMsg .WriteString (fmt .Sprintf ("🔍 Found '%s' in %d file(s):\n " , msg .SearchTerm , totalResults ))
566+
567+ if len (msg .ExactResults ) > 0 {
568+ chatMsg .WriteString ("Exact search pattern:\n " )
569+ for i , res := range msg .ExactResults {
570+ if i >= 3 {
571+ chatMsg .WriteString (fmt .Sprintf ("- ... and %d more exact matches\n " , len (msg .ExactResults )- 3 ))
572+ break
573+ }
574+ chatMsg .WriteString ("- " + res + "\n " )
575+ }
576+ }
565577
566- var chatMsg strings. Builder
567- chatMsg .WriteString (fmt . Sprintf ( "🔍 Found '%s' in %d file(s) :\n ", msg . SearchTerm , len ( msg . Results )) )
568- for i , res := range msg .Results {
569- if i >= 5 {
570- chatMsg .WriteString (fmt .Sprintf ("- ... and %d more\n " , len (msg .Results ) - 5 ))
578+ if len ( msg . AltResults ) > 0 {
579+ chatMsg .WriteString (" \n Alternative variations :\n " )
580+ for i , res := range msg .AltResults {
581+ if i >= 3 {
582+ chatMsg .WriteString (fmt .Sprintf ("- ... and %d more\n " , len (msg .AltResults ) - 3 ))
571583 break
572584 }
573585 chatMsg .WriteString ("- " + res + "\n " )
574586 }
575- chatMsg .WriteString ("\n Opening the first match: " + msg .Results [0 ])
576- a .aiPane .DisplayNotification (chatMsg .String ())
577587 }
588+
589+ matchType := "exact match"
590+ if len (msg .ExactResults ) == 0 {
591+ matchType = "alternative variation"
592+ }
593+ chatMsg .WriteString (fmt .Sprintf ("\n Opening the first match: %s (%s)" , a .searchResults [0 ], matchType ))
594+ chatMsg .WriteString ("\n \n *Tip: Use `Alt+N` (Next) and `Alt+P` (Previous) to jump between these files.*" )
595+ a .aiPane .DisplayNotification (chatMsg .String ())
578596 }
579- return a , nil
597+ return a , tea . Batch ( cmds ... )
580598
581599 case TerminalOutputMsg , TerminalDoneMsg , AIResponseMsg , AINotificationMsg , AIAvailabilityMsg , ClearStatusMsg :
582600 // Handle ClearStatusMsg
@@ -1064,6 +1082,31 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
10641082 // Clear AI chat history (New Chat)
10651083 a .aiPane .ClearHistory ()
10661084 a .statusMessage = "AI chat history cleared"
1085+ a .searchResults = []string {}
1086+ return a , nil
1087+
1088+ case "alt+n" :
1089+ if len (a .searchResults ) > 0 {
1090+ a .searchResultIndex ++
1091+ if a .searchResultIndex >= len (a .searchResults ) {
1092+ a .searchResultIndex = 0 // loop back to start
1093+ }
1094+ a .openSearchResult ()
1095+ } else {
1096+ a .statusMessage = "No search results to jump to"
1097+ }
1098+ return a , nil
1099+
1100+ case "alt+p" :
1101+ if len (a .searchResults ) > 0 {
1102+ a .searchResultIndex --
1103+ if a .searchResultIndex < 0 {
1104+ a .searchResultIndex = len (a .searchResults ) - 1 // loop back to end
1105+ }
1106+ a .openSearchResult ()
1107+ } else {
1108+ a .statusMessage = "No search results to jump to"
1109+ }
10671110 return a , nil
10681111
10691112 case "ctrl+h" :
@@ -2190,7 +2233,7 @@ func (a *App) handleAIMessage(message string) tea.Cmd {
21902233 }
21912234 }
21922235
2193- results , err := a .fileManager .SearchFilesContent (terms )
2236+ exactResults , altResults , err := a .fileManager .SearchFilesContent (terms )
21942237 if err != nil {
21952238 return AgenticFixResultMsg {
21962239 Result : & agentic.FixResult {
@@ -2201,7 +2244,7 @@ func (a *App) handleAIMessage(message string) tea.Cmd {
22012244 }
22022245 }
22032246
2204- if len (results ) == 0 {
2247+ if len (exactResults ) == 0 && len ( altResults ) == 0 {
22052248 return AgenticFixResultMsg {
22062249 Result : & agentic.FixResult {
22072250 Success : false ,
@@ -2212,8 +2255,9 @@ func (a *App) handleAIMessage(message string) tea.Cmd {
22122255 }
22132256
22142257 return SearchCompleteMsg {
2215- SearchTerm : strings .Join (terms , ", " ),
2216- Results : results ,
2258+ SearchTerm : strings .Join (terms , ", " ),
2259+ ExactResults : exactResults ,
2260+ AltResults : altResults ,
22172261 }
22182262 }
22192263 }
@@ -2256,6 +2300,35 @@ func (a *App) handleAIMessage(message string) tea.Cmd {
22562300 }
22572301}
22582302
2303+ // openSearchResult opens the file at the current search result index and jumps to the matched term
2304+ func (a * App ) openSearchResult () {
2305+ if len (a .searchResults ) == 0 || a .searchResultIndex < 0 || a .searchResultIndex >= len (a .searchResults ) {
2306+ return
2307+ }
2308+
2309+ res := a .searchResults [a .searchResultIndex ]
2310+ fullPath := filepath .Join (a .config .WorkspaceDir , res )
2311+
2312+ err := a .editorPane .LoadFile (fullPath )
2313+ if err != nil {
2314+ a .statusMessage = fmt .Sprintf ("Error opening file %d/%d: %s" , a .searchResultIndex + 1 , len (a .searchResults ), err .Error ())
2315+ return
2316+ }
2317+
2318+ a .statusMessage = fmt .Sprintf ("Opened search match %d of %d: %s" , a .searchResultIndex + 1 , len (a .searchResults ), res )
2319+ a .aiPane .SetWorkingDir (filepath .Dir (a .editorPane .currentFile .Filepath ))
2320+
2321+ // Search and jump to the matched term inside the editor
2322+ if len (a .searchTerms ) > 0 {
2323+ a .editorPane .SearchAndJump (a .searchTerms )
2324+ }
2325+
2326+ // Switch to editor pane
2327+ a .activePane = types .EditorPaneType
2328+ a .editorPane .focused = true
2329+ a .aiPane .focused = false
2330+ }
2331+
22592332// ensureGoModule checks if a go.mod exists in the given directory and runs
22602333// go mod init and go mod tidy if it doesn't, to automate Go project creation.
22612334func (a * App ) ensureGoModule (dir string ) {
0 commit comments