@@ -30,7 +30,8 @@ pub struct OutputWidget {
3030 key_bindings : KeyBindings ,
3131 output_height : u16 ,
3232 error_pane_placement : ErrorPanePlacement ,
33- visible_range : Range < usize > ,
33+ visible_range_x : Range < usize > ,
34+ visible_range_y : Range < usize > ,
3435 highlight : String ,
3536 highlight_positions : Vec < ( usize , Range < usize > ) > ,
3637 highlight_index : usize ,
@@ -56,7 +57,8 @@ impl OutputWidget {
5657 error_pane_placement,
5758 highlight : String :: new ( ) ,
5859 highlight_positions : vec ! [ ] ,
59- visible_range : 0 ..0 ,
60+ visible_range_x : 0 ..0 ,
61+ visible_range_y : 0 ..0 ,
6062 highlight_index : 0 ,
6163 }
6264 }
@@ -73,10 +75,9 @@ impl OutputWidget {
7375 pub fn highlight_next ( & mut self ) {
7476 if !self . highlight_positions . is_empty ( ) {
7577 self . highlight_index = ( self . highlight_index + 1 ) % self . highlight_positions . len ( ) ;
76- let ( line, _) = self . highlight_positions [ self . highlight_index ] ;
77- if !self . visible_range . contains ( & line) {
78- self . offset . y = line. saturating_sub ( self . visible_range . len ( ) / 2 ) ;
79- }
78+
79+ let ( line, range) = self . highlight_positions [ self . highlight_index ] . clone ( ) ;
80+ self . adjust_viewport_for_highlight ( line, range) ;
8081 }
8182 }
8283
@@ -88,11 +89,8 @@ impl OutputWidget {
8889 self . highlight_index = self . highlight_index . saturating_sub ( 1 ) ;
8990 }
9091
91- let ( line, _) = self . highlight_positions [ self . highlight_index ] ;
92-
93- if !self . visible_range . contains ( & line) {
94- self . offset . y = line. saturating_sub ( self . visible_range . len ( ) / 2 ) ;
95- }
92+ let ( line, range) = self . highlight_positions [ self . highlight_index ] . clone ( ) ;
93+ self . adjust_viewport_for_highlight ( line, range) ;
9694 }
9795 }
9896
@@ -132,7 +130,7 @@ impl OutputWidget {
132130 // find the first match in the visible range otherwise start from the beginning
133131 match positions
134132 . iter ( )
135- . find_position ( |( line, _range) | line >= & self . visible_range . start )
133+ . find_position ( |( line, _range) | line >= & self . visible_range_y . start )
136134 {
137135 Some ( ( z, _) ) => self . highlight_index = z,
138136 None => self . highlight_index = 0 ,
@@ -142,16 +140,29 @@ impl OutputWidget {
142140
143141 // focus on the first match
144142 if !self . highlight_positions . is_empty ( ) {
145- let ( line, _) = self . highlight_positions [ self . highlight_index ] ;
146- if !self . visible_range . contains ( & line) {
147- self . offset . y = line. saturating_sub ( self . visible_range . len ( ) / 2 ) ;
148- }
143+ let ( line, range) = self . highlight_positions [ self . highlight_index ] . clone ( ) ;
144+ self . adjust_viewport_for_highlight ( line, range) ;
149145 }
150146 } else {
151147 self . highlight_positions = vec ! [ ] ;
152148 }
153149 }
154150
151+ fn adjust_viewport_for_highlight ( & mut self , line_num : usize , range : Range < usize > ) {
152+ if !self . visible_range_y . contains ( & line_num) {
153+ self . offset . y = line_num. saturating_sub ( self . visible_range_y . len ( ) / 2 ) ;
154+ }
155+
156+ if !self . visible_range_x . contains ( & range. start ) {
157+ if range. start < self . visible_range_x . len ( ) {
158+ // scroll fully to the left if highligh is in the first "horizontal "page"
159+ self . offset . x = 0 ;
160+ } else {
161+ self . offset . x = range. start . saturating_sub ( self . visible_range_x . len ( ) / 4 ) ;
162+ }
163+ }
164+ }
165+
155166 pub fn output_len ( & self ) -> usize {
156167 self . output . lines . len ( )
157168 }
@@ -302,19 +313,20 @@ impl Widget for &mut OutputWidget {
302313
303314 let height = output_content_area. height . min ( output_len as u16 ) ;
304315
305- let visible_range : Range < usize > = if height >= output_len as u16 {
316+ let visible_lines : Range < usize > = if height >= output_len as u16 {
306317 0 ..output_len
307318 } else {
308319 let from = ( self . offset . y as usize ) . min ( output_len) ;
309320 let to = ( self . offset . y as usize + height as usize ) . min ( output_len) ;
310321 from..to
311322 } ;
312323
313- self . visible_range = visible_range. clone ( ) ;
324+ self . visible_range_x = self . offset . x ..self . offset . x + output_content_area. width as usize ;
325+ self . visible_range_y = self . offset . y ..self . offset . y + output_content_area. height as usize ;
314326
315327 let output = self . main_output ( ) ;
316328
317- let line_nums = visible_range
329+ let line_nums = visible_lines
318330 . clone ( )
319331 . flat_map ( |i| {
320332 let visual_line_count = if self . wrap {
@@ -336,12 +348,12 @@ impl Widget for &mut OutputWidget {
336348
337349 let output_par = {
338350 let mut par = if !self . highlight_positions . is_empty ( ) {
339- let lines = ( & output. lines [ visible_range . clone ( ) ] )
351+ let lines = ( & output. lines [ visible_lines . clone ( ) ] )
340352 . iter ( )
341353 . enumerate ( )
342354 . map ( |( line_index, line) | {
343355 // todo simplify
344- let logical_line_num = line_index + visible_range . start ;
356+ let logical_line_num = line_index + visible_lines . start ;
345357
346358 let ( current_match_line, current_match_range) =
347359 self . highlight_positions . get ( self . highlight_index ) . unwrap ( ) ;
@@ -386,7 +398,7 @@ impl Widget for &mut OutputWidget {
386398 . scroll ( ( 0 , self . offset . x as u16 ) ) // todo
387399 . block ( Block :: default ( ) )
388400 } else {
389- Paragraph :: new ( output. lines [ visible_range ] . join ( "\n " ) )
401+ Paragraph :: new ( output. lines [ visible_lines ] . join ( "\n " ) )
390402 . scroll ( ( 0 , self . offset . x as u16 ) ) // todo
391403 . block ( Block :: default ( ) )
392404 } ;
@@ -619,6 +631,34 @@ mod tests {
619631 assert_snapshot ! ( "highlight another highlight" , terminal. backend( ) ) ;
620632 }
621633
634+ #[ test]
635+ fn highlighting_horizontal_scroll ( ) {
636+ let mut terminal = Terminal :: new ( TestBackend :: new ( 15 , 6 ) ) . unwrap ( ) ;
637+
638+ let mut widget = OutputWidget :: default ( ) ;
639+
640+ let out = vec ! [
641+ " hl1 " ,
642+ " hl2 hl3 " ,
643+ " hl4 hl5 " ,
644+ ] ;
645+
646+ widget. handle_command_output ( Output :: ok_stdin ( & out. join ( "\n " ) ) ) ;
647+ terminal
648+ . draw ( |frame| widget. render ( frame. area ( ) , frame. buffer_mut ( ) ) )
649+ . unwrap ( ) ;
650+ assert_snapshot ! ( "highlight horizontal base" , terminal. backend( ) ) ;
651+
652+ widget. highlight ( "hl" , false ) ;
653+ for i in 1 ..6 {
654+ widget. highlight_next ( ) ;
655+ terminal
656+ . draw ( |frame| widget. render ( frame. area ( ) , frame. buffer_mut ( ) ) )
657+ . unwrap ( ) ;
658+ assert_snapshot ! ( format!( "highlight {i}" ) , terminal. backend( ) ) ;
659+ }
660+ }
661+
622662 #[ test]
623663 fn split_line_into_parts_by_ranges_test ( ) {
624664 let str = "01234567890123456789" ;
0 commit comments