@@ -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 codes according to the `Operating system commands`.
83
+ /// Currently only works in the Windows Terminal and ConEmu.
84
+ struct TaskbarProgress {
85
+ show : bool ,
86
+ error : bool ,
87
+ }
88
+
89
+ /// A taskbar progress value printable as ANSI OSC escape code
90
+ enum TaskbarValue {
91
+ /// Do not output anything
92
+ None ,
93
+ /// Remove progress
94
+ Remove ,
95
+ /// Progress value 0-100
96
+ Value ( f64 ) ,
97
+ /// Indeterminate state (no bar, just animation)
98
+ Indeterminate ,
99
+ Error ,
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
+ /// Creates a new `TaskbarProgress` from a cargo's config system.
113
+ ///
114
+ /// * `config == None` enables taskbar progress reporting on supported
115
+ /// terminal emulators (currently, Windows Terminal and ConEmu)
116
+ fn from_config ( config : Option < bool > , supported_terminal : bool ) -> Self {
117
+ let show = match config {
118
+ Some ( v) => v,
119
+ None => supported_terminal,
120
+ } ;
121
+
122
+ TaskbarProgress { show, error : false }
123
+ }
124
+
125
+ fn is_supported_terminal ( gctx : & GlobalContext ) -> bool {
126
+ gctx. get_env ( "WT_SESSION" ) . is_ok ( ) || gctx. get_env ( "ConEmuANSI" ) . ok ( ) == Some ( "ON" . into ( ) )
127
+ }
128
+
129
+ pub fn remove ( & self ) -> TaskbarValue {
130
+ if self . show {
131
+ TaskbarValue :: Remove
132
+ } else {
133
+ TaskbarValue :: None
134
+ }
135
+ }
136
+
137
+ pub fn value ( & self , percent : f64 ) -> TaskbarValue {
138
+ if self . show {
139
+ if self . error {
140
+ TaskbarValue :: Error
141
+ } else {
142
+ TaskbarValue :: Value ( percent)
143
+ }
144
+ } else {
145
+ TaskbarValue :: None
146
+ }
147
+ }
148
+
149
+ pub fn indeterminate ( & self ) -> TaskbarValue {
150
+ if self . show {
151
+ if self . error {
152
+ TaskbarValue :: Error
153
+ } else {
154
+ TaskbarValue :: Indeterminate
155
+ }
156
+ } else {
157
+ 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.
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 => ( 2 , 0.0 ) ,
181
+ } ;
182
+ write ! ( f, "\x1b ]9;4;{state};{progress:.0}\x1b \\ " )
183
+ }
77
184
}
78
185
79
186
impl < ' gctx > Progress < ' gctx > {
@@ -126,6 +233,10 @@ 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 (
237
+ progress_config. taskbar ,
238
+ TaskbarProgress :: is_supported_terminal ( gctx) ,
239
+ ) ,
129
240
} ,
130
241
name : name. to_string ( ) ,
131
242
done : false ,
@@ -223,7 +334,7 @@ impl<'gctx> Progress<'gctx> {
223
334
/// calling it too often.
224
335
pub fn print_now ( & mut self , msg : & str ) -> CargoResult < ( ) > {
225
336
match & mut self . state {
226
- Some ( s) => s. print ( "" , msg) ,
337
+ Some ( s) => s. print ( ProgressOutput :: PrintNow , msg) ,
227
338
None => Ok ( ( ) ) ,
228
339
}
229
340
}
@@ -234,6 +345,12 @@ impl<'gctx> Progress<'gctx> {
234
345
s. clear ( ) ;
235
346
}
236
347
}
348
+
349
+ pub fn indicate_error ( & mut self ) {
350
+ if let Some ( s) = & mut self . state {
351
+ s. format . taskbar . error ( )
352
+ }
353
+ }
237
354
}
238
355
239
356
impl Throttle {
@@ -269,6 +386,7 @@ impl Throttle {
269
386
impl < ' gctx > State < ' gctx > {
270
387
fn tick ( & mut self , cur : usize , max : usize , msg : & str ) -> CargoResult < ( ) > {
271
388
if self . done {
389
+ write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ?;
272
390
return Ok ( ( ) ) ;
273
391
}
274
392
@@ -279,22 +397,31 @@ impl<'gctx> State<'gctx> {
279
397
// Write out a pretty header, then the progress bar itself, and then
280
398
// return back to the beginning of the line for the next print.
281
399
self . try_update_max_width ( ) ;
282
- if let Some ( pbar ) = self . format . progress ( cur, max) {
283
- self . print ( & pbar , msg) ?;
400
+ if let Some ( progress ) = self . format . progress ( cur, max) {
401
+ self . print ( progress , msg) ?;
284
402
}
285
403
Ok ( ( ) )
286
404
}
287
405
288
- fn print ( & mut self , prefix : & str , msg : & str ) -> CargoResult < ( ) > {
406
+ fn print ( & mut self , progress : ProgressOutput , msg : & str ) -> CargoResult < ( ) > {
289
407
self . throttle . update ( ) ;
290
408
self . try_update_max_width ( ) ;
291
409
410
+ let ( mut line, taskbar) = match progress {
411
+ ProgressOutput :: PrintNow => ( String :: new ( ) , None ) ,
412
+ ProgressOutput :: TextAndTaskbar ( prefix, taskbar_value) => ( prefix, Some ( taskbar_value) ) ,
413
+ ProgressOutput :: Taskbar ( taskbar_value) => ( String :: new ( ) , Some ( taskbar_value) ) ,
414
+ } ;
415
+
292
416
// make sure we have enough room for the header
293
417
if self . format . max_width < 15 {
418
+ // even if we don't have space we can still output taskbar progress
419
+ if let Some ( tb) = taskbar {
420
+ write ! ( self . gctx. shell( ) . err( ) , "{}\r " , tb) ?;
421
+ }
294
422
return Ok ( ( ) ) ;
295
423
}
296
424
297
- let mut line = prefix. to_string ( ) ;
298
425
self . format . render ( & mut line, msg) ;
299
426
while line. len ( ) < self . format . max_width - 15 {
300
427
line. push ( ' ' ) ;
@@ -305,7 +432,11 @@ impl<'gctx> State<'gctx> {
305
432
let mut shell = self . gctx . shell ( ) ;
306
433
shell. set_needs_clear ( false ) ;
307
434
shell. status_header ( & self . name ) ?;
308
- write ! ( shell. err( ) , "{}\r " , line) ?;
435
+ if let Some ( tb) = taskbar {
436
+ write ! ( shell. err( ) , "{}{}\r " , line, tb) ?;
437
+ } else {
438
+ write ! ( shell. err( ) , "{}\r " , line) ?;
439
+ }
309
440
self . last_line = Some ( line) ;
310
441
shell. set_needs_clear ( true ) ;
311
442
}
@@ -314,6 +445,8 @@ impl<'gctx> State<'gctx> {
314
445
}
315
446
316
447
fn clear ( & mut self ) {
448
+ // Always clear the taskbar progress
449
+ let _ = write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ;
317
450
// No need to clear if the progress is not currently being displayed.
318
451
if self . last_line . is_some ( ) && !self . gctx . shell ( ) . is_cleared ( ) {
319
452
self . gctx . shell ( ) . err_erase_line ( ) ;
@@ -331,7 +464,7 @@ impl<'gctx> State<'gctx> {
331
464
}
332
465
333
466
impl Format {
334
- fn progress ( & self , cur : usize , max : usize ) -> Option < String > {
467
+ fn progress ( & self , cur : usize , max : usize ) -> Option < ProgressOutput > {
335
468
assert ! ( cur <= max) ;
336
469
// Render the percentage at the far right and then figure how long the
337
470
// progress bar is
@@ -342,8 +475,16 @@ impl Format {
342
475
ProgressStyle :: Ratio => format ! ( " {}/{}" , cur, max) ,
343
476
ProgressStyle :: Indeterminate => String :: new ( ) ,
344
477
} ;
478
+ let taskbar = match self . style {
479
+ ProgressStyle :: Percentage | ProgressStyle :: Ratio => self . taskbar . value ( pct * 100.0 ) ,
480
+ ProgressStyle :: Indeterminate => self . taskbar . indeterminate ( ) ,
481
+ } ;
482
+
345
483
let extra_len = stats. len ( ) + 2 /* [ and ] */ + 15 /* status header */ ;
346
484
let Some ( display_width) = self . width ( ) . checked_sub ( extra_len) else {
485
+ if self . taskbar . show {
486
+ return Some ( ProgressOutput :: Taskbar ( taskbar) ) ;
487
+ }
347
488
return None ;
348
489
} ;
349
490
@@ -371,7 +512,7 @@ impl Format {
371
512
string. push ( ']' ) ;
372
513
string. push_str ( & stats) ;
373
514
374
- Some ( string)
515
+ Some ( ProgressOutput :: TextAndTaskbar ( string, taskbar ) )
375
516
}
376
517
377
518
fn render ( & self , string : & mut String , msg : & str ) {
@@ -398,7 +539,11 @@ impl Format {
398
539
399
540
#[ cfg( test) ]
400
541
fn progress_status ( & self , cur : usize , max : usize , msg : & str ) -> Option < String > {
401
- let mut ret = self . progress ( cur, max) ?;
542
+ let mut ret = match self . progress ( cur, max) ? {
543
+ // Check only the variant that contains text
544
+ ProgressOutput :: TextAndTaskbar ( text, _) => text,
545
+ _ => return None ,
546
+ } ;
402
547
self . render ( & mut ret, msg) ;
403
548
Some ( ret)
404
549
}
@@ -420,6 +565,7 @@ fn test_progress_status() {
420
565
style : ProgressStyle :: Ratio ,
421
566
max_print : 40 ,
422
567
max_width : 60 ,
568
+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
423
569
} ;
424
570
assert_eq ! (
425
571
format. progress_status( 0 , 4 , "" ) ,
@@ -493,6 +639,7 @@ fn test_progress_status_percentage() {
493
639
style : ProgressStyle :: Percentage ,
494
640
max_print : 40 ,
495
641
max_width : 60 ,
642
+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
496
643
} ;
497
644
assert_eq ! (
498
645
format. progress_status( 0 , 77 , "" ) ,
@@ -518,6 +665,7 @@ fn test_progress_status_too_short() {
518
665
style : ProgressStyle :: Percentage ,
519
666
max_print : 25 ,
520
667
max_width : 25 ,
668
+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
521
669
} ;
522
670
assert_eq ! (
523
671
format. progress_status( 1 , 1 , "" ) ,
@@ -528,6 +676,7 @@ fn test_progress_status_too_short() {
528
676
style : ProgressStyle :: Percentage ,
529
677
max_print : 24 ,
530
678
max_width : 24 ,
679
+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
531
680
} ;
532
681
assert_eq ! ( format. progress_status( 1 , 1 , "" ) , None ) ;
533
682
}
0 commit comments