11use rat_widget:: { event:: TextOutcome , text_input} ;
2- use ratatui:: crossterm:: event:: { Event , KeyCode , KeyEvent , KeyModifiers } ;
2+ use ratatui:: {
3+ crossterm:: event:: {
4+ Event , KeyCode , KeyEvent , KeyModifiers , MouseButton , MouseEvent , MouseEventKind ,
5+ } ,
6+ layout:: Position ,
7+ } ;
38
49use crate :: { app:: App , types:: Pane , ui:: preview} ;
510
@@ -84,6 +89,20 @@ impl App {
8489 }
8590 }
8691
92+ pub fn handle_mouse ( & mut self , mouse : MouseEvent ) {
93+ // modals swallow mouse input, mirroring key handling
94+ if self . confirm_apply_all || self . options_open {
95+ return ;
96+ }
97+ let pos = Position :: new ( mouse. column , mouse. row ) ;
98+ match mouse. kind {
99+ MouseEventKind :: Down ( MouseButton :: Left ) => self . handle_click ( pos, mouse) ,
100+ MouseEventKind :: ScrollDown => self . handle_scroll ( pos, ScrollDir :: Down ) ,
101+ MouseEventKind :: ScrollUp => self . handle_scroll ( pos, ScrollDir :: Up ) ,
102+ _ => { }
103+ }
104+ }
105+
87106 fn handle_options_key ( & mut self , key : KeyEvent ) {
88107 match key. code {
89108 KeyCode :: Char ( 'c' ) if key. modifiers . contains ( KeyModifiers :: CONTROL ) => {
@@ -117,6 +136,86 @@ impl App {
117136 }
118137 }
119138
139+ fn select_next_file ( & mut self ) {
140+ if self . results . is_empty ( ) {
141+ return ;
142+ }
143+ let next = ( self . selected_file ( ) + 1 ) . min ( self . results . len ( ) - 1 ) ;
144+ self . file_list . select ( Some ( next) ) ;
145+ self . preview . reset_position ( ) ;
146+ self . dispatch_preview ( ) ;
147+ }
148+
149+ fn select_prev_file ( & mut self ) {
150+ let prev = self . selected_file ( ) . saturating_sub ( 1 ) ;
151+ self . file_list . select ( Some ( prev) ) ;
152+ self . preview . reset_position ( ) ;
153+ self . dispatch_preview ( ) ;
154+ }
155+
156+ fn handle_click ( & mut self , pos : Position , mouse : MouseEvent ) {
157+ let Some ( pane) = self . pane_areas . pane_at ( pos) else {
158+ return ;
159+ } ;
160+ // during search, only input panes are focusable (mirrors Tab)
161+ if self . searching && !pane. is_input ( ) {
162+ return ;
163+ }
164+ self . focused_pane = pane;
165+ match pane {
166+ Pane :: SearchInput => {
167+ text_input:: handle_events ( & mut self . search_input , true , & Event :: Mouse ( mouse) ) ;
168+ }
169+ Pane :: ReplaceInput => {
170+ text_input:: handle_events ( & mut self . replace_input , true , & Event :: Mouse ( mouse) ) ;
171+ }
172+ Pane :: FileList => {
173+ if let Some ( idx) = self . file_list . row_at_clicked ( ( pos. x , pos. y ) )
174+ && Some ( idx) != self . file_list . selected ( )
175+ {
176+ self . file_list . select ( Some ( idx) ) ;
177+ self . preview . reset_position ( ) ;
178+ self . dispatch_preview ( ) ;
179+ }
180+ }
181+ Pane :: Preview => {
182+ if let Some ( idx) = self . preview . match_at ( pos) {
183+ self . preview . select_match ( idx) ;
184+ }
185+ }
186+ }
187+ }
188+
189+ fn handle_scroll ( & mut self , pos : Position , dir : ScrollDir ) {
190+ let Some ( pane) = self . pane_areas . pane_at ( pos) else {
191+ return ;
192+ } ;
193+ if self . searching && !pane. is_input ( ) {
194+ return ;
195+ }
196+ match pane {
197+ Pane :: FileList => {
198+ if matches ! ( dir, ScrollDir :: Down ) {
199+ self . select_next_file ( ) ;
200+ } else {
201+ self . select_prev_file ( ) ;
202+ }
203+ }
204+ Pane :: Preview => {
205+ let count = self
206+ . results
207+ . get ( self . selected_file ( ) )
208+ . map_or ( 0 , |fm| fm. matches . len ( ) ) ;
209+ if matches ! ( dir, ScrollDir :: Down ) {
210+ self . preview . move_down ( count) ;
211+ } else {
212+ self . preview . move_up ( ) ;
213+ }
214+ }
215+ Pane :: SearchInput | Pane :: ReplaceInput => { }
216+ }
217+ }
218+
120219 fn handle_non_input_key ( & mut self , key : KeyEvent ) {
121220 match key. code {
122221 KeyCode :: Char ( 'q' ) => self . exit = true ,
@@ -139,17 +238,11 @@ impl App {
139238 return ;
140239 }
141240 KeyCode :: Char ( 'j' ) | KeyCode :: Down if !self . results . is_empty ( ) => {
142- let next = ( self . selected_file ( ) + 1 ) . min ( self . results . len ( ) - 1 ) ;
143- self . file_list . select ( Some ( next) ) ;
144- self . preview . reset_position ( ) ;
145- self . dispatch_preview ( ) ;
241+ self . select_next_file ( ) ;
146242 return ;
147243 }
148244 KeyCode :: Char ( 'k' ) | KeyCode :: Up => {
149- let prev = self . selected_file ( ) . saturating_sub ( 1 ) ;
150- self . file_list . select ( Some ( prev) ) ;
151- self . preview . reset_position ( ) ;
152- self . dispatch_preview ( ) ;
245+ self . select_prev_file ( ) ;
153246 return ;
154247 }
155248 KeyCode :: Char ( 'l' ) | KeyCode :: Enter | KeyCode :: Right if !self . results . is_empty ( ) => {
@@ -184,3 +277,9 @@ impl App {
184277 }
185278 }
186279}
280+
281+ #[ derive( Clone , Copy ) ]
282+ enum ScrollDir {
283+ Up ,
284+ Down ,
285+ }
0 commit comments