1
1
use crate :: components:: {
2
2
visibility_blocking, CommandBlocking , CommandInfo , Component ,
3
- DrawableComponent , EventState ,
3
+ DrawableComponent , EventState , ScrollType , VerticalScroll ,
4
4
} ;
5
+ use crate :: strings:: order;
5
6
use crate :: {
6
7
app:: Environment ,
7
8
keys:: { key_match, SharedKeyConfig } ,
8
9
strings, ui,
9
10
} ;
11
+ use anyhow:: Result ;
10
12
use crossterm:: event:: Event ;
13
+ use ratatui:: text:: Line ;
11
14
use ratatui:: {
12
15
layout:: { Alignment , Rect } ,
13
16
text:: Span ,
@@ -22,39 +25,68 @@ pub struct MsgPopup {
22
25
visible : bool ,
23
26
theme : SharedTheme ,
24
27
key_config : SharedKeyConfig ,
28
+ scroll : VerticalScroll ,
25
29
}
26
30
27
- use anyhow:: Result ;
31
+ const POPUP_HEIGHT : u16 = 25 ;
32
+ const BORDER_WIDTH : u16 = 2 ;
33
+ const MINIMUM_WIDTH : u16 = 60 ;
28
34
29
35
impl DrawableComponent for MsgPopup {
30
36
fn draw ( & self , f : & mut Frame , _rect : Rect ) -> Result < ( ) > {
31
37
if !self . visible {
32
38
return Ok ( ( ) ) ;
33
39
}
34
40
41
+ let max_width = f. size ( ) . width . max ( MINIMUM_WIDTH ) ;
42
+
35
43
// determine the maximum width of text block
36
- let lens = self
44
+ let width = self
37
45
. msg
38
- . split ( '\n' )
46
+ . lines ( )
39
47
. 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 > > ( ) ;
53
86
54
- let area = ui:: centered_rect_absolute ( width, 25 , f. size ( ) ) ;
55
87
f. render_widget ( Clear , area) ;
56
88
f. render_widget (
57
- Paragraph :: new ( self . msg . clone ( ) )
89
+ Paragraph :: new ( scrolled_lines )
58
90
. block (
59
91
Block :: default ( )
60
92
. title ( Span :: styled (
@@ -69,6 +101,8 @@ impl DrawableComponent for MsgPopup {
69
101
area,
70
102
) ;
71
103
104
+ self . scroll . draw ( f, area, & self . theme ) ;
105
+
72
106
Ok ( ( ) )
73
107
}
74
108
}
@@ -84,6 +118,16 @@ impl Component for MsgPopup {
84
118
true ,
85
119
self . visible ,
86
120
) ) ;
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
+ ) ;
87
131
88
132
visibility_blocking ( self )
89
133
}
@@ -93,6 +137,14 @@ impl Component for MsgPopup {
93
137
if let Event :: Key ( e) = ev {
94
138
if key_match ( e, self . key_config . keys . enter ) {
95
139
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 ) ;
96
148
}
97
149
}
98
150
Ok ( EventState :: Consumed )
@@ -124,24 +176,34 @@ impl MsgPopup {
124
176
visible : false ,
125
177
theme : env. theme . clone ( ) ,
126
178
key_config : env. key_config . clone ( ) ,
179
+ scroll : VerticalScroll :: new ( ) ,
127
180
}
128
181
}
129
182
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;
133
189
self . msg = msg. to_string ( ) ;
134
- self . show ( ) ?;
190
+ self . scroll . reset ( ) ;
191
+ self . show ( )
192
+ }
135
193
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
+ )
137
200
}
138
201
139
202
///
140
203
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
+ )
146
208
}
147
209
}
0 commit comments