@@ -74,6 +74,113 @@ struct Format {
74
74
style : ProgressStyle ,
75
75
max_width : usize ,
76
76
max_print : usize ,
77
+ taskbar : TaskbarProgress ,
78
+ }
79
+
80
+ /// Taskbar progressbar
81
+ ///
82
+ /// Outputs ANSI sequences according to the `Operating system commands`.
83
+ struct TaskbarProgress {
84
+ enabled : bool ,
85
+ error : bool ,
86
+ }
87
+
88
+ /// A taskbar progress value printable as ANSI OSC escape code
89
+ enum TaskbarValue {
90
+ /// Do not output anything
91
+ None ,
92
+ /// Remove progress
93
+ Remove ,
94
+ /// Progress value 0-100
95
+ Value ( f64 ) ,
96
+ /// Indeterminate state (no bar, just animation)
97
+ Indeterminate ,
98
+ /// Progress value 0-100 in the error state
99
+ Error ( f64 ) ,
100
+ }
101
+
102
+ enum ProgressOutput {
103
+ /// Print progress without a message
104
+ PrintNow ,
105
+ /// Progress, message and taskbar progress
106
+ TextAndTaskbar ( String , TaskbarValue ) ,
107
+ /// Only taskbar progress, no message and no text progress
108
+ Taskbar ( TaskbarValue ) ,
109
+ }
110
+
111
+ impl TaskbarProgress {
112
+ #[ cfg( test) ]
113
+ fn new ( enabled : bool ) -> Self {
114
+ Self {
115
+ enabled,
116
+ error : false ,
117
+ }
118
+ }
119
+
120
+ /// Creates a new `TaskbarProgress` from a cargo's config.
121
+ /// If not explicitly enabled or disabled, detect a supported terminal.
122
+ fn from_config ( gctx : & GlobalContext ) -> Self {
123
+ let enabled = match gctx. progress_config ( ) . taskbar {
124
+ Some ( v) => v,
125
+ None => {
126
+ gctx. get_env ( "WT_SESSION" ) . is_ok ( )
127
+ || gctx. get_env ( "ConEmuANSI" ) . ok ( ) == Some ( "ON" . into ( ) )
128
+ }
129
+ } ;
130
+
131
+ Self {
132
+ enabled,
133
+ error : false ,
134
+ }
135
+ }
136
+
137
+ pub fn remove ( & self ) -> TaskbarValue {
138
+ if self . enabled {
139
+ TaskbarValue :: Remove
140
+ } else {
141
+ TaskbarValue :: None
142
+ }
143
+ }
144
+
145
+ pub fn value ( & self , percent : f64 ) -> TaskbarValue {
146
+ match ( self . enabled , self . error ) {
147
+ ( true , false ) => TaskbarValue :: Value ( percent) ,
148
+ ( true , true ) => TaskbarValue :: Error ( percent) ,
149
+ ( false , _) => TaskbarValue :: None ,
150
+ }
151
+ }
152
+
153
+ pub fn indeterminate ( & self ) -> TaskbarValue {
154
+ match ( self . enabled , self . error ) {
155
+ ( true , false ) => TaskbarValue :: Indeterminate ,
156
+ ( true , true ) => TaskbarValue :: Error ( 100.0 ) ,
157
+ ( false , _) => TaskbarValue :: None ,
158
+ }
159
+ }
160
+
161
+ pub fn error ( & mut self ) {
162
+ self . error = true ;
163
+ }
164
+ }
165
+
166
+ impl std:: fmt:: Display for TaskbarValue {
167
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
168
+ // From https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
169
+ // ESC ] 9 ; 4 ; st ; pr ST
170
+ // When st is 0: remove progress.
171
+ // When st is 1: set progress value to pr (number, 0-100).
172
+ // When st is 2: set error state in taskbar, pr is optional.
173
+ // When st is 3: set indeterminate state, pr is ignored.
174
+ // When st is 4: set paused state, pr is optional.
175
+ let ( state, progress) = match self {
176
+ Self :: None => return Ok ( ( ) ) ,
177
+ Self :: Remove => ( 0 , 0.0 ) ,
178
+ Self :: Value ( v) => ( 1 , * v) ,
179
+ Self :: Indeterminate => ( 3 , 0.0 ) ,
180
+ Self :: Error ( v) => ( 2 , * v) ,
181
+ } ;
182
+ write ! ( f, "\x1b ]9;4;{state};{progress:.0}\x07 " )
183
+ }
77
184
}
78
185
79
186
impl < ' gctx > Progress < ' gctx > {
@@ -126,6 +233,7 @@ impl<'gctx> Progress<'gctx> {
126
233
// 50 gives some space for text after the progress bar,
127
234
// even on narrow (e.g. 80 char) terminals.
128
235
max_print : 50 ,
236
+ taskbar : TaskbarProgress :: from_config ( gctx) ,
129
237
} ,
130
238
name : name. to_string ( ) ,
131
239
done : false ,
@@ -223,7 +331,7 @@ impl<'gctx> Progress<'gctx> {
223
331
/// calling it too often.
224
332
pub fn print_now ( & mut self , msg : & str ) -> CargoResult < ( ) > {
225
333
match & mut self . state {
226
- Some ( s) => s. print ( "" , msg) ,
334
+ Some ( s) => s. print ( ProgressOutput :: PrintNow , msg) ,
227
335
None => Ok ( ( ) ) ,
228
336
}
229
337
}
@@ -234,6 +342,13 @@ impl<'gctx> Progress<'gctx> {
234
342
s. clear ( ) ;
235
343
}
236
344
}
345
+
346
+ /// Sets the taskbar progress to the error state.
347
+ pub fn indicate_error ( & mut self ) {
348
+ if let Some ( s) = & mut self . state {
349
+ s. format . taskbar . error ( )
350
+ }
351
+ }
237
352
}
238
353
239
354
impl Throttle {
@@ -269,6 +384,7 @@ impl Throttle {
269
384
impl < ' gctx > State < ' gctx > {
270
385
fn tick ( & mut self , cur : usize , max : usize , msg : & str ) -> CargoResult < ( ) > {
271
386
if self . done {
387
+ write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ?;
272
388
return Ok ( ( ) ) ;
273
389
}
274
390
@@ -279,22 +395,31 @@ impl<'gctx> State<'gctx> {
279
395
// Write out a pretty header, then the progress bar itself, and then
280
396
// return back to the beginning of the line for the next print.
281
397
self . try_update_max_width ( ) ;
282
- if let Some ( pbar ) = self . format . progress ( cur, max) {
283
- self . print ( & pbar , msg) ?;
398
+ if let Some ( progress ) = self . format . progress ( cur, max) {
399
+ self . print ( progress , msg) ?;
284
400
}
285
401
Ok ( ( ) )
286
402
}
287
403
288
- fn print ( & mut self , prefix : & str , msg : & str ) -> CargoResult < ( ) > {
404
+ fn print ( & mut self , progress : ProgressOutput , msg : & str ) -> CargoResult < ( ) > {
289
405
self . throttle . update ( ) ;
290
406
self . try_update_max_width ( ) ;
291
407
408
+ let ( mut line, taskbar) = match progress {
409
+ ProgressOutput :: PrintNow => ( String :: new ( ) , None ) ,
410
+ ProgressOutput :: TextAndTaskbar ( prefix, taskbar_value) => ( prefix, Some ( taskbar_value) ) ,
411
+ ProgressOutput :: Taskbar ( taskbar_value) => ( String :: new ( ) , Some ( taskbar_value) ) ,
412
+ } ;
413
+
292
414
// make sure we have enough room for the header
293
415
if self . format . max_width < 15 {
416
+ // even if we don't have space we can still output taskbar progress
417
+ if let Some ( tb) = taskbar {
418
+ write ! ( self . gctx. shell( ) . err( ) , "{}\r " , tb) ?;
419
+ }
294
420
return Ok ( ( ) ) ;
295
421
}
296
422
297
- let mut line = prefix. to_string ( ) ;
298
423
self . format . render ( & mut line, msg) ;
299
424
while line. len ( ) < self . format . max_width - 15 {
300
425
line. push ( ' ' ) ;
@@ -305,7 +430,11 @@ impl<'gctx> State<'gctx> {
305
430
let mut shell = self . gctx . shell ( ) ;
306
431
shell. set_needs_clear ( false ) ;
307
432
shell. status_header ( & self . name ) ?;
308
- write ! ( shell. err( ) , "{}\r " , line) ?;
433
+ if let Some ( tb) = taskbar {
434
+ write ! ( shell. err( ) , "{}{}\r " , line, tb) ?;
435
+ } else {
436
+ write ! ( shell. err( ) , "{}\r " , line) ?;
437
+ }
309
438
self . last_line = Some ( line) ;
310
439
shell. set_needs_clear ( true ) ;
311
440
}
@@ -314,6 +443,8 @@ impl<'gctx> State<'gctx> {
314
443
}
315
444
316
445
fn clear ( & mut self ) {
446
+ // Always clear the taskbar progress
447
+ let _ = write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ;
317
448
// No need to clear if the progress is not currently being displayed.
318
449
if self . last_line . is_some ( ) && !self . gctx . shell ( ) . is_cleared ( ) {
319
450
self . gctx . shell ( ) . err_erase_line ( ) ;
@@ -331,7 +462,7 @@ impl<'gctx> State<'gctx> {
331
462
}
332
463
333
464
impl Format {
334
- fn progress ( & self , cur : usize , max : usize ) -> Option < String > {
465
+ fn progress ( & self , cur : usize , max : usize ) -> Option < ProgressOutput > {
335
466
assert ! ( cur <= max) ;
336
467
// Render the percentage at the far right and then figure how long the
337
468
// progress bar is
@@ -342,8 +473,16 @@ impl Format {
342
473
ProgressStyle :: Ratio => format ! ( " {}/{}" , cur, max) ,
343
474
ProgressStyle :: Indeterminate => String :: new ( ) ,
344
475
} ;
476
+ let taskbar = match self . style {
477
+ ProgressStyle :: Percentage | ProgressStyle :: Ratio => self . taskbar . value ( pct * 100.0 ) ,
478
+ ProgressStyle :: Indeterminate => self . taskbar . indeterminate ( ) ,
479
+ } ;
480
+
345
481
let extra_len = stats. len ( ) + 2 /* [ and ] */ + 15 /* status header */ ;
346
482
let Some ( display_width) = self . width ( ) . checked_sub ( extra_len) else {
483
+ if self . taskbar . enabled {
484
+ return Some ( ProgressOutput :: Taskbar ( taskbar) ) ;
485
+ }
347
486
return None ;
348
487
} ;
349
488
@@ -371,7 +510,7 @@ impl Format {
371
510
string. push ( ']' ) ;
372
511
string. push_str ( & stats) ;
373
512
374
- Some ( string)
513
+ Some ( ProgressOutput :: TextAndTaskbar ( string, taskbar ) )
375
514
}
376
515
377
516
fn render ( & self , string : & mut String , msg : & str ) {
@@ -398,7 +537,11 @@ impl Format {
398
537
399
538
#[ cfg( test) ]
400
539
fn progress_status ( & self , cur : usize , max : usize , msg : & str ) -> Option < String > {
401
- let mut ret = self . progress ( cur, max) ?;
540
+ let mut ret = match self . progress ( cur, max) ? {
541
+ // Check only the variant that contains text
542
+ ProgressOutput :: TextAndTaskbar ( text, _) => text,
543
+ _ => return None ,
544
+ } ;
402
545
self . render ( & mut ret, msg) ;
403
546
Some ( ret)
404
547
}
@@ -420,6 +563,7 @@ fn test_progress_status() {
420
563
style : ProgressStyle :: Ratio ,
421
564
max_print : 40 ,
422
565
max_width : 60 ,
566
+ taskbar : TaskbarProgress :: new ( false ) ,
423
567
} ;
424
568
assert_eq ! (
425
569
format. progress_status( 0 , 4 , "" ) ,
@@ -493,6 +637,7 @@ fn test_progress_status_percentage() {
493
637
style : ProgressStyle :: Percentage ,
494
638
max_print : 40 ,
495
639
max_width : 60 ,
640
+ taskbar : TaskbarProgress :: new ( false ) ,
496
641
} ;
497
642
assert_eq ! (
498
643
format. progress_status( 0 , 77 , "" ) ,
@@ -518,6 +663,7 @@ fn test_progress_status_too_short() {
518
663
style : ProgressStyle :: Percentage ,
519
664
max_print : 25 ,
520
665
max_width : 25 ,
666
+ taskbar : TaskbarProgress :: new ( false ) ,
521
667
} ;
522
668
assert_eq ! (
523
669
format. progress_status( 1 , 1 , "" ) ,
@@ -528,6 +674,7 @@ fn test_progress_status_too_short() {
528
674
style : ProgressStyle :: Percentage ,
529
675
max_print : 24 ,
530
676
max_width : 24 ,
677
+ taskbar : TaskbarProgress :: new ( false ) ,
531
678
} ;
532
679
assert_eq ! ( format. progress_status( 1 , 1 , "" ) , None ) ;
533
680
}
0 commit comments