Skip to content

Commit f8156e7

Browse files
Implement the TraitLike trait for ColorableTerminal
The `ColorableTerminal` now has the `TermLike` trait. If we are not in a *tty* the progress will be hidden and will instead be printed after every channel is checked for updates. NB: since we are using `indicatif` to display progress, when running in a *tty*, we can use `buffer_unordered` instead of `buffered`, as `indicatif` already handles the output in the correct order.
1 parent 81a75ee commit f8156e7

File tree

4 files changed

+128
-24
lines changed

4 files changed

+128
-24
lines changed

Cargo.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ cfg-if = "1.0"
4646
chrono = { version = "0.4", default-features = false, features = ["std"] }
4747
clap = { version = "4", features = ["derive", "wrap_help"] }
4848
clap_complete = "4"
49+
console = "0.16"
4950
curl = { version = "0.4.44", optional = true }
5051
effective-limits = "0.5.5"
5152
enum-map = "2.5.0"

src/cli/rustup_mode.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -792,12 +792,17 @@ async fn default_(
792792
}
793793

794794
async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result<utils::ExitCode> {
795+
let t = cfg.process.stdout().terminal(cfg.process);
796+
let is_a_tty = t.is_a_tty();
795797
let mut update_available = false;
796798
let channels = cfg.list_channels()?;
797799
let num_channels = channels.len();
798800
if num_channels > 0 {
799-
let mp = MultiProgress::with_draw_target(ProgressDrawTarget::stdout());
800-
801+
let mp = if is_a_tty {
802+
MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(t)))
803+
} else {
804+
MultiProgress::with_draw_target(ProgressDrawTarget::hidden())
805+
};
801806
let progress_bars: Vec<_> = channels
802807
.iter()
803808
.map(|(name, _)| {
@@ -819,40 +824,40 @@ async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result<utils::ExitCode
819824
let dist_version = distributable.show_dist_version().await?;
820825
let mut update_a = false;
821826

822-
let (message, style) = match (current_version, dist_version) {
827+
let message = match (current_version, dist_version) {
823828
(None, None) => {
824-
let msg =
825-
format!("{} - Cannot identify installed or update versions", name);
826-
let style = ProgressStyle::with_template("{msg}").unwrap();
827-
(msg, style)
829+
format!("{} - Cannot identify installed or update versions", name)
828830
}
829831
(Some(cv), None) => {
830-
let msg = format!("{} - Up to date : {}", name, cv);
831-
let style = ProgressStyle::with_template("{msg}").unwrap();
832-
(msg, style)
832+
format!("{} - Up to date : {}", name, cv)
833833
}
834834
(Some(cv), Some(dv)) => {
835835
update_a = true;
836-
let msg = format!("{} - Update available : {} -> {}", name, cv, dv);
837-
let style = ProgressStyle::with_template("{msg}").unwrap();
838-
(msg, style)
836+
format!("{} - Update available : {} -> {}", name, cv, dv)
839837
}
840838
(None, Some(dv)) => {
841839
update_a = true;
842-
let msg =
843-
format!("{} - Update available : (Unknown version) -> {}", name, dv);
844-
let style = ProgressStyle::with_template("{msg}").unwrap();
845-
(msg, style)
840+
format!("{} - Update available : (Unknown version) -> {}", name, dv)
846841
}
847842
};
848-
pb.set_style(style);
849-
pb.finish_with_message(message);
850-
Ok::<bool, Error>(update_a)
843+
pb.set_style(ProgressStyle::with_template("{msg}").unwrap());
844+
pb.finish_with_message(message.clone());
845+
Ok::<(bool, String), Error>((update_a, message))
851846
})
852-
.buffered(num_channels)
847+
.buffered(num_channels) //TODO: this could be buffer_unordered as we are using `indicatif`
853848
.collect::<Vec<_>>()
854849
.await;
855-
update_available = channels.into_iter().any(|r| r.unwrap_or(false));
850+
851+
let t = cfg.process.stdout().terminal(cfg.process);
852+
for result in channels.into_iter() {
853+
let (update_a, message) = result?;
854+
if update_a {
855+
update_available = true;
856+
}
857+
if !is_a_tty {
858+
writeln!(t.lock(), "{}", message)?;
859+
}
860+
}
856861
}
857862
let self_update_mode = cfg.get_self_update_mode()?;
858863
// Priority: no-self-update feature > self_update_mode > no-self-update args.

