diff --git a/Cargo.lock b/Cargo.lock index df841251d86..12ad17a9665 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,6 +336,7 @@ dependencies = [ "shell-escape", "snapbox", "supports-hyperlinks", + "supports-unicode", "tar", "tempfile", "time", @@ -3243,6 +3244,15 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" +[[package]] +name = "supports-unicode" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f850c19edd184a205e883199a261ed44471c81e39bd95b1357f5febbef00e77a" +dependencies = [ + "is-terminal", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 28c475e6eb9..e17c4e423c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -204,6 +204,7 @@ unicase.workspace = true unicode-width.workspace = true url.workspace = true walkdir.workspace = true +supports-unicode = "2.1.0" [target.'cfg(target_has_atomic = "64")'.dependencies] tracing-chrome.workspace = true diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index aa1b526758d..6d83f8e8e95 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -69,8 +69,7 @@ pub fn cli() -> Command { .arg( opt("charset", "Character set to use in output") .value_name("CHARSET") - .value_parser(["utf8", "ascii"]) - .default_value("utf8"), + .value_parser(["utf8", "ascii"]), ) .arg( opt("format", "Format string used for printing dependencies") @@ -101,6 +100,24 @@ pub fn cli() -> Command { )) } +#[derive(Copy, Clone)] +pub enum Charset { + Utf8, + Ascii, +} + +impl FromStr for Charset { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "utf8" => Ok(Charset::Utf8), + "ascii" => Ok(Charset::Ascii), + _ => Err("invalid charset"), + } + } +} + pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { if args.flag("version") { let verbose = args.verbose() > 0; @@ -181,8 +198,17 @@ subtree of the package given to -p.\n\ print_available_packages(&ws)?; } - let charset = tree::Charset::from_str(args.get_one::("charset").unwrap()) - .map_err(|e| anyhow::anyhow!("{}", e))?; + let charset = args.get_one::("charset"); + if let Some(charset) = charset + .map(|c| Charset::from_str(c)) + .transpose() + .map_err(|e| anyhow::anyhow!("{}", e))? + { + match charset { + Charset::Utf8 => gctx.shell().set_unicode(true)?, + Charset::Ascii => gctx.shell().set_unicode(false)?, + } + } let opts = tree::TreeOptions { cli_features: args.cli_features()?, packages, @@ -193,7 +219,6 @@ subtree of the package given to -p.\n\ prefix, no_dedupe, duplicates: args.flag("duplicates"), - charset, format: args.get_one::("format").cloned().unwrap(), graph_features, max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX), diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index 791e0c35790..ff4bb3d2d9e 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -53,6 +53,8 @@ impl Shell { color_choice: auto_clr, hyperlinks: supports_hyperlinks(), stderr_tty: std::io::stderr().is_terminal(), + stdout_unicode: supports_unicode(&std::io::stdout()), + stderr_unicode: supports_unicode(&std::io::stderr()), }, verbosity: Verbosity::Verbose, needs_clear: false, @@ -230,11 +232,11 @@ impl Shell { /// Updates the color choice (always, never, or auto) from a string.. pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> { if let ShellOut::Stream { - ref mut stdout, - ref mut stderr, - ref mut color_choice, + stdout, + stderr, + color_choice, .. - } = self.output + } = &mut self.output { let cfg = color .map(|c| c.parse()) @@ -249,16 +251,40 @@ impl Shell { Ok(()) } - pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> { + pub fn set_unicode(&mut self, yes: bool) -> CargoResult<()> { if let ShellOut::Stream { - ref mut hyperlinks, .. - } = self.output + stdout_unicode, + stderr_unicode, + .. + } = &mut self.output { + *stdout_unicode = yes; + *stderr_unicode = yes; + } + Ok(()) + } + + pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> { + if let ShellOut::Stream { hyperlinks, .. } = &mut self.output { *hyperlinks = yes; } Ok(()) } + pub fn out_unicode(&self) -> bool { + match &self.output { + ShellOut::Write(_) => true, + ShellOut::Stream { stdout_unicode, .. } => *stdout_unicode, + } + } + + pub fn err_unicode(&self) -> bool { + match &self.output { + ShellOut::Write(_) => true, + ShellOut::Stream { stderr_unicode, .. } => *stderr_unicode, + } + } + /// Gets the current color choice. /// /// If we are not using a color stream, this will always return `Never`, even if the color @@ -384,6 +410,8 @@ enum ShellOut { stderr_tty: bool, color_choice: ColorChoice, hyperlinks: bool, + stdout_unicode: bool, + stderr_unicode: bool, }, } @@ -416,17 +444,17 @@ impl ShellOut { /// Gets stdout as a `io::Write`. fn stdout(&mut self) -> &mut dyn Write { - match *self { - ShellOut::Stream { ref mut stdout, .. } => stdout, - ShellOut::Write(ref mut w) => w, + match self { + ShellOut::Stream { stdout, .. } => stdout, + ShellOut::Write(w) => w, } } /// Gets stderr as a `io::Write`. fn stderr(&mut self) -> &mut dyn Write { - match *self { - ShellOut::Stream { ref mut stderr, .. } => stderr, - ShellOut::Write(ref mut w) => w, + match self { + ShellOut::Stream { stderr, .. } => stderr, + ShellOut::Write(w) => w, } } } @@ -519,6 +547,10 @@ fn supports_color(choice: anstream::ColorChoice) -> bool { } } +fn supports_unicode(stream: &dyn IsTerminal) -> bool { + !stream.is_terminal() || supports_unicode::supports_unicode() +} + fn supports_hyperlinks() -> bool { #[allow(clippy::disallowed_methods)] // We are reading the state of the system, not config if std::env::var_os("TERM_PROGRAM").as_deref() == Some(std::ffi::OsStr::new("iTerm.app")) { diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 373c2125ead..da502da8ba9 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -39,8 +39,6 @@ pub struct TreeOptions { /// appear with different versions, and report if any where found. Implies /// `invert`. pub duplicates: bool, - /// The style of characters to use. - pub charset: Charset, /// A format string indicating how each package should be displayed. pub format: String, /// Includes features in the tree as separate nodes. @@ -68,23 +66,6 @@ impl Target { } } -pub enum Charset { - Utf8, - Ascii, -} - -impl FromStr for Charset { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "utf8" => Ok(Charset::Utf8), - "ascii" => Ok(Charset::Ascii), - _ => Err("invalid charset"), - } - } -} - #[derive(Clone, Copy)] pub enum Prefix { None, @@ -236,9 +217,10 @@ fn print( let format = Pattern::new(&opts.format) .with_context(|| format!("tree format `{}` not valid", opts.format))?; - let symbols = match opts.charset { - Charset::Utf8 => &UTF8_SYMBOLS, - Charset::Ascii => &ASCII_SYMBOLS, + let symbols = if gctx.shell().out_unicode() { + &UTF8_SYMBOLS + } else { + &ASCII_SYMBOLS }; // The visited deps is used to display a (*) whenever a dep has diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index da89f40f2dc..f38fbd3df53 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -1049,6 +1049,9 @@ impl GlobalContext { if let Some(hyperlinks) = term.hyperlinks { self.shell().set_hyperlinks(hyperlinks)?; } + if let Some(unicode) = term.unicode { + self.shell().set_unicode(unicode)?; + } self.progress_config = term.progress.unwrap_or_default(); @@ -2646,6 +2649,7 @@ pub struct TermConfig { pub quiet: Option, pub color: Option, pub hyperlinks: Option, + pub unicode: Option, #[serde(default)] #[serde(deserialize_with = "progress_or_string")] pub progress: Option, diff --git a/src/doc/man/cargo-tree.md b/src/doc/man/cargo-tree.md index 1bb52883c11..f8773430c16 100644 --- a/src/doc/man/cargo-tree.md +++ b/src/doc/man/cargo-tree.md @@ -150,7 +150,7 @@ The default is the host platform. Use the value `all` to include *all* targets. {{#option "`--charset` _charset_" }} Chooses the character set to use for the tree. Valid values are "utf8" or -"ascii". Default is "utf8". +"ascii". When unspecified, cargo will auto-select a value. {{/option}} {{#option "`-f` _format_" "`--format` _format_" }} diff --git a/src/doc/man/generated_txt/cargo-tree.txt b/src/doc/man/generated_txt/cargo-tree.txt index 5b81f0aa1f3..c0fa4485f0a 100644 --- a/src/doc/man/generated_txt/cargo-tree.txt +++ b/src/doc/man/generated_txt/cargo-tree.txt @@ -141,7 +141,8 @@ OPTIONS Tree Formatting Options --charset charset Chooses the character set to use for the tree. Valid values are - “utf8” or “ascii”. Default is “utf8”. + “utf8” or “ascii”. When unspecified, cargo will auto-select + a value. -f format, --format format Set the format string for each package. The default is “{p}”. diff --git a/src/doc/src/commands/cargo-tree.md b/src/doc/src/commands/cargo-tree.md index ec930581706..a859f27433a 100644 --- a/src/doc/src/commands/cargo-tree.md +++ b/src/doc/src/commands/cargo-tree.md @@ -146,7 +146,7 @@ The default is the host platform. Use the value all to include
--charset charset
Chooses the character set to use for the tree. Valid values are “utf8” or -“ascii”. Default is “utf8”.
+“ascii”. When unspecified, cargo will auto-select a value.
-f format
diff --git a/src/doc/src/reference/config.md b/src/doc/src/reference/config.md index 83962919dad..6b95e621c25 100644 --- a/src/doc/src/reference/config.md +++ b/src/doc/src/reference/config.md @@ -182,6 +182,7 @@ quiet = false # whether cargo output is quiet verbose = false # whether cargo provides verbose output color = 'auto' # whether cargo colorizes output hyperlinks = true # whether cargo inserts links into output +unicode = true # whether cargo can render output using non-ASCII unicode characters progress.when = 'auto' # whether cargo shows progress bar progress.width = 80 # width of progress bar ``` @@ -1298,6 +1299,13 @@ Can be overridden with the `--color` command-line option. Controls whether or not hyperlinks are used in the terminal. +#### `term.unicode` +* Type: bool +* Default: auto-detect +* Environment: `CARGO_TERM_UNICODE` + +Control whether output can be rendered using non-ASCII unicode characters. + #### `term.progress.when` * Type: string * Default: "auto" diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index 2fab70be971..7861c28fbb4 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -175,7 +175,7 @@ The default is the host platform. Use the value \fBall\fR to include \fIall\fR t \fB\-\-charset\fR \fIcharset\fR .RS 4 Chooses the character set to use for the tree. Valid values are \[lq]utf8\[rq] or -\[lq]ascii\[rq]\&. Default is \[lq]utf8\[rq]\&. +\[lq]ascii\[rq]\&. When unspecified, cargo will auto\-select a value. .RE .sp \fB\-f\fR \fIformat\fR, diff --git a/tests/testsuite/cargo_tree/help/stdout.term.svg b/tests/testsuite/cargo_tree/help/stdout.term.svg index c7ab6b80047..984758e3916 100644 --- a/tests/testsuite/cargo_tree/help/stdout.term.svg +++ b/tests/testsuite/cargo_tree/help/stdout.term.svg @@ -1,4 +1,4 @@ - +