@@ -74,6 +74,113 @@ struct Format {
7474 style : ProgressStyle ,
7575 max_width : usize ,
7676 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+ }
77184}
78185
79186impl < ' gctx > Progress < ' gctx > {
@@ -126,6 +233,10 @@ impl<'gctx> Progress<'gctx> {
126233 // 50 gives some space for text after the progress bar,
127234 // even on narrow (e.g. 80 char) terminals.
128235 max_print : 50 ,
236+ taskbar : TaskbarProgress :: from_config (
237+ progress_config. taskbar ,
238+ TaskbarProgress :: is_supported_terminal ( gctx) ,
239+ ) ,
129240 } ,
130241 name : name. to_string ( ) ,
131242 done : false ,
@@ -223,7 +334,7 @@ impl<'gctx> Progress<'gctx> {
223334 /// calling it too often.
224335 pub fn print_now ( & mut self , msg : & str ) -> CargoResult < ( ) > {
225336 match & mut self . state {
226- Some ( s) => s. print ( "" , msg) ,
337+ Some ( s) => s. print ( ProgressOutput :: PrintNow , msg) ,
227338 None => Ok ( ( ) ) ,
228339 }
229340 }
@@ -234,6 +345,12 @@ impl<'gctx> Progress<'gctx> {
234345 s. clear ( ) ;
235346 }
236347 }
348+
349+ pub fn indicate_error ( & mut self ) {
350+ if let Some ( s) = & mut self . state {
351+ s. format . taskbar . error ( )
352+ }
353+ }
237354}
238355
239356impl Throttle {
@@ -269,6 +386,7 @@ impl Throttle {
269386impl < ' gctx > State < ' gctx > {
270387 fn tick ( & mut self , cur : usize , max : usize , msg : & str ) -> CargoResult < ( ) > {
271388 if self . done {
389+ write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ?;
272390 return Ok ( ( ) ) ;
273391 }
274392
@@ -279,22 +397,31 @@ impl<'gctx> State<'gctx> {
279397 // Write out a pretty header, then the progress bar itself, and then
280398 // return back to the beginning of the line for the next print.
281399 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) ?;
284402 }
285403 Ok ( ( ) )
286404 }
287405
288- fn print ( & mut self , prefix : & str , msg : & str ) -> CargoResult < ( ) > {
406+ fn print ( & mut self , progress : ProgressOutput , msg : & str ) -> CargoResult < ( ) > {
289407 self . throttle . update ( ) ;
290408 self . try_update_max_width ( ) ;
291409
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+
292416 // make sure we have enough room for the header
293417 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+ }
294422 return Ok ( ( ) ) ;
295423 }
296424
297- let mut line = prefix. to_string ( ) ;
298425 self . format . render ( & mut line, msg) ;
299426 while line. len ( ) < self . format . max_width - 15 {
300427 line. push ( ' ' ) ;
@@ -305,7 +432,11 @@ impl<'gctx> State<'gctx> {
305432 let mut shell = self . gctx . shell ( ) ;
306433 shell. set_needs_clear ( false ) ;
307434 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+ }
309440 self . last_line = Some ( line) ;
310441 shell. set_needs_clear ( true ) ;
311442 }
@@ -314,6 +445,8 @@ impl<'gctx> State<'gctx> {
314445 }
315446
316447 fn clear ( & mut self ) {
448+ // Always clear the taskbar progress
449+ let _ = write ! ( self . gctx. shell( ) . err( ) , "{}" , self . format. taskbar. remove( ) ) ;
317450 // No need to clear if the progress is not currently being displayed.
318451 if self . last_line . is_some ( ) && !self . gctx . shell ( ) . is_cleared ( ) {
319452 self . gctx . shell ( ) . err_erase_line ( ) ;
@@ -331,7 +464,7 @@ impl<'gctx> State<'gctx> {
331464}
332465
333466impl Format {
334- fn progress ( & self , cur : usize , max : usize ) -> Option < String > {
467+ fn progress ( & self , cur : usize , max : usize ) -> Option < ProgressOutput > {
335468 assert ! ( cur <= max) ;
336469 // Render the percentage at the far right and then figure how long the
337470 // progress bar is
@@ -342,8 +475,16 @@ impl Format {
342475 ProgressStyle :: Ratio => format ! ( " {}/{}" , cur, max) ,
343476 ProgressStyle :: Indeterminate => String :: new ( ) ,
344477 } ;
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+
345483 let extra_len = stats. len ( ) + 2 /* [ and ] */ + 15 /* status header */ ;
346484 let Some ( display_width) = self . width ( ) . checked_sub ( extra_len) else {
485+ if self . taskbar . show {
486+ return Some ( ProgressOutput :: Taskbar ( taskbar) ) ;
487+ }
347488 return None ;
348489 } ;
349490
@@ -371,7 +512,7 @@ impl Format {
371512 string. push ( ']' ) ;
372513 string. push_str ( & stats) ;
373514
374- Some ( string)
515+ Some ( ProgressOutput :: TextAndTaskbar ( string, taskbar ) )
375516 }
376517
377518 fn render ( & self , string : & mut String , msg : & str ) {
@@ -398,7 +539,11 @@ impl Format {
398539
399540 #[ cfg( test) ]
400541 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+ } ;
402547 self . render ( & mut ret, msg) ;
403548 Some ( ret)
404549 }
@@ -420,6 +565,7 @@ fn test_progress_status() {
420565 style : ProgressStyle :: Ratio ,
421566 max_print : 40 ,
422567 max_width : 60 ,
568+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
423569 } ;
424570 assert_eq ! (
425571 format. progress_status( 0 , 4 , "" ) ,
@@ -493,6 +639,7 @@ fn test_progress_status_percentage() {
493639 style : ProgressStyle :: Percentage ,
494640 max_print : 40 ,
495641 max_width : 60 ,
642+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
496643 } ;
497644 assert_eq ! (
498645 format. progress_status( 0 , 77 , "" ) ,
@@ -518,6 +665,7 @@ fn test_progress_status_too_short() {
518665 style : ProgressStyle :: Percentage ,
519666 max_print : 25 ,
520667 max_width : 25 ,
668+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
521669 } ;
522670 assert_eq ! (
523671 format. progress_status( 1 , 1 , "" ) ,
@@ -528,6 +676,7 @@ fn test_progress_status_too_short() {
528676 style : ProgressStyle :: Percentage ,
529677 max_print : 24 ,
530678 max_width : 24 ,
679+ taskbar : TaskbarProgress :: from_config ( None , false ) ,
531680 } ;
532681 assert_eq ! ( format. progress_status( 1 , 1 , "" ) , None ) ;
533682}
0 commit comments