@@ -14,18 +14,21 @@ use ratatui::widgets::Paragraph;
1414
1515use crate :: app:: { Action , App , STALE_LOCK , Target } ;
1616use crate :: model:: { Head , Leak , LeakSource , RepoOp , Snapshot } ;
17- use fmt:: { age_changes_in, rel_age, truncate_right, width} ;
17+ use fmt:: { age_changes_in, rel_age, truncate_right, width, wrap_words } ;
1818use layout:: Density ;
1919use theme:: Theme ;
2020
2121const SPINNER : [ & str ; 10 ] = [ "⠋" , "⠙" , "⠹" , "⠸" , "⠼" , "⠴" , "⠦" , "⠧" , "⠇" , "⠏" ] ;
22+ /// Most rows a long warning wraps onto.
23+ const WARNING_LINES : usize = 3 ;
2224
2325/// Draws the whole UI and records the hit map for the next input.
2426pub fn draw ( f : & mut Frame , app : & mut App , theme : & Theme ) {
2527 app. hits . clear ( ) ;
2628 app. nav . clear ( ) ;
2729 app. row_targets . clear ( ) ;
2830 app. detail_targets . clear ( ) ;
31+ app. clearable . clear ( ) ;
2932 app. age_wake . set ( None ) ;
3033 let area = f. area ( ) ;
3134 if area. width == 0 || area. height == 0 {
@@ -54,6 +57,7 @@ pub fn draw(f: &mut Frame, app: &mut App, theme: &Theme) {
5457 let mut y = area. y ;
5558 let mut left = area. height ;
5659 let max_warnings = match area. height {
60+ h if h >= 12 => 3 ,
5761 h if h >= 8 => 2 ,
5862 h if h >= 5 => 1 ,
5963 _ => 0 ,
@@ -83,19 +87,41 @@ pub fn draw(f: &mut Frame, app: &mut App, theme: &Theme) {
8387 let mut band = None ;
8488 let mut error_shown = false ;
8589 let rows = max_warnings. min ( ( left - hint_rows) as usize ) ;
86- for w in fit_warnings ( warnings ( app, & snap, theme, area. width as usize ) , rows) {
87- let r = Rect :: new ( area. x , y, area. width , 1 ) ;
88- f. render_widget (
89- Paragraph :: new ( fit_line ( w. line , area. width as usize ) ) . style ( w. fill ) ,
90- r,
91- ) ;
90+ let mut all = warnings ( app, & snap, theme, area. width as usize ) ;
91+ // A cleared warning that has gone shows again if it comes back.
92+ app. cleared
93+ . retain ( |k| all. iter ( ) . any ( |w| w. key . as_ref ( ) == Some ( k) ) ) ;
94+ all. retain ( |w| !w. key . as_ref ( ) . is_some_and ( |k| app. cleared . contains ( k) ) ) ;
95+ app. clearable = all. iter ( ) . filter_map ( |w| w. key . clone ( ) ) . collect ( ) ;
96+ for w in & mut all {
97+ if let Some ( k) = & w. key {
98+ w. action = Some ( Action :: Clear ( vec ! [ k. clone( ) ] ) ) ;
99+ }
100+ }
101+ let shown = fit_warnings ( all, rows) ;
102+ // Rows the warnings leave free let a long one wrap.
103+ let mut spare = rows - shown. len ( ) ;
104+ for w in shown {
105+ let lines = match & w. text {
106+ Some ( ( text, style) ) => {
107+ let max = 1 + spare. min ( WARNING_LINES - 1 ) ;
108+ let lines = warning_lines ( text, * style, area. width as usize , max) ;
109+ spare -= lines. len ( ) - 1 ;
110+ lines
111+ }
112+ None => vec ! [ fit_line( w. line, area. width as usize ) ] ,
113+ } ;
114+ let r = Rect :: new ( area. x , y, area. width , lines. len ( ) as u16 ) ;
115+ f. render_widget ( Paragraph :: new ( lines) . style ( w. fill ) , r) ;
92116 if let Some ( action) = w. action {
117+ if matches ! ( action, Action :: Open ( _) ) {
118+ band = Some ( r) ;
119+ }
93120 app. hits . clicks . push ( ( r, action) ) ;
94- band = Some ( r) ;
95121 }
96122 error_shown |= w. error ;
97- y += 1 ;
98- left -= 1 ;
123+ y += r . height ;
124+ left -= r . height ;
99125 }
100126
101127 let body = Rect :: new ( area. x , y, area. width , left - hint_rows) ;
@@ -353,6 +379,9 @@ struct Warning {
353379 text : Option < ( String , Style ) > ,
354380 /// The refresh error: the body below is stale.
355381 error : bool ,
382+ /// What clearing it remembers; `None` for the secrets band and the
383+ /// refresh error, which cannot be cleared.
384+ key : Option < String > ,
356385}
357386
358387/// Fits `warnings` into `rows`: when there are more, the last row carries
@@ -372,26 +401,45 @@ fn fit_warnings(mut warnings: Vec<Warning>, rows: usize) -> Vec<Warning> {
372401 . iter ( )
373402 . filter_map ( |w| w. text . as_ref ( ) . map ( |t| t. 0 . as_str ( ) ) )
374403 . collect ( ) ;
404+ let keys: Vec < String > = rest. iter ( ) . filter_map ( |w| w. key . clone ( ) ) . collect ( ) ;
375405 warnings. push ( Warning {
376406 line : Line :: from ( Span :: styled ( format ! ( " ⚠ {}" , joined. join( " · " ) ) , style) ) ,
377407 fill : Style :: new ( ) ,
378- action : None ,
408+ action : ( !keys . is_empty ( ) ) . then_some ( Action :: Clear ( keys ) ) ,
379409 text : None ,
380410 error : rest. iter ( ) . any ( |w| w. error ) ,
411+ key : None ,
381412 } ) ;
382413 }
383414 None => { }
384415 }
385416 warnings
386417}
387418
419+ /// `text` after the warning glyph, wrapped onto at most `max` rows of
420+ /// `cols` cells, the later rows lined up under the first.
421+ fn warning_lines ( text : & str , style : Style , cols : usize , max : usize ) -> Vec < Line < ' static > > {
422+ wrap_words ( text, cols. saturating_sub ( 3 ) , max)
423+ . into_iter ( )
424+ . enumerate ( )
425+ . map ( |( i, part) | {
426+ let lead = if i == 0 { " ⚠ " } else { " " } ;
427+ fit_line (
428+ Line :: from ( Span :: styled ( format ! ( "{lead}{part}" ) , style) ) ,
429+ cols,
430+ )
431+ } )
432+ . collect ( )
433+ }
434+
388435fn warnings ( app : & App , snap : & Snapshot , theme : & Theme , cols : usize ) -> Vec < Warning > {
389436 let err = Style :: new ( ) . fg ( theme. err ) . add_modifier ( Modifier :: BOLD ) ;
390437 let warn = Style :: new ( ) . fg ( theme. warn ) ;
391438 let line = |text : String , style : Style | Warning {
392439 line : Line :: from ( Span :: styled ( format ! ( " ⚠ {text}" ) , style) ) ,
393440 fill : Style :: new ( ) ,
394441 action : None ,
442+ key : Some ( text. clone ( ) ) ,
395443 text : Some ( ( text, style) ) ,
396444 error : false ,
397445 } ;
@@ -404,12 +452,14 @@ fn warnings(app: &App, snap: &Snapshot, theme: &Theme, cols: usize) -> Vec<Warni
404452 action : Some ( Action :: Open ( Target :: Leaks ) ) ,
405453 text : None ,
406454 error : false ,
455+ key : None ,
407456 } ) ;
408457 }
409458 // Next, so that it is never what a shared row cuts off.
410459 if let Some ( e) = & app. error {
411460 out. push ( Warning {
412461 error : true ,
462+ key : None ,
413463 ..line ( e. clone ( ) , err)
414464 } ) ;
415465 }
@@ -425,7 +475,11 @@ fn warnings(app: &App, snap: &Snapshot, theme: &Theme, cols: usize) -> Vec<Warni
425475 out. push ( line ( format ! ( "{conflicts} conflict{s}" ) , err) ) ;
426476 }
427477 if let Some ( age) = app. lock_age ( ) . filter ( |a| * a >= STALE_LOCK ) {
428- out. push ( line ( format ! ( "index.lock held {}" , age_for( app, age) ) , warn) ) ;
478+ // Keyed without the age, which changes each second.
479+ out. push ( Warning {
480+ key : Some ( "index.lock" . into ( ) ) ,
481+ ..line ( format ! ( "index.lock held {}" , age_for( app, age) ) , warn)
482+ } ) ;
429483 }
430484 if let Some ( e) = & app. fetch . last_error {
431485 out. push ( line ( format ! ( "fetch failed: {}" , e. label( ) ) , warn) ) ;
@@ -440,23 +494,26 @@ fn warnings(app: &App, snap: &Snapshot, theme: &Theme, cols: usize) -> Vec<Warni
440494}
441495
442496fn hint_bar ( f : & mut Frame , app : & mut App , theme : & Theme , r : Rect ) {
443- let items: & [ ( & str , & str , Action ) ] = if app. stack . is_empty ( ) {
444- & [
497+ let mut items: Vec < ( & str , & str , Action ) > = if app. stack . is_empty ( ) {
498+ vec ! [
445499 ( "?" , "help" , Action :: Help ) ,
446500 ( "f" , "fetch" , Action :: Fetch ) ,
447501 ( "q" , "quit" , Action :: Quit ) ,
448502 ]
449503 } else {
450- & [
504+ vec ! [
451505 ( "esc" , "back" , Action :: Back ) ,
452506 ( "w" , "wrap" , Action :: ToggleWrap ) ,
453507 ( "q" , "quit" , Action :: Quit ) ,
454508 ]
455509 } ;
510+ if !app. clearable . is_empty ( ) {
511+ items. insert ( 2 , ( "c" , "clear" , Action :: Clear ( app. clearable . clone ( ) ) ) ) ;
512+ }
456513 let key_style = Style :: new ( ) . fg ( theme. accent ) . add_modifier ( Modifier :: BOLD ) ;
457514 let mut spans = Vec :: new ( ) ;
458515 let mut x = 0usize ;
459- for ( key, label, action) in items {
516+ for ( key, label, action) in & items {
460517 let item_w = 1 + width ( key) + 1 + width ( label) + 1 ;
461518 if x + item_w > r. width as usize {
462519 break ;
0 commit comments