diff --git a/Cargo.lock b/Cargo.lock index e160a8dd18..d1ff65e053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,7 +87,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ - "term 0.7.0", + "term", ] [[package]] @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de97b894edd5b5bcceef8b78d7da9b75b1d2f2f9a910569d0bde3dd31d84939" +checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" dependencies = [ "curl-sys", "libc", @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.52+curl-7.81.0" +version = "0.4.53+curl-7.82.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b8c2d1023ea5fded5b7b892e4b8e95f70038a421126a056761a84246a28971" +checksum = "8092905a5a9502c312f223b2775f57ec5c5b715f9a15ee9d2a8591d1364a0352" dependencies = [ "cc", "libc", @@ -1192,7 +1192,7 @@ dependencies = [ "regex", "regex-syntax", "string_cache", - "term 0.7.0", + "term", "tiny-keccak", "unicode-xid", ] @@ -1324,14 +1324,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "7ba42135c6a5917b9db9cd7b293e5409e1c6b041e6f9825e92e55a894c63b6f8" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -1455,9 +1456,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "c539a50b93a303167eded6e8dff5220cd39447409fb659f4cd24b1f72fe4f133" dependencies = [ "libc", ] @@ -2005,6 +2006,7 @@ name = "rustup" version = "1.24.3" dependencies = [ "anyhow", + "atty", "cc", "cfg-if 1.0.0", "chrono", @@ -2018,6 +2020,7 @@ dependencies = [ "lazy_static", "libc", "num_cpus", + "once_cell", "opener", "openssl", "pulldown-cmark", @@ -2036,7 +2039,7 @@ dependencies = [ "strsim 0.10.0", "tar", "tempfile", - "term 0.5.1", + "termcolor", "thiserror", "threadpool", "toml", @@ -2436,23 +2439,22 @@ dependencies = [ [[package]] name = "term" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ - "byteorder", + "dirs-next", + "rustversion", "winapi", ] [[package]] -name = "term" -version = "0.7.0" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "winapi-util", ] [[package]] @@ -2780,6 +2782,12 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.79" diff --git a/Cargo.toml b/Cargo.toml index 5ae776336b..66df3b1cc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,8 @@ [package] -authors = ["Daniel Silverstone ", "Diggory Blake "] +authors = [ + "Daniel Silverstone ", + "Diggory Blake ", +] build = "build.rs" description = "Manage multiple rust installations with ease" edition = "2021" @@ -13,7 +16,12 @@ version = "1.24.3" [features] curl-backend = ["download/curl-backend"] -default = ["curl-backend", "reqwest-backend", "reqwest-default-tls", "reqwest-rustls-tls"] +default = [ + "curl-backend", + "reqwest-backend", + "reqwest-default-tls", + "reqwest-rustls-tls", +] reqwest-backend = ["download/reqwest-backend"] vendored-openssl = ['openssl/vendored'] @@ -27,38 +35,43 @@ no-self-update = [] # Sorted by alphabetic order [dependencies] anyhow = "1.0.31" +atty = "0.2" cfg-if = "1.0" chrono = "0.4" clap = "2" -download = {path = "download", default-features = false} +download = { path = "download", default-features = false } effective-limits = "0.5.3" enum-map = "2.0.3" flate2 = "1" git-testament = "0.2" -home = {git = "https://github.com/rbtcollins/home", rev = "a243ee2fbee6022c57d56f5aa79aefe194eabe53"} +home = { git = "https://github.com/rbtcollins/home", rev = "a243ee2fbee6022c57d56f5aa79aefe194eabe53" } lazy_static = "1" libc = "0.2" num_cpus = "1.13" +once_cell = "1.8" opener = "0.5.0" # Used by `curl` or `reqwest` backend although it isn't imported # by our rustup. -openssl = {version = "0.10", optional = true} -pulldown-cmark = {version = "0.8", default-features = false} +openssl = { version = "0.10", optional = true } +pulldown-cmark = { version = "0.8", default-features = false } rand = "0.8" regex = "1" remove_dir_all = "0.7.0" same-file = "1" scopeguard = "1" semver = "1.0" -serde = {version = "1.0", features = ["derive"]} -sequoia-openpgp = { version = "1.5", default-features = false, features = ["crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"] } +sequoia-openpgp = { version = "1.5", default-features = false, features = [ + "crypto-rust", + "allow-experimental-crypto", + "allow-variable-time-crypto", +] } +serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" sharded-slab = "0.1.1" strsim = "0.10" tar = "0.4.26" tempfile = "3.1" -# FIXME(issue #1818, #1826, and friends) -term = "=0.5.1" +termcolor = "1.1" thiserror = "1.0" threadpool = "1" toml = "0.5" diff --git a/src/cli.rs b/src/cli.rs index 7ac235724c..8023dd877c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,5 +11,5 @@ pub mod proxy_mode; pub mod rustup_mode; pub mod self_update; pub mod setup_mode; -mod term2; +pub(crate) mod term2; mod topical_doc; diff --git a/src/cli/common.rs b/src/cli/common.rs index bfa3635986..263c0d2f17 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -9,7 +9,6 @@ use std::{cmp, env}; use anyhow::{anyhow, Context, Result}; use git_testament::{git_testament, render_testament}; use lazy_static::lazy_static; -use term2::Terminal; use super::self_update; use super::term2; @@ -194,7 +193,7 @@ fn show_channel_updates(cfg: &Cfg, toolchains: Vec<(String, Result match result { Ok(UpdateStatus::Installed) => { banner = "installed"; - color = Some(term2::color::GREEN); + color = Some(term2::Color::Green); } Ok(UpdateStatus::Updated(v)) => { if name == "rustup" { @@ -204,7 +203,7 @@ fn show_channel_updates(cfg: &Cfg, toolchains: Vec<(String, Result previous_version = Some(v); } banner = "updated"; - color = Some(term2::color::GREEN); + color = Some(term2::Color::Green); } Ok(UpdateStatus::Unchanged) => { if name == "rustup" { @@ -215,7 +214,7 @@ fn show_channel_updates(cfg: &Cfg, toolchains: Vec<(String, Result } Err(_) => { banner = "update failed"; - color = Some(term2::color::RED); + color = Some(term2::Color::Red); } } diff --git a/src/cli/download_tracker.rs b/src/cli/download_tracker.rs index 05a19e6cf0..c2278c7a7a 100644 --- a/src/cli/download_tracker.rs +++ b/src/cli/download_tracker.rs @@ -3,8 +3,6 @@ use std::fmt; use std::io::Write; use std::time::{Duration, Instant}; -use term::Terminal; - use super::term2; use crate::dist::Notification as In; use crate::utils::tty; @@ -32,10 +30,7 @@ pub(crate) struct DownloadTracker { /// Time stamp of the start of the download start_sec: Option, /// The terminal we write the information to. - /// XXX: Could be a term trait, but with #1818 on the horizon that - /// is a pointless change to make - better to let that transition - /// happen and take stock after that. - term: term2::StdoutTerminal, + term: Box, /// Whether we displayed progress for the download or not. /// /// If the download is quick enough, we don't have time to diff --git a/src/cli/log.rs b/src/cli/log.rs index 6316fc68cb..515c948c4b 100644 --- a/src/cli/log.rs +++ b/src/cli/log.rs @@ -1,6 +1,5 @@ use std::fmt; use std::io::Write; -use term2::Terminal; use super::term2; use crate::process; @@ -25,7 +24,7 @@ macro_rules! debug { pub(crate) fn warn_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); - let _ = t.fg(term2::color::YELLOW); + let _ = t.fg(term2::Color::Yellow); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "warning: "); let _ = t.reset(); @@ -35,7 +34,7 @@ pub(crate) fn warn_fmt(args: fmt::Arguments<'_>) { pub(crate) fn err_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); - let _ = t.fg(term2::color::RED); + let _ = t.fg(term2::Color::Red); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "error: "); let _ = t.reset(); @@ -54,7 +53,7 @@ pub(crate) fn info_fmt(args: fmt::Arguments<'_>) { pub(crate) fn verbose_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); - let _ = t.fg(term2::color::MAGENTA); + let _ = t.fg(term2::Color::Magenta); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "verbose: "); let _ = t.reset(); @@ -65,7 +64,7 @@ pub(crate) fn verbose_fmt(args: fmt::Arguments<'_>) { pub(crate) fn debug_fmt(args: fmt::Arguments<'_>) { if process().var("RUSTUP_DEBUG").is_ok() { let mut t = term2::stderr(); - let _ = t.fg(term2::color::BLUE); + let _ = t.fg(term2::Color::Blue); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "debug: "); let _ = t.reset(); diff --git a/src/cli/markdown.rs b/src/cli/markdown.rs index 9ae9401a29..3a9c0a4599 100644 --- a/src/cli/markdown.rs +++ b/src/cli/markdown.rs @@ -1,20 +1,18 @@ // Write Markdown to the terminal -use std::io; - use pulldown_cmark::{Event, Tag}; -use super::term2::{color, Attr, Terminal}; +use super::term2::{Attr, Color, Terminal}; // Handles the wrapping of text written to the console -struct LineWrapper<'a, T: Terminal> { +struct LineWrapper<'a> { indent: u32, margin: u32, pos: u32, - w: &'a mut T, + w: &'a mut dyn Terminal, } -impl<'a, T: Terminal + 'a> LineWrapper<'a, T> { +impl<'a> LineWrapper<'a> { // Just write a newline fn write_line(&mut self) { let _ = writeln!(self.w); @@ -81,7 +79,7 @@ impl<'a, T: Terminal + 'a> LineWrapper<'a, T> { } } // Constructor - fn new(w: &'a mut T, indent: u32, margin: u32) -> Self { + fn new(w: &'a mut dyn Terminal, indent: u32, margin: u32) -> Self { LineWrapper { indent, margin, @@ -92,14 +90,14 @@ impl<'a, T: Terminal + 'a> LineWrapper<'a, T> { } // Handles the formatting of text -struct LineFormatter<'a, T: Terminal + io::Write> { +struct LineFormatter<'a> { is_code_block: bool, - wrapper: LineWrapper<'a, T>, + wrapper: LineWrapper<'a>, attrs: Vec, } -impl<'a, T: Terminal + io::Write + 'a> LineFormatter<'a, T> { - fn new(w: &'a mut T, indent: u32, margin: u32) -> Self { +impl<'a> LineFormatter<'a> { + fn new(w: &'a mut dyn Terminal, indent: u32, margin: u32) -> Self { LineFormatter { is_code_block: false, wrapper: LineWrapper::new(w, indent, margin), @@ -145,7 +143,7 @@ impl<'a, T: Terminal + io::Write + 'a> LineFormatter<'a, T> { self.wrapper.write_line(); } Tag::Emphasis => { - self.push_attr(Attr::ForegroundColor(color::RED)); + self.push_attr(Attr::ForegroundColor(Color::Red)); } Tag::Strong => {} Tag::Strikethrough => {} @@ -221,7 +219,7 @@ impl<'a, T: Terminal + io::Write + 'a> LineFormatter<'a, T> { } } -pub(crate) fn md<'a, S: AsRef, T: Terminal + io::Write + 'a>(t: &'a mut T, content: S) { +pub(crate) fn md>(t: &mut dyn Terminal, content: S) { let mut f = LineFormatter::new(t, 0, 79); let parser = pulldown_cmark::Parser::new(content.as_ref()); for event in parser { diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index f7d78979e2..aa46584e98 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -10,7 +10,6 @@ use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand}; use super::help::*; use super::self_update; use super::term2; -use super::term2::Terminal; use super::topical_doc; use super::{ common, @@ -72,7 +71,7 @@ pub fn main() -> Result { message, .. }) => { - writeln!(process().stdout().lock(), "{}", message)?; + writeln!(process().stdout(), "{}", message)?; return Ok(utils::ExitCode(0)); } Err(clap::Error { @@ -80,7 +79,7 @@ pub fn main() -> Result { message, .. }) => { - writeln!(process().stdout().lock(), "{}", message)?; + writeln!(process().stdout(), "{}", message)?; info!("This is the version for the rustup toolchain manager, not the rustc compiler."); fn rustc_version() -> std::result::Result> { @@ -114,7 +113,7 @@ pub fn main() -> Result { ] .contains(kind) { - writeln!(process().stdout().lock(), "{}", message)?; + writeln!(process().stdout(), "{}", message)?; return Ok(utils::ExitCode(1)); } } @@ -896,23 +895,23 @@ fn check_updates(cfg: &Cfg) -> Result { write!(t, "{} - ", name)?; match (current_version, dist_version) { (None, None) => { - let _ = t.fg(term2::color::RED); + let _ = t.fg(term2::Color::Red); writeln!(t, "Cannot identify installed or update versions")?; } (Some(cv), None) => { - let _ = t.fg(term2::color::GREEN); + let _ = t.fg(term2::Color::Green); write!(t, "Up to date")?; let _ = t.reset(); writeln!(t, " : {}", cv)?; } (Some(cv), Some(dv)) => { - let _ = t.fg(term2::color::YELLOW); + let _ = t.fg(term2::Color::Yellow); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : {} -> {}", cv, dv)?; } (None, Some(dv)) => { - let _ = t.fg(term2::color::YELLOW); + let _ = t.fg(term2::Color::Yellow); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : (Unknown version) -> {}", dv)?; @@ -1121,7 +1120,7 @@ fn show(cfg: &Cfg) -> Result { if show_installed_toolchains { let mut t = term2::stdout(); if show_headers { - print_header::(&mut t, "installed toolchains")?; + print_header::(&mut *t, "installed toolchains")?; } let default_name: Result = cfg .get_default()? @@ -1142,7 +1141,7 @@ fn show(cfg: &Cfg) -> Result { if show_active_targets { let mut t = term2::stdout(); if show_headers { - print_header::(&mut t, "installed targets for active toolchain")?; + print_header::(&mut *t, "installed targets for active toolchain")?; } for at in active_targets { writeln!( @@ -1162,7 +1161,7 @@ fn show(cfg: &Cfg) -> Result { if show_active_toolchain { let mut t = term2::stdout(); if show_headers { - print_header::(&mut t, "active toolchain")?; + print_header::(&mut *t, "active toolchain")?; } match active_toolchain { @@ -1195,9 +1194,9 @@ fn show(cfg: &Cfg) -> Result { } } - fn print_header(t: &mut term2::StdoutTerminal, s: &str) -> std::result::Result<(), E> + fn print_header(t: &mut dyn term2::Terminal, s: &str) -> std::result::Result<(), E> where - E: From + From, + E: From, { t.attr(term2::Attr::Bold)?; writeln!(t, "{}", s)?; diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 91ecb9e4a6..0251656f70 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -61,7 +61,6 @@ use super::common::{self, ignorable_error, Confirm}; use super::errors::*; use super::markdown::md; use super::term2; -use crate::cli::term2::Terminal; use crate::dist::dist::{self, Profile, TargetTriple}; use crate::process; use crate::toolchain::{DistributableToolchain, Toolchain}; @@ -370,22 +369,22 @@ pub(crate) fn install( if no_prompt { warn!("installing msvc toolchain without its prerequisites"); } else if !quiet && plan == VsInstallPlan::Automatic { - md(&mut term, MSVC_AUTO_INSTALL_MESSAGE); + md(term.as_mut(), MSVC_AUTO_INSTALL_MESSAGE); if common::confirm( "\nAutomatically download and install Visual Studio 2022 Community edition? (Y/n)", true, )? { try_install_msvc()?; } else { - md(&mut term, MSVC_MANUAL_INSTALL_MESSAGE); + md(term.as_mut(), MSVC_MANUAL_INSTALL_MESSAGE); if !common::confirm("\nContinue? (y/N)", false)? { info!("aborting installation"); return Ok(utils::ExitCode(0)); } } } else { - md(&mut term, MSVC_MESSAGE); - md(&mut term, MSVC_MANUAL_INSTALL_MESSAGE); + md(term.as_mut(), MSVC_MESSAGE); + md(term.as_mut(), MSVC_MANUAL_INSTALL_MESSAGE); if !common::confirm("\nContinue? (y/N)", false)? { info!("aborting installation"); return Ok(utils::ExitCode(0)); @@ -396,10 +395,10 @@ pub(crate) fn install( if !no_prompt { let msg = pre_install_msg(opts.no_modify_path)?; - md(&mut term, msg); + md(&mut *term, msg); loop { - md(&mut term, current_install_opts(&opts)); + md(&mut *term, current_install_opts(&opts)); match common::confirm_advanced()? { Confirm::No => { info!("aborting installation"); @@ -476,7 +475,7 @@ pub(crate) fn install( } else { format!(post_install_msg_unix!(), cargo_home = cargo_home) }; - md(&mut term, msg); + md(&mut *term, msg); #[cfg(windows)] if !no_prompt { @@ -910,7 +909,7 @@ pub(crate) fn uninstall(no_prompt: bool) -> Result { if !no_prompt { writeln!(process().stdout())?; let msg = format!(pre_uninstall_msg!(), cargo_home = canonical_cargo_home()?); - md(&mut term2::stdout(), msg); + md(&mut *term2::stdout(), msg); if !common::confirm("\nContinue? (y/N)", false)? { info!("aborting uninstallation"); return Ok(utils::ExitCode(0)); @@ -1191,12 +1190,12 @@ pub(crate) fn check_rustup_update() -> Result<()> { write!(t, "rustup - ")?; if current_version != available_version { - let _ = t.fg(term2::color::YELLOW); + let _ = t.fg(term2::Color::Yellow); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : {} -> {}", current_version, available_version)?; } else { - let _ = t.fg(term2::color::GREEN); + let _ = t.fg(term2::Color::Green); write!(t, "Up to date")?; let _ = t.reset(); writeln!(t, " : {}", current_version)?; diff --git a/src/cli/setup_mode.rs b/src/cli/setup_mode.rs index f54accbd80..cba1941d79 100644 --- a/src/cli/setup_mode.rs +++ b/src/cli/setup_mode.rs @@ -98,7 +98,7 @@ pub fn main() -> Result { if e.kind == clap::ErrorKind::HelpDisplayed || e.kind == clap::ErrorKind::VersionDisplayed => { - writeln!(process().stdout().lock(), "{}", e.message)?; + writeln!(process().stdout(), "{}", e.message)?; return Ok(utils::ExitCode(0)); } Err(e) => return Err(e.into()), diff --git a/src/cli/term2.rs b/src/cli/term2.rs index 05abb5be23..cb9ea12188 100644 --- a/src/cli/term2.rs +++ b/src/cli/term2.rs @@ -3,238 +3,197 @@ //! if TERM isn't defined. use std::io; -use std::ops::Deref; -use std::sync::Mutex; +use std::io::Write; +use std::sync::{Arc, Mutex}; -use lazy_static::lazy_static; -pub(crate) use term::color; -pub(crate) use term::Attr; -pub(crate) use term::Terminal; +use once_cell::sync::OnceCell; + +use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; -use crate::currentprocess::filesource::{Isatty, Writer}; use crate::process; -mod termhack { - // Things we should submit to term as improvements: here temporarily. - use std::collections::HashMap; - use std::io; - - use term::terminfo::TermInfo; - use term::{StderrTerminal, StdoutTerminal, Terminal, TerminfoTerminal}; - - /// Return a Terminal object for T on this platform. - /// If there is no terminfo and the platform requires terminfo, then None is returned. - fn make_terminal( - terminfo: Option, - source: F, - ) -> Option + Send>> - where - T: 'static + io::Write + Send, - // Works around stdio instances being unclonable. - F: Fn() -> T + Copy, - { - let result = terminfo - .map(move |ti| TerminfoTerminal::new_with_terminfo(source(), ti)) - .map(|t| Box::new(t) as Box + Send>); - #[cfg(windows)] - { - result.or_else(|| { - term::WinConsole::new(source()) - .ok() - .map(|t| Box::new(t) as Box + Send>) - }) - } - #[cfg(not(windows))] - { - result - } - } +/// Public via Terminal +#[derive(Copy, Clone, Debug)] +pub enum Color { + Red, + Green, + Yellow, + Blue, + Magenta, +} - pub(crate) fn make_terminal_with_fallback( - terminfo: Option, - source: F, - ) -> Box + Send> - where - T: 'static + io::Write + Send, - // Works around stdio instances being unclonable. - F: Fn() -> T + Copy, - { - make_terminal(terminfo, source) - .or_else(|| { - let ti = TermInfo { - names: vec![], - bools: HashMap::new(), - numbers: HashMap::new(), - strings: HashMap::new(), - }; - let t = TerminfoTerminal::new_with_terminfo(source(), ti); - Some(Box::new(t) as Box + Send>) - }) - .unwrap() - } - /// Return a Terminal wrapping stdout, or None if a terminal couldn't be - /// opened. - #[allow(unused)] - pub(crate) fn stdout(terminfo: Option) -> Option> { - make_terminal(terminfo, io::stdout) - } - - /// Return a Terminal wrapping stderr, or None if a terminal couldn't be - /// opened. - #[allow(unused)] - pub(crate) fn stderr(terminfo: Option) -> Option> { - make_terminal(terminfo, io::stderr) - } - - /// Return a Terminal wrapping stdout. - #[allow(unused)] - pub(crate) fn stdout_with_fallback(terminfo: Option) -> Box { - make_terminal_with_fallback(terminfo, io::stdout) - } - - /// Return a Terminal wrapping stderr. - #[allow(unused)] - pub(crate) fn stderr_with_fallback(terminfo: Option) -> Box { - make_terminal_with_fallback(terminfo, io::stderr) - } +/// Public via Terminal +#[derive(Copy, Clone, Debug)] +pub enum Attr { + Bold, + ForegroundColor(Color), +} + +/// Public via currentprocess::filesource +pub trait Terminal: io::Write { + fn fg(&mut self, color: Color) -> io::Result<()>; + fn bg(&mut self, color: Color) -> io::Result<()>; + fn attr(&mut self, attr: Attr) -> io::Result<()>; + fn reset(&mut self) -> io::Result<()>; + fn carriage_return(&mut self) -> io::Result<()>; } -// Decorator to: -// - Disable all terminal controls on non-tty's -// - Swallow errors when we try to use features a terminal doesn't have -// such as setting colours when no TermInfo DB is present -pub(crate) struct AutomationFriendlyTerminal(Box + Send>) -where - T: Isatty + io::Write; -pub(crate) type StdoutTerminal = AutomationFriendlyTerminal>; -pub(crate) type StderrTerminal = AutomationFriendlyTerminal>; - -macro_rules! swallow_unsupported { - ( $call:expr ) => {{ - use term::Error::*; - match $call { - Ok(()) | Err(ColorOutOfRange) | Err(NotSupported) => Ok(()), - Err(e) => Err(e), +impl From for termcolor::Color { + fn from(color: Color) -> termcolor::Color { + match color { + Color::Red => termcolor::Color::Red, + Color::Green => termcolor::Color::Green, + Color::Yellow => termcolor::Color::Yellow, + Color::Blue => termcolor::Color::Blue, + Color::Magenta => termcolor::Color::Magenta, } - }}; + } } -impl Isatty for Box { +use crate::currentprocess::filesource::Isatty; + +/// Disable all terminal controls on non-tty's +pub(crate) struct AutomationFriendlyTerminal { + stream: StandardStream, + color: ColorSpec, +} + +impl Isatty for AutomationFriendlyTerminal { fn isatty(&self) -> bool { - self.deref().isatty() + self.stream.supports_color() } } -impl term::Terminal for AutomationFriendlyTerminal -where - T: io::Write + Isatty, -{ - type Output = T; - - fn fg(&mut self, color: color::Color) -> term::Result<()> { - if !self.get_ref().isatty() { +impl Terminal for AutomationFriendlyTerminal { + fn fg(&mut self, color: Color) -> io::Result<()> { + if !self.isatty() { return Ok(()); } - swallow_unsupported!(self.0.fg(color)) + self.color.set_fg(Some(color.into())); + self.stream.set_color(&self.color) } - fn bg(&mut self, color: color::Color) -> term::Result<()> { - if !self.get_ref().isatty() { + fn bg(&mut self, color: Color) -> io::Result<()> { + if !self.isatty() { return Ok(()); } - swallow_unsupported!(self.0.bg(color)) + self.color.set_bg(Some(color.into())); + self.stream.set_color(&self.color) } - fn attr(&mut self, attr: Attr) -> term::Result<()> { - if !self.get_ref().isatty() { + fn attr(&mut self, attr: Attr) -> io::Result<()> { + if !self.isatty() { return Ok(()); } - swallow_unsupported!(self.0.attr(attr)) - } - - fn supports_attr(&self, attr: Attr) -> bool { - self.0.supports_attr(attr) + match attr { + Attr::Bold => self.color.set_bold(true), + Attr::ForegroundColor(color) => self.color.set_fg(Some(color.into())), + }; + self.stream.set_color(&self.color) } - fn reset(&mut self) -> term::Result<()> { - if !self.get_ref().isatty() { + fn reset(&mut self) -> io::Result<()> { + if !self.isatty() { return Ok(()); } - swallow_unsupported!(self.0.reset()) + self.stream.reset() } - /// Returns true if reset is supported. - fn supports_reset(&self) -> bool { - self.0.supports_reset() + fn carriage_return(&mut self) -> io::Result<()> { + self.stream.write(b"\r").map(|_| ()) } +} - fn supports_color(&self) -> bool { - self.0.supports_color() +impl io::Write for AutomationFriendlyTerminal { + fn write(&mut self, buf: &[u8]) -> std::result::Result { + self.stream.write(buf) } - fn cursor_up(&mut self) -> term::Result<()> { - if !self.get_ref().isatty() { - return Ok(()); + fn flush(&mut self) -> std::result::Result<(), io::Error> { + self.stream.flush() + } +} + +impl AutomationFriendlyTerminal { + pub(crate) fn stdout() -> AutomationFriendlyTerminal { + let choice = if crate::utils::tty::stdout_isatty() { + ColorChoice::Auto + } else { + ColorChoice::Never + }; + AutomationFriendlyTerminal { + stream: termcolor::StandardStream::stdout(choice), + color: ColorSpec::new(), } - swallow_unsupported!(self.0.cursor_up()) } - fn delete_line(&mut self) -> term::Result<()> { - swallow_unsupported!(self.0.delete_line()) + pub(crate) fn stderr() -> AutomationFriendlyTerminal { + let choice = if crate::utils::tty::stderr_isatty() { + ColorChoice::Auto + } else { + ColorChoice::Never + }; + AutomationFriendlyTerminal { + stream: termcolor::StandardStream::stderr(choice), + color: ColorSpec::new(), + } } +} + +/// A cheaply clonable handle to a terminal. +#[derive(Clone)] +struct SharedTerminal { + inner: Arc>>, +} - fn carriage_return(&mut self) -> term::Result<()> { - // This might leak control chars in !isatty? needs checking. - swallow_unsupported!(self.0.carriage_return()) +impl io::Write for SharedTerminal { + fn write(&mut self, buf: &[u8]) -> std::result::Result { + self.inner.lock().unwrap().write(buf) } - fn get_ref(&self) -> &Self::Output { - self.0.get_ref() + fn flush(&mut self) -> std::result::Result<(), io::Error> { + self.inner.lock().unwrap().flush() } +} - fn get_mut(&mut self) -> &mut Self::Output { - self.0.get_mut() +impl Terminal for SharedTerminal { + fn fg(&mut self, color: Color) -> io::Result<()> { + self.inner.lock().unwrap().fg(color) } - /// Returns the contained stream, destroying the `Terminal` - fn into_inner(self) -> Self::Output - where - Self: Sized, - { - unimplemented!() - // self.0.into_inner().into_inner() + fn bg(&mut self, color: Color) -> io::Result<()> { + self.inner.lock().unwrap().bg(color) } -} -impl io::Write for AutomationFriendlyTerminal { - fn write(&mut self, buf: &[u8]) -> Result { - self.0.write(buf) + fn attr(&mut self, attr: Attr) -> io::Result<()> { + self.inner.lock().unwrap().attr(attr) } - fn flush(&mut self) -> Result<(), io::Error> { - self.0.flush() + fn reset(&mut self) -> io::Result<()> { + self.inner.lock().unwrap().reset() } -} -lazy_static! { - // Cache the terminfo database for performance. - // Caching the actual terminals may be better, as on Windows terminal - // detection is per-fd, but this at least avoids the IO subsystem and - // caching the stdout instances is more complex - static ref TERMINFO: Mutex> = - Mutex::new(term::terminfo::TermInfo::from_env().ok()); + fn carriage_return(&mut self) -> io::Result<()> { + self.inner.lock().unwrap().carriage_return() + } } -pub(crate) fn stdout() -> StdoutTerminal { - let info_result = TERMINFO.lock().unwrap().clone(); - AutomationFriendlyTerminal(termhack::make_terminal_with_fallback(info_result, || { - process().stdout() - })) +pub(crate) fn stdout() -> Box { + static STDOUT: OnceCell> = OnceCell::new(); + let shared = STDOUT.get_or_init(|| { + Box::new(SharedTerminal { + inner: Arc::new(Mutex::new(process().stdout())), + }) + }); + Box::::clone(shared) } -pub(crate) fn stderr() -> StderrTerminal { - let info_result = TERMINFO.lock().unwrap().clone(); - AutomationFriendlyTerminal(termhack::make_terminal_with_fallback(info_result, || { - process().stderr() - })) +pub(crate) fn stderr() -> Box { + static STDERR: OnceCell> = OnceCell::new(); + let shared = STDERR.get_or_init(|| { + Box::new(SharedTerminal { + inner: Arc::new(Mutex::new(process().stderr())), + }) + }); + Box::::clone(shared) } diff --git a/src/currentprocess/filesource.rs b/src/currentprocess/filesource.rs index 1770e86180..5f6bae2fff 100644 --- a/src/currentprocess/filesource.rs +++ b/src/currentprocess/filesource.rs @@ -1,6 +1,7 @@ use std::io::{self, BufRead, Cursor, Read, Result, Write}; use std::sync::{Arc, Mutex, MutexGuard}; +use crate::cli::term2::{AutomationFriendlyTerminal, Terminal}; use crate::utils::tty; /// Stand-in for std::io::Stdin @@ -95,14 +96,14 @@ pub trait Writer: Write + Isatty + Send { /// Stand-in for std::io::stdout pub trait StdoutSource { - fn stdout(&self) -> Box; + fn stdout(&self) -> Box; } // -------------- stderr ------------------------------- /// Stand-in for std::io::stderr pub trait StderrSource { - fn stderr(&self) -> Box; + fn stderr(&self) -> Box; } // ----------------- OS support for writers ----------------- @@ -122,8 +123,8 @@ impl Isatty for io::Stdout { } impl StdoutSource for super::OSProcess { - fn stdout(&self) -> Box { - Box::new(io::stdout()) + fn stdout(&self) -> Box { + Box::new(AutomationFriendlyTerminal::stdout()) } } @@ -142,8 +143,8 @@ impl Isatty for io::Stderr { } impl StderrSource for super::OSProcess { - fn stderr(&self) -> Box { - Box::new(io::stderr()) + fn stderr(&self) -> Box { + Box::new(AutomationFriendlyTerminal::stderr()) } } @@ -169,17 +170,31 @@ pub(crate) type TestWriterInner = Arc>>; struct TestWriter(TestWriterInner); -impl Writer for TestWriter { - fn lock(&self) -> Box { - Box::new(TestWriterLock { - inner: self.0.lock().unwrap_or_else(|e| e.into_inner()), - }) +impl Terminal for TestWriter { + fn fg(&mut self, _: crate::cli::term2::Color) -> io::Result<()> { + Ok(()) + } + + fn bg(&mut self, _: crate::cli::term2::Color) -> io::Result<()> { + Ok(()) + } + + fn attr(&mut self, _: crate::cli::term2::Attr) -> io::Result<()> { + Ok(()) + } + + fn reset(&mut self) -> io::Result<()> { + Ok(()) + } + + fn carriage_return(&mut self) -> io::Result<()> { + self.0.lock().unwrap().write(b"\r").map(|_| ()) } } impl Write for TestWriter { fn write(&mut self, buf: &[u8]) -> Result { - self.lock().write(buf) + self.0.lock().unwrap().write(buf) } fn flush(&mut self) -> Result<()> { @@ -194,13 +209,13 @@ impl Isatty for TestWriter { } impl StdoutSource for super::TestProcess { - fn stdout(&self) -> Box { + fn stdout(&self) -> Box { Box::new(TestWriter(self.stdout.clone())) } } impl StderrSource for super::TestProcess { - fn stderr(&self) -> Box { + fn stderr(&self) -> Box { Box::new(TestWriter(self.stderr.clone())) } } diff --git a/src/utils/tty.rs b/src/utils/tty.rs index c7ae1c9451..6e28b22472 100644 --- a/src/utils/tty.rs +++ b/src/utils/tty.rs @@ -1,42 +1,7 @@ -// Copied from rustc. isatty crate did not work as expected -#[cfg(unix)] pub(crate) fn stderr_isatty() -> bool { - isatty(libc::STDERR_FILENO) + atty::is(atty::Stream::Stderr) } -#[cfg(windows)] -pub(crate) fn stderr_isatty() -> bool { - isatty(winapi::um::winbase::STD_ERROR_HANDLE) -} - -#[cfg(unix)] -pub(crate) fn stdout_isatty() -> bool { - isatty(libc::STDOUT_FILENO) -} - -#[cfg(windows)] pub(crate) fn stdout_isatty() -> bool { - isatty(winapi::um::winbase::STD_OUTPUT_HANDLE) -} - -#[inline] -#[cfg(unix)] -fn isatty(fd: libc::c_int) -> bool { - unsafe { libc::isatty(fd) == 1 } -} - -#[inline] -#[cfg(windows)] -fn isatty(fd: winapi::shared::minwindef::DWORD) -> bool { - if std::env::var("MSYSTEM").is_ok() { - // FIXME: No color is better than broken color codes in MSYS shells - // https://github.com/rust-lang/rustup/issues/2292 - return false; - } - use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; - unsafe { - let handle = GetStdHandle(fd); - let mut out = 0; - GetConsoleMode(handle, &mut out) != 0 - } + atty::is(atty::Stream::Stdout) }