1
1
use crate :: {
2
2
components:: {
3
3
dialog_paragraph, utils:: time_to_string, CommandBlocking ,
4
- CommandInfo , Component , DrawableComponent ,
4
+ CommandInfo , Component , DrawableComponent , ScrollType ,
5
5
} ,
6
- strings,
6
+ keys,
7
+ strings:: { self , commands, order} ,
7
8
ui:: style:: SharedTheme ,
8
9
} ;
9
10
use anyhow:: Result ;
@@ -13,7 +14,7 @@ use asyncgit::{
13
14
} ;
14
15
use crossterm:: event:: Event ;
15
16
use itertools:: Itertools ;
16
- use std:: borrow:: Cow ;
17
+ use std:: { borrow:: Cow , cell :: Cell } ;
17
18
use sync:: CommitTags ;
18
19
use tui:: {
19
20
backend:: Backend ,
@@ -27,15 +28,21 @@ pub struct DetailsComponent {
27
28
data : Option < CommitDetails > ,
28
29
tags : Vec < String > ,
29
30
theme : SharedTheme ,
31
+ focused : bool ,
32
+ current_size : Cell < ( u16 , u16 ) > ,
33
+ selection : usize ,
30
34
}
31
35
32
36
impl DetailsComponent {
33
37
///
34
- pub const fn new ( theme : SharedTheme ) -> Self {
38
+ pub const fn new ( theme : SharedTheme , focused : bool ) -> Self {
35
39
Self {
36
40
data : None ,
37
41
tags : Vec :: new ( ) ,
38
42
theme,
43
+ focused,
44
+ current_size : Cell :: new ( ( 0 , 0 ) ) ,
45
+ selection : 0 ,
39
46
}
40
47
}
41
48
@@ -59,21 +66,76 @@ impl DetailsComponent {
59
66
Ok ( ( ) )
60
67
}
61
68
62
- fn get_text_message ( & self ) -> Vec < Text > {
69
+ fn get_number_of_lines ( & self , width : usize ) -> Option < usize > {
63
70
if let Some ( ref data) = self . data {
64
71
if let Some ( ref message) = data. message {
65
- let mut res = vec ! [ Text :: Styled (
66
- Cow :: from( message. subject. clone( ) ) ,
67
- self . theme
68
- . text( true , false )
69
- . modifier( Modifier :: BOLD ) ,
70
- ) ] ;
72
+ let wrapped_title =
73
+ textwrap:: wrap ( & message. subject , width) ;
71
74
72
75
if let Some ( ref body) = message. body {
73
- res. push ( Text :: Styled (
74
- Cow :: from ( body) ,
75
- self . theme . text ( true , false ) ,
76
- ) ) ;
76
+ let wrapped_message = textwrap:: wrap ( body, width) ;
77
+
78
+ return Some (
79
+ wrapped_title. len ( ) + wrapped_message. len ( )
80
+ - 1 ,
81
+ ) ;
82
+ }
83
+
84
+ return Some ( wrapped_title. len ( ) ) ;
85
+ }
86
+ }
87
+
88
+ None
89
+ }
90
+
91
+ fn get_wrapped_text_message ( & self , width : usize ) -> Vec < Text > {
92
+ if let Some ( ref data) = self . data {
93
+ if let Some ( ref message) = data. message {
94
+ let wrapped_title =
95
+ textwrap:: wrap ( & message. subject , width) ;
96
+
97
+ let mut res: Vec < Text > = wrapped_title
98
+ . iter ( )
99
+ . enumerate ( )
100
+ . map ( |( i, line) | {
101
+ let line_with_newline = format ! ( "{}\n " , line) ;
102
+
103
+ Text :: Styled (
104
+ line_with_newline. into ( ) ,
105
+ self . theme
106
+ . text (
107
+ true ,
108
+ self . focused
109
+ && i == self . selection ,
110
+ )
111
+ . modifier ( Modifier :: BOLD ) ,
112
+ )
113
+ } )
114
+ . collect ( ) ;
115
+
116
+ if let Some ( ref body) = message. body {
117
+ let wrapped_message = textwrap:: wrap ( body, width) ;
118
+
119
+ res. extend (
120
+ wrapped_message. iter ( ) . enumerate ( ) . map (
121
+ |( i, line) | {
122
+ let line_with_newline =
123
+ format ! ( "{}\n " , line) ;
124
+
125
+ Text :: Styled (
126
+ line_with_newline. into ( ) ,
127
+ self . theme
128
+ . text (
129
+ true ,
130
+ self . focused
131
+ && i == self
132
+ . selection ,
133
+ )
134
+ . modifier ( Modifier :: BOLD ) ,
135
+ )
136
+ } ,
137
+ ) ,
138
+ ) ;
77
139
}
78
140
79
141
return res;
@@ -181,6 +243,39 @@ impl DetailsComponent {
181
243
vec ! [ ]
182
244
}
183
245
}
246
+
247
+ fn move_selection (
248
+ & mut self ,
249
+ move_type : ScrollType ,
250
+ ) -> Result < bool > {
251
+ if self . data . is_some ( ) {
252
+ let old = self . selection ;
253
+ let width = self . current_size . get ( ) . 0 as usize ;
254
+
255
+ if let Some ( number_of_lines) =
256
+ self . get_number_of_lines ( width)
257
+ {
258
+ let max = number_of_lines. saturating_sub ( 1 ) as usize ;
259
+
260
+ let new_selection = match move_type {
261
+ ScrollType :: Down => old. saturating_add ( 1 ) ,
262
+ ScrollType :: Up => old. saturating_sub ( 1 ) ,
263
+ ScrollType :: Home => 0 ,
264
+ ScrollType :: End => max,
265
+ _ => old,
266
+ } ;
267
+
268
+ if new_selection > max {
269
+ return Ok ( false ) ;
270
+ }
271
+
272
+ self . selection = new_selection;
273
+
274
+ return Ok ( true ) ;
275
+ }
276
+ }
277
+ Ok ( false )
278
+ }
184
279
}
185
280
186
281
impl DrawableComponent for DetailsComponent {
@@ -189,6 +284,15 @@ impl DrawableComponent for DetailsComponent {
189
284
f : & mut Frame < B > ,
190
285
rect : Rect ,
191
286
) -> Result < ( ) > {
287
+ // We have to take the border into account which is one character on
288
+ // each side.
289
+ let border_width = 2 ;
290
+
291
+ self . current_size . set ( (
292
+ rect. width . saturating_sub ( border_width) ,
293
+ rect. height . saturating_sub ( border_width) ,
294
+ ) ) ;
295
+
192
296
let chunks = Layout :: default ( )
193
297
. direction ( Direction :: Vertical )
194
298
. constraints (
@@ -206,14 +310,17 @@ impl DrawableComponent for DetailsComponent {
206
310
chunks[ 0 ] ,
207
311
) ;
208
312
313
+ let wrapped_lines = self . get_wrapped_text_message (
314
+ self . current_size . get ( ) . 0 as usize ,
315
+ ) ;
316
+
209
317
f. render_widget (
210
318
dialog_paragraph (
211
319
strings:: commit:: DETAILS_MESSAGE_TITLE ,
212
- self . get_text_message ( ) . iter ( ) ,
320
+ wrapped_lines . iter ( ) ,
213
321
& self . theme ,
214
- false ,
215
- )
216
- . wrap ( true ) ,
322
+ self . focused ,
323
+ ) ,
217
324
chunks[ 1 ] ,
218
325
) ;
219
326
@@ -224,14 +331,65 @@ impl DrawableComponent for DetailsComponent {
224
331
impl Component for DetailsComponent {
225
332
fn commands (
226
333
& self ,
227
- _out : & mut Vec < CommandInfo > ,
228
- _force_all : bool ,
334
+ out : & mut Vec < CommandInfo > ,
335
+ force_all : bool ,
229
336
) -> CommandBlocking {
230
337
// visibility_blocking(self)
338
+
339
+ let width = self . current_size . get ( ) . 0 as usize ;
340
+ let number_of_lines = self . get_number_of_lines ( width) ;
341
+
342
+ out. push (
343
+ CommandInfo :: new (
344
+ commands:: NAVIGATE_COMMIT_MESSAGE ,
345
+ number_of_lines. is_some ( ) ,
346
+ self . focused || force_all,
347
+ )
348
+ . order ( order:: NAV ) ,
349
+ ) ;
350
+
231
351
CommandBlocking :: PassingOn
232
352
}
233
353
234
- fn event ( & mut self , _ev : Event ) -> Result < bool > {
354
+ fn event ( & mut self , event : Event ) -> Result < bool > {
355
+ if self . focused {
356
+ if let Event :: Key ( e) = event {
357
+ return match e {
358
+ keys:: MOVE_UP => {
359
+ self . move_selection ( ScrollType :: Up )
360
+ }
361
+ keys:: MOVE_DOWN => {
362
+ self . move_selection ( ScrollType :: Down )
363
+ }
364
+ keys:: HOME | keys:: SHIFT_UP => {
365
+ self . move_selection ( ScrollType :: Home )
366
+ }
367
+ keys:: END | keys:: SHIFT_DOWN => {
368
+ self . move_selection ( ScrollType :: End )
369
+ }
370
+ _ => Ok ( false ) ,
371
+ } ;
372
+ }
373
+ }
374
+
235
375
Ok ( false )
236
376
}
377
+
378
+ fn focused ( & self ) -> bool {
379
+ self . focused
380
+ }
381
+
382
+ fn focus ( & mut self , focus : bool ) {
383
+ if focus {
384
+ let width = self . current_size . get ( ) . 0 as usize ;
385
+
386
+ if let Some ( number_of_lines) =
387
+ self . get_number_of_lines ( width)
388
+ {
389
+ self . selection = number_of_lines. saturating_sub ( 1 ) ;
390
+ }
391
+ }
392
+
393
+ self . focused = focus;
394
+ }
237
395
}
0 commit comments