src/process/terminalsource.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use console::Term;
2+
use indicatif::TermLike;
13
use std::{
24
io::{self, Write},
35
mem::MaybeUninit,
@@ -53,6 +55,7 @@ pub struct ColorableTerminal {
5355
// source is important because otherwise parallel constructed terminals
5456
// would not be locked out.
5557
inner: Arc<Mutex<TerminalInner>>,
58+
is_a_tty: bool,
5659
}
5760

5861
/// Internal state for ColorableTerminal
@@ -84,10 +87,11 @@ impl ColorableTerminal {
8487
/// then color commands will be sent to the stream.
8588
/// Otherwise color commands are discarded.
8689
pub(super) fn new(stream: StreamSelector, process: &Process) -> Self {
90+
let is_a_tty = stream.is_a_tty(process);
8791
let choice = match process.var("RUSTUP_TERM_COLOR") {
8892
Ok(s) if s.eq_ignore_ascii_case("always") => ColorChoice::Always,
8993
Ok(s) if s.eq_ignore_ascii_case("never") => ColorChoice::Never,
90-
_ if stream.is_a_tty(process) => ColorChoice::Auto,
94+
_ if is_a_tty => ColorChoice::Auto,
9195
_ => ColorChoice::Never,
9296
};
9397
let inner = match stream {
@@ -104,6 +108,7 @@ impl ColorableTerminal {
104108
};
105109
ColorableTerminal {
106110
inner: Arc::new(Mutex::new(inner)),
111+
is_a_tty,
107112
}
108113
}
109114

@@ -179,6 +184,10 @@ impl ColorableTerminal {
179184
};
180185
Ok(())
181186
}
187+
188+
pub fn is_a_tty(&self) -> bool {
189+
self.is_a_tty
190+
}
182191
}
183192

184193
#[derive(Copy, Clone, Debug)]
@@ -223,6 +232,81 @@ impl io::Write for ColorableTerminalLocked {
223232
}
224233
}
225234

235+
impl TermLike for ColorableTerminal {
236+
fn width(&self) -> u16 {
237+
Term::stdout().size().1
238+
}
239+
240+
fn move_cursor_up(&self, n: usize) -> io::Result<()> {
241+
// As the ProgressBar may try to move the cursor up by 0 lines,
242+
// we need to handle that case to avoid writing an escape sequence
243+
// that would mess up the terminal.
244+
if n == 0 {
245+
return Ok(());
246+
}
247+
let mut t = self.lock();
248+
write!(t, "\x1b[{}A", n)?;
249+
t.flush()
250+
}
251+
252+
fn move_cursor_down(&self, n: usize) -> io::Result<()> {
253+
if n == 0 {
254+
return Ok(());
255+
}
256+
let mut t = self.lock();
257+
write!(t, "\x1b[{}B", n)?;
258+
t.flush()
259+
}
260+
261+
fn move_cursor_right(&self, n: usize) -> io::Result<()> {
262+
if n == 0 {
263+
return Ok(());
264+
}
265+
let mut t = self.lock();
266+
write!(t, "\x1b[{}C", n)?;
267+
t.flush()
268+
}
269+
270+
fn move_cursor_left(&self, n: usize) -> io::Result<()> {
271+
if n == 0 {
272+
return Ok(());
273+
}
274+
let mut t = self.lock();
275+
write!(t, "\x1b[{}D", n)?;
276+
t.flush()
277+
}
278+
279+
fn write_line(&self, line: &str) -> io::Result<()> {
280+
let mut t = self.lock();
281+
t.write_all(line.as_bytes())?;
282+
t.write_all(b"\n")?;
283+
t.flush()
284+
}
285+
286+
fn write_str(&self, s: &str) -> io::Result<()> {
287+
let mut t = self.lock();
288+
t.write_all(s.as_bytes())?;
289+
t.flush()
290+
}
291+
292+
fn clear_line(&self) -> io::Result<()> {
293+
let mut t = self.lock();
294+
t.write_all(b"\r\x1b[2K")?;
295+
t.flush()
296+
}
297+
298+
fn flush(&self) -> io::Result<()> {
299+
let mut t = self.lock();
300+
t.flush()
301+
}
302+
}
303+
304+
impl std::fmt::Debug for ColorableTerminal {
305+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306+
write!(f, "ColorableTerminal {{ inner: ... }}")
307+
}
308+
}
309+
226310
#[cfg(test)]
227311
mod tests {
228312
use std::collections::HashMap;

0 commit comments

Comments
 (0)