11use crate :: components:: {
22 visibility_blocking, CommandBlocking , CommandInfo , Component ,
3- DrawableComponent , EventState ,
3+ DrawableComponent , EventState , ScrollType , VerticalScroll ,
44} ;
5+ use crate :: strings:: order;
56use crate :: {
67 app:: Environment ,
78 keys:: { key_match, SharedKeyConfig } ,
89 strings, ui,
910} ;
11+ use anyhow:: Result ;
1012use crossterm:: event:: Event ;
13+ use ratatui:: text:: Line ;
1114use ratatui:: {
1215 layout:: { Alignment , Rect } ,
1316 text:: Span ,
@@ -22,39 +25,68 @@ pub struct MsgPopup {
2225 visible : bool ,
2326 theme : SharedTheme ,
2427 key_config : SharedKeyConfig ,
28+ scroll : VerticalScroll ,
2529}
2630
27- use anyhow:: Result ;
31+ const POPUP_HEIGHT : u16 = 25 ;
32+ const BORDER_WIDTH : u16 = 2 ;
33+ const MINIMUM_WIDTH : u16 = 60 ;
2834
2935impl DrawableComponent for MsgPopup {
3036 fn draw ( & self , f : & mut Frame , _rect : Rect ) -> Result < ( ) > {
3137 if !self . visible {
3238 return Ok ( ( ) ) ;
3339 }
3440
41+ let max_width = f. size ( ) . width . max ( MINIMUM_WIDTH ) ;
42+
3543 // determine the maximum width of text block
36- let lens = self
44+ let width = self
3745 . msg
38- . split ( '\n' )
46+ . lines ( )
3947 . map ( str:: len)
40- . collect :: < Vec < usize > > ( ) ;
41- let mut max = lens. iter ( ) . max ( ) . expect ( "max" ) + 2 ;
42- if max > std:: u16:: MAX as usize {
43- max = std:: u16:: MAX as usize ;
44- }
45- let mut width = u16:: try_from ( max)
46- . expect ( "can't fail due to check above" ) ;
47- // dont overflow screen, and dont get too narrow
48- if width > f. size ( ) . width {
49- width = f. size ( ) . width ;
50- } else if width < 60 {
51- width = 60 ;
52- }
48+ . max ( )
49+ . unwrap_or ( 0 )
50+ . saturating_add ( BORDER_WIDTH . into ( ) )
51+ . clamp ( MINIMUM_WIDTH . into ( ) , max_width. into ( ) )
52+ . try_into ( )
53+ . expect ( "can't fail because we're clamping to u16 value" ) ;
54+
55+ let area =
56+ ui:: centered_rect_absolute ( width, POPUP_HEIGHT , f. size ( ) ) ;
57+
58+ // Wrap lines and break words if there is not enough space
59+ let wrapped_msg = bwrap:: wrap_maybrk!(
60+ & self . msg,
61+ area. width. saturating_sub( BORDER_WIDTH ) . into( )
62+ ) ;
63+
64+ let msg_lines: Vec < String > =
65+ wrapped_msg. lines ( ) . map ( String :: from) . collect ( ) ;
66+ let line_num = msg_lines. len ( ) ;
67+
68+ let height = POPUP_HEIGHT
69+ . saturating_sub ( BORDER_WIDTH )
70+ . min ( f. size ( ) . height . saturating_sub ( BORDER_WIDTH ) ) ;
71+
72+ let top =
73+ self . scroll . update_no_selection ( line_num, height. into ( ) ) ;
74+
75+ let scrolled_lines = msg_lines
76+ . iter ( )
77+ . skip ( top)
78+ . take ( height. into ( ) )
79+ . map ( |line| {
80+ Line :: from ( vec ! [ Span :: styled(
81+ line. clone( ) ,
82+ self . theme. text( true , false ) ,
83+ ) ] )
84+ } )
85+ . collect :: < Vec < Line > > ( ) ;
5386
54- let area = ui:: centered_rect_absolute ( width, 25 , f. size ( ) ) ;
5587 f. render_widget ( Clear , area) ;
5688 f. render_widget (
57- Paragraph :: new ( self . msg . clone ( ) )
89+ Paragraph :: new ( scrolled_lines )
5890 . block (
5991 Block :: default ( )
6092 . title ( Span :: styled (
@@ -69,6 +101,8 @@ impl DrawableComponent for MsgPopup {
69101 area,
70102 ) ;
71103
104+ self . scroll . draw ( f, area, & self . theme ) ;
105+
72106 Ok ( ( ) )
73107 }
74108}
@@ -84,6 +118,16 @@ impl Component for MsgPopup {
84118 true ,
85119 self . visible ,
86120 ) ) ;
121+ out. push (
122+ CommandInfo :: new (
123+ strings:: commands:: navigate_commit_message (
124+ & self . key_config ,
125+ ) ,
126+ true ,
127+ self . visible ,
128+ )
129+ . order ( order:: NAV ) ,
130+ ) ;
87131
88132 visibility_blocking ( self )
89133 }
@@ -93,6 +137,14 @@ impl Component for MsgPopup {
93137 if let Event :: Key ( e) = ev {
94138 if key_match ( e, self . key_config . keys . enter ) {
95139 self . hide ( ) ;
140+ } else if key_match (
141+ e,
142+ self . key_config . keys . popup_down ,
143+ ) {
144+ self . scroll . move_top ( ScrollType :: Down ) ;
145+ } else if key_match ( e, self . key_config . keys . popup_up )
146+ {
147+ self . scroll . move_top ( ScrollType :: Up ) ;
96148 }
97149 }
98150 Ok ( EventState :: Consumed )
@@ -124,24 +176,34 @@ impl MsgPopup {
124176 visible : false ,
125177 theme : env. theme . clone ( ) ,
126178 key_config : env. key_config . clone ( ) ,
179+ scroll : VerticalScroll :: new ( ) ,
127180 }
128181 }
129182
130- ///
131- pub fn show_error ( & mut self , msg : & str ) -> Result < ( ) > {
132- self . title = strings:: msg_title_error ( & self . key_config ) ;
183+ fn set_new_msg (
184+ & mut self ,
185+ msg : & str ,
186+ title : String ,
187+ ) -> Result < ( ) > {
188+ self . title = title;
133189 self . msg = msg. to_string ( ) ;
134- self . show ( ) ?;
190+ self . scroll . reset ( ) ;
191+ self . show ( )
192+ }
135193
136- Ok ( ( ) )
194+ ///
195+ pub fn show_error ( & mut self , msg : & str ) -> Result < ( ) > {
196+ self . set_new_msg (
197+ msg,
198+ strings:: msg_title_error ( & self . key_config ) ,
199+ )
137200 }
138201
139202 ///
140203 pub fn show_info ( & mut self , msg : & str ) -> Result < ( ) > {
141- self . title = strings:: msg_title_info ( & self . key_config ) ;
142- self . msg = msg. to_string ( ) ;
143- self . show ( ) ?;
144-
145- Ok ( ( ) )
204+ self . set_new_msg (
205+ msg,
206+ strings:: msg_title_info ( & self . key_config ) ,
207+ )
146208 }
147209}
0 commit comments