Skip to content

Commit

Permalink
Auto merge of #13337 - epage:unicode, r=weihanglo
Browse files Browse the repository at this point in the history
feat(tree): Control `--charset` via auto-detecting config value

### What does this PR try to resolve?

This tries to generalize `cargo tree --charset` so any part of cargo's output can use it.  For example,
- `cargo search` could use this for fancier tables
- `cargo add` could use this for fancier feature lists
- #12235 could use this for fancy rendering like miette does (CC `@Muscraft` )
- Progress bars could use fancier characters <-- this is what I'm personally working towards

This builds on the idea from #12889 that we can use fancier terminal features so long as we have good auto-detection and provide users an escape hatch until the auto-detection is improved or in case they disagree.

As a side benefit
- `cargo tree` will auto-detect when the prior default of `--charset utf8` is a good choice
- Users can control `cargo tree --charset` via `.cargo/config.toml`

### How should we test and review this PR?

This is gradually introduced through the individual commits.

### Additional information
  • Loading branch information
bors committed Mar 15, 2024
2 parents 48fb957 + 294f57a commit c319962
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 78 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 30 additions & 5 deletions src/bin/cargo/commands/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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<Charset, &'static str> {
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;
Expand Down Expand Up @@ -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::<String>("charset").unwrap())
.map_err(|e| anyhow::anyhow!("{}", e))?;
let charset = args.get_one::<String>("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,
Expand All @@ -193,7 +219,6 @@ subtree of the package given to -p.\n\
prefix,
no_dedupe,
duplicates: args.flag("duplicates"),
charset,
format: args.get_one::<String>("format").cloned().unwrap(),
graph_features,
max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),
Expand Down
58 changes: 45 additions & 13 deletions src/cargo/core/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())
Expand All @@ -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
Expand Down Expand Up @@ -384,6 +410,8 @@ enum ShellOut {
stderr_tty: bool,
color_choice: ColorChoice,
hyperlinks: bool,
stdout_unicode: bool,
stderr_unicode: bool,
},
}

Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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")) {
Expand Down
26 changes: 4 additions & 22 deletions src/cargo/ops/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Charset, &'static str> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
_ => Err("invalid charset"),
}
}
}

#[derive(Clone, Copy)]
pub enum Prefix {
None,
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/cargo/util/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -2646,6 +2649,7 @@ pub struct TermConfig {
pub quiet: Option<bool>,
pub color: Option<String>,
pub hyperlinks: Option<bool>,
pub unicode: Option<bool>,
#[serde(default)]
#[serde(deserialize_with = "progress_or_string")]
pub progress: Option<ProgressConfig>,
Expand Down
2 changes: 1 addition & 1 deletion src/doc/man/cargo-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_" }}
Expand Down
3 changes: 2 additions & 1 deletion src/doc/man/generated_txt/cargo-tree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}”.
Expand Down
2 changes: 1 addition & 1 deletion src/doc/src/commands/cargo-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ The default is the host platform. Use the value <code>all</code> to include <em>

<dt class="option-term" id="option-cargo-tree---charset"><a class="option-anchor" href="#option-cargo-tree---charset"></a><code>--charset</code> <em>charset</em></dt>
<dd class="option-desc">Chooses the character set to use for the tree. Valid values are “utf8” or
“ascii”. Default is “utf8”.</dd>
“ascii”. When unspecified, cargo will auto-select a value.</dd>


<dt class="option-term" id="option-cargo-tree--f"><a class="option-anchor" href="#option-cargo-tree--f"></a><code>-f</code> <em>format</em></dt>
Expand Down
8 changes: 8 additions & 0 deletions src/doc/src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/etc/man/cargo-tree.1
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit c319962

Please sign in to comment.