Skip to content

Commit 340d123

Browse files
authored
Add terminal integration via ANSI OSC 9;4 sequences (#14615)
### What does this PR try to resolve? A few terminal emulators support progress output to Windows taskbar. `winget` uses this to show install progress. Notably, Windows Terminal [recently (2020) added support](microsoft/terminal#8055) for ANSI codes [specified](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC) in ConEmu (another terminal emulator for Windows) documentation. Also, in "[Learn Windows](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences)". I've found the previous attempt to add this feature: #11436 As per @weihanglo's request, I've added the config option to enable/disable this feature. **It's enabled on supported terminal emulators.** Fixes #11432 FCP: #14615 (comment) ### How should we test and review this PR? Run `cargo build` in Windows Terminal with configuration option `term.progress.taskbar` set to `true`. ### Not sure - [x] Should all the code be `#[cfg(windows)]`? Probably no, because the feature is also usable in WSL. > Solved by introducing heuristic based on environment variable set by terminal - [ ] If Ctrl+C is pressed, a progressbar will stay in a last state forever (shown in the ConEmu video). `winget` is also behaves like alike. I've experimented with `ctrl_c handler` and it's totally fixable. - [x] `Enabled` is a sensible default for WSL because it works on linux builds in Windows Terminal too > Solved by introducing heuristic based on environment variable set by terminal - [x] Downloading stage may produce unpleasant blinking due to a rapid 0-100 changes > Solved by not displaying bar when downloading and using indeterminate state in other cases so amination don't reset ### TLDR * An `term.progress.taskbar` option with bool type is added * On Windows Terminal and ConEmu is enabled by default * If enabled reports build progress to taskbar icon and/or tab header ### Videos https://github.com/user-attachments/assets/48bb648a-e819-490e-b3ac-3502bc5f2f3a https://github.com/user-attachments/assets/1d7ddf7a-34dd-4db1-b654-e64d7170798e
2 parents 42b6ae8 + a0624ea commit 340d123

File tree

5 files changed

+225
-17
lines changed

5 files changed

+225
-17
lines changed

src/cargo/core/compiler/job_queue/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ impl<'gctx> DrainState<'gctx> {
854854
}
855855

856856
fn handle_error(
857-
&self,
857+
&mut self,
858858
shell: &mut Shell,
859859
err_state: &mut ErrorsDuringDrain,
860860
new_err: impl Into<ErrorToHandle>,
@@ -863,6 +863,7 @@ impl<'gctx> DrainState<'gctx> {
863863
if new_err.print_always || err_state.count == 0 {
864864
crate::display_error(&new_err.error, shell);
865865
if err_state.count == 0 && !self.active.is_empty() {
866+
self.progress.indicate_error();
866867
let _ = shell.warn("build failed, waiting for other jobs to finish...");
867868
}
868869
err_state.count += 1;

src/cargo/core/shell.rs

+24
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ impl Shell {
5656
stderr_tty: std::io::stderr().is_terminal(),
5757
stdout_unicode: supports_unicode(&std::io::stdout()),
5858
stderr_unicode: supports_unicode(&std::io::stderr()),
59+
stderr_term_integration: supports_term_integration(&std::io::stderr()),
5960
},
6061
verbosity: Verbosity::Verbose,
6162
needs_clear: false,
@@ -122,6 +123,18 @@ impl Shell {
122123
}
123124
}
124125

126+
pub fn is_err_term_integration_available(&self) -> bool {
127+
if let ShellOut::Stream {
128+
stderr_term_integration,
129+
..
130+
} = self.output
131+
{
132+
stderr_term_integration
133+
} else {
134+
false
135+
}
136+
}
137+
125138
/// Gets a reference to the underlying stdout writer.
126139
pub fn out(&mut self) -> &mut dyn Write {
127140
if self.needs_clear {
@@ -426,6 +439,7 @@ enum ShellOut {
426439
hyperlinks: bool,
427440
stdout_unicode: bool,
428441
stderr_unicode: bool,
442+
stderr_term_integration: bool,
429443
},
430444
}
431445

@@ -575,6 +589,16 @@ fn supports_hyperlinks() -> bool {
575589
supports_hyperlinks::supports_hyperlinks()
576590
}
577591

592+
/// Determines whether the terminal supports ANSI OSC 9;4.
593+
#[allow(clippy::disallowed_methods)] // Read environment variables to detect terminal
594+
fn supports_term_integration(stream: &dyn IsTerminal) -> bool {
595+
let windows_terminal = std::env::var("WT_SESSION").is_ok();
596+
let conemu = std::env::var("ConEmuANSI").ok() == Some("ON".into());
597+
let wezterm = std::env::var("TERM_PROGRAM").ok() == Some("WezTerm".into());
598+
599+
(windows_terminal || conemu || wezterm) && stream.is_terminal()
600+
}
601+
578602
pub struct Hyperlink<D: fmt::Display> {
579603
url: Option<D>,
580604
}

src/cargo/util/context/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2833,6 +2833,8 @@ pub struct ProgressConfig {
28332833
#[serde(default)]
28342834
pub when: ProgressWhen,
28352835
pub width: Option<usize>,
2836+
/// Communicate progress status with a terminal
2837+
pub term_integration: Option<bool>,
28362838
}
28372839

28382840
#[derive(Debug, Default, Deserialize)]
@@ -2865,10 +2867,12 @@ where
28652867
"auto" => Ok(Some(ProgressConfig {
28662868
when: ProgressWhen::Auto,
28672869
width: None,
2870+
term_integration: None,
28682871
})),
28692872
"never" => Ok(Some(ProgressConfig {
28702873
when: ProgressWhen::Never,
28712874
width: None,
2875+
term_integration: None,
28722876
})),
28732877
"always" => Err(E::custom("\"always\" progress requires a `width` key")),
28742878
_ => Err(E::unknown_variant(s, &["auto", "never"])),
@@ -2890,6 +2894,7 @@ where
28902894
if let ProgressConfig {
28912895
when: ProgressWhen::Always,
28922896
width: None,
2897+
..
28932898
} = pc
28942899
{
28952900
return Err(serde::de::Error::custom(

0 commit comments

Comments
 (0)