From e6e799f40706cc36cab584a07c918fe08f0aa57a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 30 Sep 2025 16:51:42 -0500 Subject: [PATCH 1/5] refactor: Migrate to anstyle for color definitions --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + src/cli/common.rs | 8 ++++---- src/cli/markdown.rs | 4 ++-- src/cli/self_update.rs | 6 +++--- src/process/terminal_source.rs | 15 ++++++++++----- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df059a0f8e..89be0e5857 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,16 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "anstyle-termcolor" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8969b2b476cad1bf03d37494ad7b8a84ae9e030e248fddc62bdc4484e5ed4a95" +dependencies = [ + "anstyle", + "termcolor", +] + [[package]] name = "anstyle-wincon" version = "3.0.10" @@ -2206,6 +2216,7 @@ name = "rustup" version = "1.28.2" dependencies = [ "anstyle", + "anstyle-termcolor", "anyhow", "cc", "cfg-if 1.0.3", diff --git a/Cargo.toml b/Cargo.toml index 2ce13cf5e3..7cc8241bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ test = ["dep:snapbox", "dep:walkdir"] # Sorted by alphabetic order [dependencies] anstyle = "1.0.11" +anstyle-termcolor = "1.1.4" anyhow = "1.0.69" cfg-if = "1.0" chrono = { version = "0.4", default-features = false, features = ["std"] } diff --git a/src/cli/common.rs b/src/cli/common.rs index 46e722ad29..6fa921c346 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -7,9 +7,9 @@ use std::path::{Path, PathBuf}; use std::sync::LazyLock; use std::{cmp, env}; +use anstyle::AnsiColor; use anyhow::{Context, Result, anyhow}; use git_testament::{git_testament, render_testament}; -use termcolor::Color; use tracing::{error, info, warn}; use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; @@ -152,10 +152,10 @@ fn show_channel_updates( ) -> Result<()> { let data = updates.into_iter().map(|(pkg, result)| { let (banner, color) = match &result { - Ok(UpdateStatus::Installed) => ("installed", Some(Color::Green)), - Ok(UpdateStatus::Updated(_)) => ("updated", Some(Color::Green)), + Ok(UpdateStatus::Installed) => ("installed", Some(AnsiColor::Green)), + Ok(UpdateStatus::Updated(_)) => ("updated", Some(AnsiColor::Green)), Ok(UpdateStatus::Unchanged) => ("unchanged", None), - Err(_) => ("update failed", Some(Color::Red)), + Err(_) => ("update failed", Some(AnsiColor::Red)), }; let (previous_version, version) = match &pkg { diff --git a/src/cli/markdown.rs b/src/cli/markdown.rs index 4ee2f1862a..c204234109 100644 --- a/src/cli/markdown.rs +++ b/src/cli/markdown.rs @@ -1,8 +1,8 @@ // Write Markdown to the terminal use std::io::Write; +use anstyle::AnsiColor; use pulldown_cmark::{Event, Tag, TagEnd}; -use termcolor::Color; use crate::process::{Attr, ColorableTerminal}; @@ -151,7 +151,7 @@ impl<'a> LineFormatter<'a> { self.wrapper.write_line(); } Tag::Emphasis => { - self.push_attr(Attr::ForegroundColor(Color::Red)); + self.push_attr(Attr::ForegroundColor(AnsiColor::Red)); } Tag::Strong => {} Tag::Strikethrough => {} diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 6c334d94af..8699f229a9 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -40,6 +40,7 @@ use std::process::Command; use std::str::FromStr; use std::{fmt, fs}; +use anstyle::AnsiColor; use anyhow::{Context, Result, anyhow}; use cfg_if::cfg_if; use clap::ValueEnum; @@ -47,7 +48,6 @@ use clap::builder::PossibleValue; use itertools::Itertools; use same_file::Handle; use serde::{Deserialize, Serialize}; -use termcolor::Color; use tracing::{error, info, trace, warn}; use crate::dist::download::DownloadCfg; @@ -1368,13 +1368,13 @@ pub(crate) async fn check_rustup_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Res write!(t.lock(), "rustup - ")?; Ok(if current_version != available_version { - let _ = t.fg(Color::Yellow); + let _ = t.fg(AnsiColor::Yellow); write!(t.lock(), "Update available")?; let _ = t.reset(); writeln!(t.lock(), " : {current_version} -> {available_version}")?; true } else { - let _ = t.fg(Color::Green); + let _ = t.fg(AnsiColor::Green); write!(t.lock(), "Up to date")?; let _ = t.reset(); writeln!(t.lock(), " : {current_version}")?; diff --git a/src/process/terminal_source.rs b/src/process/terminal_source.rs index ce5af00de7..6a61f24273 100644 --- a/src/process/terminal_source.rs +++ b/src/process/terminal_source.rs @@ -9,7 +9,10 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor}; +use anstyle::AnsiColor; +use anstyle_termcolor::to_termcolor_color; +use termcolor::ColorChoice; +use termcolor::{ColorSpec, StandardStream, StandardStreamLock, WriteColor}; use super::Process; #[cfg(feature = "test")] @@ -108,10 +111,10 @@ impl ColorableTerminal { } } - pub fn fg(&mut self, color: Color) -> io::Result<()> { + pub fn fg(&mut self, color: AnsiColor) -> io::Result<()> { match self.inner.lock().unwrap().deref_mut() { TerminalInner::StandardStream(s, spec) => { - spec.set_fg(Some(color)); + spec.set_fg(Some(to_termcolor_color(color.into()))); s.set_color(spec) } #[cfg(feature = "test")] @@ -124,7 +127,9 @@ impl ColorableTerminal { TerminalInner::StandardStream(s, spec) => { match attr { Attr::Bold => spec.set_bold(true), - Attr::ForegroundColor(color) => spec.set_fg(Some(color)), + Attr::ForegroundColor(color) => { + spec.set_fg(Some(to_termcolor_color(color.into()))) + } }; s.set_color(spec) } @@ -356,7 +361,7 @@ impl StreamSelector { #[derive(Copy, Clone, Debug)] pub enum Attr { Bold, - ForegroundColor(Color), + ForegroundColor(AnsiColor), } #[cfg(test)] From 8e37355ab859bc29608b0c0590fe5aeac3a3df8f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 2 Oct 2025 10:27:57 -0500 Subject: [PATCH 2/5] refactor: Move style building out of ColorableTerminal --- src/cli/common.rs | 18 +++++++----- src/cli/markdown.rs | 29 ++++++++++++++---- src/cli/rustup_mode.rs | 12 +++++--- src/cli/self_update.rs | 14 +++++---- src/process.rs | 2 +- src/process/terminal_source.rs | 54 +++++++--------------------------- 6 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/cli/common.rs b/src/cli/common.rs index 6fa921c346..f71fbd210b 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use std::sync::LazyLock; use std::{cmp, env}; -use anstyle::AnsiColor; +use anstyle::{AnsiColor, Style}; use anyhow::{Context, Result, anyhow}; use git_testament::{git_testament, render_testament}; use tracing::{error, info, warn}; @@ -18,7 +18,7 @@ use crate::{ dist::{TargetTriple, ToolchainDesc}, errors::RustupError, install::UpdateStatus, - process::{Attr, Process}, + process::Process, toolchain::{LocalToolchainName, Toolchain, ToolchainName}, utils, }; @@ -203,11 +203,14 @@ fn show_channel_updates( for (pkg, banner, width, color, version, previous_version) in data { let padding = max_width - width; let padding: String = " ".repeat(padding); - let _ = write!(t.lock(), " {padding}"); - let _ = t.attr(Attr::Bold); - if let Some(color) = color { - let _ = t.fg(color); + let style = if let Some(color) = color { + color.on_default() + } else { + Style::new() } + .bold(); + let _ = write!(t.lock(), " {padding}"); + let _ = t.style(&style); let _ = write!(t.lock(), "{pkg} {banner}"); let _ = t.reset(); let _ = write!(t.lock(), " - {version}"); @@ -257,9 +260,10 @@ pub(super) fn list_items( process: &Process, ) -> Result { let mut t = process.stdout(); + let bold = Style::new().bold(); for (name, installed) in items { if installed && !installed_only && !quiet { - t.attr(Attr::Bold)?; + t.style(&bold)?; writeln!(t.lock(), "{name} (installed)")?; t.reset()?; } else if installed || !installed_only { diff --git a/src/cli/markdown.rs b/src/cli/markdown.rs index c204234109..bbffa4e80f 100644 --- a/src/cli/markdown.rs +++ b/src/cli/markdown.rs @@ -1,10 +1,10 @@ // Write Markdown to the terminal use std::io::Write; -use anstyle::AnsiColor; +use anstyle::{AnsiColor, Style}; use pulldown_cmark::{Event, Tag, TagEnd}; -use crate::process::{Attr, ColorableTerminal}; +use crate::process::ColorableTerminal; // Handles the wrapping of text written to the console struct LineWrapper<'a> { @@ -96,6 +96,7 @@ struct LineFormatter<'a> { is_code_block: bool, wrapper: LineWrapper<'a>, attrs: Vec, + style: Style, } impl<'a> LineFormatter<'a> { @@ -104,18 +105,21 @@ impl<'a> LineFormatter<'a> { is_code_block: false, wrapper: LineWrapper::new(w, indent, margin), attrs: Vec::new(), + style: Style::new(), } } fn push_attr(&mut self, attr: Attr) { self.attrs.push(attr); - let _ = self.wrapper.w.attr(attr); + attr.apply_to(&mut self.style); + let _ = self.wrapper.w.style(&self.style); } fn pop_attr(&mut self) { self.attrs.pop(); - let _ = self.wrapper.w.reset(); + self.style = Style::new(); for attr in &self.attrs { - let _ = self.wrapper.w.attr(*attr); + attr.apply_to(&mut self.style); } + let _ = self.wrapper.w.style(&self.style); } fn start_tag(&mut self, tag: Tag<'a>) { @@ -240,6 +244,21 @@ impl<'a> LineFormatter<'a> { } } +#[derive(Copy, Clone, Debug)] +pub enum Attr { + Bold, + ForegroundColor(AnsiColor), +} + +impl Attr { + fn apply_to(&self, style: &mut Style) { + match self { + Self::Bold => *style = style.bold(), + Self::ForegroundColor(color) => *style = style.fg_color(Some((*color).into())), + } + } +} + pub(crate) fn md>(t: &mut ColorableTerminal, content: S) { let mut f = LineFormatter::new(t, 0, 79); let parser = pulldown_cmark::Parser::new(content.as_ref()); diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 1c7b7beaac..6941c06d8e 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -10,6 +10,7 @@ use std::{ time::Duration, }; +use anstyle::Style; use anyhow::{Context, Error, Result, anyhow}; use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum, builder::PossibleValue}; use clap_complete::Shell; @@ -39,7 +40,7 @@ use crate::{ }, errors::RustupError, install::{InstallMethod, UpdateStatus}, - process::{Attr, ColorableTerminal, Process}, + process::{ColorableTerminal, Process}, toolchain::{ CustomToolchainName, DistributableToolchain, LocalToolchainName, MaybeResolvableToolchainName, ResolvableLocalToolchainName, ResolvableToolchainName, @@ -1072,10 +1073,12 @@ async fn which( async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { common::warn_if_host_is_emulated(cfg.process); + let bold = Style::new().bold(); + // Print host triple { let mut t = cfg.process.stdout(); - t.attr(Attr::Bold)?; + t.style(&bold)?; write!(t.lock(), "Default host: ")?; t.reset()?; writeln!(t.lock(), "{}", cfg.get_default_host_triple()?)?; @@ -1084,7 +1087,7 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { // Print rustup home directory { let mut t = cfg.process.stdout(); - t.attr(Attr::Bold)?; + t.style(&bold)?; write!(t.lock(), "rustup home: ")?; t.reset()?; writeln!(t.lock(), "{}", cfg.rustup_dir.display())?; @@ -1193,7 +1196,8 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { } fn print_header(t: &mut ColorableTerminal, s: &str) -> Result<(), Error> { - t.attr(Attr::Bold)?; + let bold = Style::new().bold(); + t.style(&bold)?; { let mut term_lock = t.lock(); writeln!(term_lock, "{s}")?; diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 8699f229a9..0c6f26812a 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -40,7 +40,7 @@ use std::process::Command; use std::str::FromStr; use std::{fmt, fs}; -use anstyle::AnsiColor; +use anstyle::{AnsiColor, Style}; use anyhow::{Context, Result, anyhow}; use cfg_if::cfg_if; use clap::ValueEnum; @@ -63,7 +63,7 @@ use crate::{ download::download_file, errors::RustupError, install::UpdateStatus, - process::{Attr, Process}, + process::Process, toolchain::{ DistributableToolchain, MaybeOfficialToolchainName, ResolvableToolchainName, Toolchain, ToolchainName, @@ -1364,17 +1364,21 @@ pub(crate) async fn check_rustup_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Res // Get available rustup version let available_version = get_available_rustup_version(dl_cfg).await?; - let _ = t.attr(Attr::Bold); + let bold = Style::new().bold(); + let yellow = AnsiColor::Yellow.on_default().bold(); + let green = AnsiColor::Green.on_default().bold(); + + let _ = t.style(&bold); write!(t.lock(), "rustup - ")?; Ok(if current_version != available_version { - let _ = t.fg(AnsiColor::Yellow); + let _ = t.style(&yellow); write!(t.lock(), "Update available")?; let _ = t.reset(); writeln!(t.lock(), " : {current_version} -> {available_version}")?; true } else { - let _ = t.fg(AnsiColor::Green); + let _ = t.style(&green); write!(t.lock(), "Up to date")?; let _ = t.reset(); writeln!(t.lock(), " : {current_version}")?; diff --git a/src/process.rs b/src/process.rs index 39f2c87fda..46c479fcf3 100644 --- a/src/process.rs +++ b/src/process.rs @@ -28,7 +28,7 @@ use crate::cli::log; mod file_source; mod terminal_source; -pub use terminal_source::{Attr, ColorableTerminal}; +pub use terminal_source::ColorableTerminal; /// Allows concrete types for the process abstraction. #[derive(Clone, Debug)] diff --git a/src/process/terminal_source.rs b/src/process/terminal_source.rs index 6a61f24273..c34d93fca7 100644 --- a/src/process/terminal_source.rs +++ b/src/process/terminal_source.rs @@ -9,10 +9,10 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use anstyle::AnsiColor; -use anstyle_termcolor::to_termcolor_color; +use anstyle::Style; +use anstyle_termcolor::to_termcolor_spec; use termcolor::ColorChoice; -use termcolor::{ColorSpec, StandardStream, StandardStreamLock, WriteColor}; +use termcolor::{StandardStream, StandardStreamLock, WriteColor}; use super::Process; #[cfg(feature = "test")] @@ -60,12 +60,8 @@ impl ColorableTerminal { _ => ColorChoice::Never, }; let inner = match stream { - StreamSelector::Stdout => { - TerminalInner::StandardStream(StandardStream::stdout(choice), ColorSpec::new()) - } - StreamSelector::Stderr => { - TerminalInner::StandardStream(StandardStream::stderr(choice), ColorSpec::new()) - } + StreamSelector::Stdout => TerminalInner::StandardStream(StandardStream::stdout(choice)), + StreamSelector::Stderr => TerminalInner::StandardStream(StandardStream::stderr(choice)), #[cfg(feature = "test")] StreamSelector::TestWriter(w) => TerminalInner::TestWriter(w, choice), #[cfg(all(test, feature = "test"))] @@ -99,7 +95,7 @@ impl ColorableTerminal { addr_of_mut!((*ptr).guard).write((*ptr).inner.lock().unwrap()); // let locked = match *guard {....} addr_of_mut!((*ptr).locked).write(match (*ptr).guard.deref_mut() { - TerminalInner::StandardStream(s, _) => { + TerminalInner::StandardStream(s) => { let locked = s.lock(); TerminalInnerLocked::StandardStream(locked) } @@ -111,28 +107,9 @@ impl ColorableTerminal { } } - pub fn fg(&mut self, color: AnsiColor) -> io::Result<()> { + pub fn style(&mut self, new: &Style) -> io::Result<()> { match self.inner.lock().unwrap().deref_mut() { - TerminalInner::StandardStream(s, spec) => { - spec.set_fg(Some(to_termcolor_color(color.into()))); - s.set_color(spec) - } - #[cfg(feature = "test")] - TerminalInner::TestWriter(_, _) => Ok(()), - } - } - - pub fn attr(&mut self, attr: Attr) -> io::Result<()> { - match self.inner.lock().unwrap().deref_mut() { - TerminalInner::StandardStream(s, spec) => { - match attr { - Attr::Bold => spec.set_bold(true), - Attr::ForegroundColor(color) => { - spec.set_fg(Some(to_termcolor_color(color.into()))) - } - }; - s.set_color(spec) - } + TerminalInner::StandardStream(s) => s.set_color(&to_termcolor_spec(*new)), #[cfg(feature = "test")] TerminalInner::TestWriter(_, _) => Ok(()), } @@ -140,10 +117,7 @@ impl ColorableTerminal { pub fn reset(&mut self) -> io::Result<()> { match self.inner.lock().unwrap().deref_mut() { - TerminalInner::StandardStream(s, color) => { - color.clear(); - s.reset() - } + TerminalInner::StandardStream(s) => s.reset(), #[cfg(feature = "test")] TerminalInner::TestWriter(_, _) => Ok(()), } @@ -311,7 +285,7 @@ impl TerminalInnerLocked { /// Internal state for ColorableTerminal enum TerminalInner { - StandardStream(StandardStream, ColorSpec), + StandardStream(StandardStream), #[cfg(feature = "test")] #[allow(dead_code)] // ColorChoice only read in test code TestWriter(TestWriter, ColorChoice), @@ -320,7 +294,7 @@ enum TerminalInner { impl TerminalInner { fn as_write(&mut self) -> &mut dyn io::Write { match self { - TerminalInner::StandardStream(s, _) => s, + TerminalInner::StandardStream(s) => s, #[cfg(feature = "test")] TerminalInner::TestWriter(w, _) => w, } @@ -358,12 +332,6 @@ impl StreamSelector { } } -#[derive(Copy, Clone, Debug)] -pub enum Attr { - Bold, - ForegroundColor(AnsiColor), -} - #[cfg(test)] mod tests { use std::collections::HashMap; From 4d01ac4c46c4020b0b5543899ba8fc85610988ca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 1 Oct 2025 20:58:35 -0500 Subject: [PATCH 3/5] refactor: Replace termcolor with anstream --- Cargo.lock | 22 +-------------- Cargo.toml | 3 +- src/cli/rustup_mode.rs | 2 +- src/process/terminal_source.rs | 51 +++++++++++++++++++++++----------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89be0e5857..090a5c355d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,16 +74,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "anstyle-termcolor" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8969b2b476cad1bf03d37494ad7b8a84ae9e030e248fddc62bdc4484e5ed4a95" -dependencies = [ - "anstyle", - "termcolor", -] - [[package]] name = "anstyle-wincon" version = "3.0.10" @@ -2215,8 +2205,8 @@ dependencies = [ name = "rustup" version = "1.28.2" dependencies = [ + "anstream", "anstyle", - "anstyle-termcolor", "anyhow", "cc", "cfg-if 1.0.3", @@ -2266,7 +2256,6 @@ dependencies = [ "strsim", "tar", "tempfile", - "termcolor", "thiserror 2.0.17", "threadpool", "tokio", @@ -2616,15 +2605,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 7cc8241bdb..bd3bc10228 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,8 @@ test = ["dep:snapbox", "dep:walkdir"] # Sorted by alphabetic order [dependencies] +anstream = "0.6.20" anstyle = "1.0.11" -anstyle-termcolor = "1.1.4" anyhow = "1.0.69" cfg-if = "1.0" chrono = { version = "0.4", default-features = false, features = ["std"] } @@ -87,7 +87,6 @@ sharded-slab = "0.1.1" strsim = "0.11" tar = "0.4.26" tempfile = "3.8" -termcolor = "1.2" thiserror = "2" threadpool = "1" tokio = { version = "1.26.0", default-features = false, features = ["macros", "rt-multi-thread", "sync"] } diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 6941c06d8e..2589751c4e 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -10,6 +10,7 @@ use std::{ time::Duration, }; +use anstream::ColorChoice; use anstyle::Style; use anyhow::{Context, Error, Result, anyhow}; use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum, builder::PossibleValue}; @@ -18,7 +19,6 @@ use console::style; use futures_util::stream::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use itertools::Itertools; -use termcolor::ColorChoice; use tokio::sync::Semaphore; use tracing::{info, trace, warn}; use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; diff --git a/src/process/terminal_source.rs b/src/process/terminal_source.rs index c34d93fca7..c2a16601fd 100644 --- a/src/process/terminal_source.rs +++ b/src/process/terminal_source.rs @@ -9,10 +9,8 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use anstyle::Style; -use anstyle_termcolor::to_termcolor_spec; -use termcolor::ColorChoice; -use termcolor::{StandardStream, StandardStreamLock, WriteColor}; +use anstream::{AutoStream, ColorChoice}; +use anstyle::{Reset, Style}; use super::Process; #[cfg(feature = "test")] @@ -60,8 +58,12 @@ impl ColorableTerminal { _ => ColorChoice::Never, }; let inner = match stream { - StreamSelector::Stdout => TerminalInner::StandardStream(StandardStream::stdout(choice)), - StreamSelector::Stderr => TerminalInner::StandardStream(StandardStream::stderr(choice)), + StreamSelector::Stdout => { + TerminalInner::Stdout(AutoStream::new(std::io::stdout(), choice)) + } + StreamSelector::Stderr => { + TerminalInner::Stderr(AutoStream::new(std::io::stderr(), choice)) + } #[cfg(feature = "test")] StreamSelector::TestWriter(w) => TerminalInner::TestWriter(w, choice), #[cfg(all(test, feature = "test"))] @@ -95,9 +97,13 @@ impl ColorableTerminal { addr_of_mut!((*ptr).guard).write((*ptr).inner.lock().unwrap()); // let locked = match *guard {....} addr_of_mut!((*ptr).locked).write(match (*ptr).guard.deref_mut() { - TerminalInner::StandardStream(s) => { - let locked = s.lock(); - TerminalInnerLocked::StandardStream(locked) + TerminalInner::Stdout(_) => { + let locked = std::io::stdout().lock(); + TerminalInnerLocked::Stdout(AutoStream::new(locked, self.color_choice)) + } + TerminalInner::Stderr(_) => { + let locked = std::io::stderr().lock(); + TerminalInnerLocked::Stderr(AutoStream::new(locked, self.color_choice)) } #[cfg(feature = "test")] TerminalInner::TestWriter(w, _) => TerminalInnerLocked::TestWriter(w.lock()), @@ -109,15 +115,24 @@ impl ColorableTerminal { pub fn style(&mut self, new: &Style) -> io::Result<()> { match self.inner.lock().unwrap().deref_mut() { - TerminalInner::StandardStream(s) => s.set_color(&to_termcolor_spec(*new)), + TerminalInner::Stdout(s) => { + write!(s, "{Reset}{new}") + } + TerminalInner::Stderr(s) => { + write!(s, "{Reset}{new}") + } #[cfg(feature = "test")] TerminalInner::TestWriter(_, _) => Ok(()), } } - pub fn reset(&mut self) -> io::Result<()> { match self.inner.lock().unwrap().deref_mut() { - TerminalInner::StandardStream(s) => s.reset(), + TerminalInner::Stdout(s) => { + write!(s, "{Reset}") + } + TerminalInner::Stderr(s) => { + write!(s, "{Reset}") + } #[cfg(feature = "test")] TerminalInner::TestWriter(_, _) => Ok(()), } @@ -268,7 +283,8 @@ impl io::Write for ColorableTerminalLocked { } enum TerminalInnerLocked { - StandardStream(StandardStreamLock<'static>), + Stdout(AutoStream>), + Stderr(AutoStream>), #[cfg(feature = "test")] TestWriter(TestWriterLock<'static>), } @@ -276,7 +292,8 @@ enum TerminalInnerLocked { impl TerminalInnerLocked { fn as_write(&mut self) -> &mut dyn io::Write { match self { - TerminalInnerLocked::StandardStream(s) => s, + TerminalInnerLocked::Stdout(s) => s, + TerminalInnerLocked::Stderr(s) => s, #[cfg(feature = "test")] TerminalInnerLocked::TestWriter(w) => w, } @@ -285,7 +302,8 @@ impl TerminalInnerLocked { /// Internal state for ColorableTerminal enum TerminalInner { - StandardStream(StandardStream), + Stdout(AutoStream), + Stderr(AutoStream), #[cfg(feature = "test")] #[allow(dead_code)] // ColorChoice only read in test code TestWriter(TestWriter, ColorChoice), @@ -294,7 +312,8 @@ enum TerminalInner { impl TerminalInner { fn as_write(&mut self) -> &mut dyn io::Write { match self { - TerminalInner::StandardStream(s) => s, + TerminalInner::Stdout(s) => s, + TerminalInner::Stderr(s) => s, #[cfg(feature = "test")] TerminalInner::TestWriter(w, _) => w, } From c21b2afcecd1ee8d98ef225c1a5446930ce340a7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 2 Oct 2025 14:31:54 -0500 Subject: [PATCH 4/5] refactor: Don't bother grabbing lock for tests We already hold a lock and the performance loss is not likely to be noticed --- src/process/terminal_source.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/process/terminal_source.rs b/src/process/terminal_source.rs index c2a16601fd..0ff22c36ad 100644 --- a/src/process/terminal_source.rs +++ b/src/process/terminal_source.rs @@ -14,7 +14,7 @@ use anstyle::{Reset, Style}; use super::Process; #[cfg(feature = "test")] -use super::file_source::{TestWriter, TestWriterLock}; +use super::file_source::TestWriter; /// A colorable terminal that can be written to pub struct ColorableTerminal { @@ -106,7 +106,7 @@ impl ColorableTerminal { TerminalInnerLocked::Stderr(AutoStream::new(locked, self.color_choice)) } #[cfg(feature = "test")] - TerminalInner::TestWriter(w, _) => TerminalInnerLocked::TestWriter(w.lock()), + TerminalInner::TestWriter(w, _) => TerminalInnerLocked::TestWriter(w.clone()), }); // ColorableTerminalLocked { inner, guard, locked } uninit.assume_init() @@ -286,7 +286,7 @@ enum TerminalInnerLocked { Stdout(AutoStream>), Stderr(AutoStream>), #[cfg(feature = "test")] - TestWriter(TestWriterLock<'static>), + TestWriter(TestWriter), } impl TerminalInnerLocked { From ff36b2a89f49aaaeed3ccea487e12f7cba05e868 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 30 Sep 2025 13:38:18 -0500 Subject: [PATCH 5/5] refactor: Directly apply styling --- src/cli/common.rs | 24 +++++++++------------ src/cli/markdown.rs | 6 +++--- src/cli/rustup_mode.rs | 39 +++++++++++++++++----------------- src/cli/self_update.rs | 19 +++++++---------- src/process/terminal_source.rs | 34 ++++++----------------------- 5 files changed, 47 insertions(+), 75 deletions(-) diff --git a/src/cli/common.rs b/src/cli/common.rs index f71fbd210b..152bdebacf 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -193,7 +193,8 @@ fn show_channel_updates( Ok((pkg, banner, width, color, version, previous_version)) }); - let mut t = cfg.process.stdout(); + let t = cfg.process.stdout(); + let mut t = t.lock(); let data: Vec<_> = data.collect::>()?; let max_width = data @@ -209,17 +210,13 @@ fn show_channel_updates( Style::new() } .bold(); - let _ = write!(t.lock(), " {padding}"); - let _ = t.style(&style); - let _ = write!(t.lock(), "{pkg} {banner}"); - let _ = t.reset(); - let _ = write!(t.lock(), " - {version}"); + let _ = write!(t, " {padding}{style}{pkg} {banner}{style:#} - {version}"); if let Some(previous_version) = previous_version { - let _ = write!(t.lock(), " (from {previous_version})"); + let _ = write!(t, " (from {previous_version})"); } - let _ = writeln!(t.lock()); + let _ = writeln!(t); } - let _ = writeln!(t.lock()); + let _ = writeln!(t); Ok(()) } @@ -259,15 +256,14 @@ pub(super) fn list_items( quiet: bool, process: &Process, ) -> Result { - let mut t = process.stdout(); + let t = process.stdout(); + let mut t = t.lock(); let bold = Style::new().bold(); for (name, installed) in items { if installed && !installed_only && !quiet { - t.style(&bold)?; - writeln!(t.lock(), "{name} (installed)")?; - t.reset()?; + writeln!(t, "{bold}{name} (installed){bold:#}")?; } else if installed || !installed_only { - writeln!(t.lock(), "{name}")?; + writeln!(t, "{name}")?; } } Ok(utils::ExitCode(0)) diff --git a/src/cli/markdown.rs b/src/cli/markdown.rs index bbffa4e80f..b52a264471 100644 --- a/src/cli/markdown.rs +++ b/src/cli/markdown.rs @@ -1,7 +1,7 @@ // Write Markdown to the terminal use std::io::Write; -use anstyle::{AnsiColor, Style}; +use anstyle::{AnsiColor, Reset, Style}; use pulldown_cmark::{Event, Tag, TagEnd}; use crate::process::ColorableTerminal; @@ -111,7 +111,7 @@ impl<'a> LineFormatter<'a> { fn push_attr(&mut self, attr: Attr) { self.attrs.push(attr); attr.apply_to(&mut self.style); - let _ = self.wrapper.w.style(&self.style); + let _ = write!(self.wrapper.w.lock(), "{Reset}{}", self.style); } fn pop_attr(&mut self) { self.attrs.pop(); @@ -119,7 +119,7 @@ impl<'a> LineFormatter<'a> { for attr in &self.attrs { attr.apply_to(&mut self.style); } - let _ = self.wrapper.w.style(&self.style); + let _ = write!(self.wrapper.w.lock(), "{Reset}{}", self.style); } fn start_tag(&mut self, tag: Tag<'a>) { diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 2589751c4e..7bffb1a2e5 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1077,21 +1077,25 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { // Print host triple { - let mut t = cfg.process.stdout(); - t.style(&bold)?; - write!(t.lock(), "Default host: ")?; - t.reset()?; - writeln!(t.lock(), "{}", cfg.get_default_host_triple()?)?; + let t = cfg.process.stdout(); + let mut t = t.lock(); + writeln!( + t, + "{bold}Default host: {bold:#}{}", + cfg.get_default_host_triple()? + )?; } // Print rustup home directory { - let mut t = cfg.process.stdout(); - t.style(&bold)?; - write!(t.lock(), "rustup home: ")?; - t.reset()?; - writeln!(t.lock(), "{}", cfg.rustup_dir.display())?; - writeln!(t.lock())?; + let t = cfg.process.stdout(); + let mut t = t.lock(); + writeln!( + t, + "{bold}rustup home: {bold:#}{}", + cfg.rustup_dir.display() + )?; + writeln!(t)?; } let installed_toolchains = cfg.list_toolchains()?; @@ -1195,15 +1199,12 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result { } } - fn print_header(t: &mut ColorableTerminal, s: &str) -> Result<(), Error> { + fn print_header(t: &mut ColorableTerminal, text: &str) -> Result<(), Error> { let bold = Style::new().bold(); - t.style(&bold)?; - { - let mut term_lock = t.lock(); - writeln!(term_lock, "{s}")?; - writeln!(term_lock, "{}", "-".repeat(s.len()))?; - } // drop the term_lock - t.reset()?; + let divider = "-".repeat(text.len()); + let mut term_lock = t.lock(); + writeln!(term_lock, "{bold}{text}{bold:#}")?; + writeln!(term_lock, "{bold}{divider}{bold:#}")?; Ok(()) } diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 0c6f26812a..dbed9f5407 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -1357,7 +1357,8 @@ impl fmt::Display for SchemaVersion { /// Returns whether an update was available pub(crate) async fn check_rustup_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Result { - let mut t = dl_cfg.process.stdout(); + let t = dl_cfg.process.stdout(); + let mut t = t.lock(); // Get current rustup version let current_version = env!("CARGO_PKG_VERSION"); @@ -1368,20 +1369,16 @@ pub(crate) async fn check_rustup_update(dl_cfg: &DownloadCfg<'_>) -> anyhow::Res let yellow = AnsiColor::Yellow.on_default().bold(); let green = AnsiColor::Green.on_default().bold(); - let _ = t.style(&bold); - write!(t.lock(), "rustup - ")?; + write!(t, "{bold}rustup - {bold:#}")?; Ok(if current_version != available_version { - let _ = t.style(&yellow); - write!(t.lock(), "Update available")?; - let _ = t.reset(); - writeln!(t.lock(), " : {current_version} -> {available_version}")?; + writeln!( + t, + "{yellow}Update available{yellow:#} : {current_version} -> {available_version}" + )?; true } else { - let _ = t.style(&green); - write!(t.lock(), "Up to date")?; - let _ = t.reset(); - writeln!(t.lock(), " : {current_version}")?; + writeln!(t, "{green}Up to date{green:#} : {current_version}")?; false }) } diff --git a/src/process/terminal_source.rs b/src/process/terminal_source.rs index 0ff22c36ad..c6b390be38 100644 --- a/src/process/terminal_source.rs +++ b/src/process/terminal_source.rs @@ -9,8 +9,9 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; +#[cfg(feature = "test")] +use anstream::StripStream; use anstream::{AutoStream, ColorChoice}; -use anstyle::{Reset, Style}; use super::Process; #[cfg(feature = "test")] @@ -106,38 +107,15 @@ impl ColorableTerminal { TerminalInnerLocked::Stderr(AutoStream::new(locked, self.color_choice)) } #[cfg(feature = "test")] - TerminalInner::TestWriter(w, _) => TerminalInnerLocked::TestWriter(w.clone()), + TerminalInner::TestWriter(w, _) => { + TerminalInnerLocked::TestWriter(StripStream::new(Box::new(w.clone()))) + } }); // ColorableTerminalLocked { inner, guard, locked } uninit.assume_init() } } - pub fn style(&mut self, new: &Style) -> io::Result<()> { - match self.inner.lock().unwrap().deref_mut() { - TerminalInner::Stdout(s) => { - write!(s, "{Reset}{new}") - } - TerminalInner::Stderr(s) => { - write!(s, "{Reset}{new}") - } - #[cfg(feature = "test")] - TerminalInner::TestWriter(_, _) => Ok(()), - } - } - pub fn reset(&mut self) -> io::Result<()> { - match self.inner.lock().unwrap().deref_mut() { - TerminalInner::Stdout(s) => { - write!(s, "{Reset}") - } - TerminalInner::Stderr(s) => { - write!(s, "{Reset}") - } - #[cfg(feature = "test")] - TerminalInner::TestWriter(_, _) => Ok(()), - } - } - pub fn is_a_tty(&self) -> bool { self.is_a_tty } @@ -286,7 +264,7 @@ enum TerminalInnerLocked { Stdout(AutoStream>), Stderr(AutoStream>), #[cfg(feature = "test")] - TestWriter(TestWriter), + TestWriter(StripStream>), } impl TerminalInnerLocked {