Skip to content

Commit c319962

Browse files
committed
Auto merge of #13337 - epage:unicode, r=weihanglo
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
2 parents 48fb957 + 294f57a commit c319962

File tree

12 files changed

+139
-78
lines changed

12 files changed

+139
-78
lines changed

Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ unicase.workspace = true
204204
unicode-width.workspace = true
205205
url.workspace = true
206206
walkdir.workspace = true
207+
supports-unicode = "2.1.0"
207208

208209
[target.'cfg(target_has_atomic = "64")'.dependencies]
209210
tracing-chrome.workspace = true

src/bin/cargo/commands/tree.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ pub fn cli() -> Command {
6969
.arg(
7070
opt("charset", "Character set to use in output")
7171
.value_name("CHARSET")
72-
.value_parser(["utf8", "ascii"])
73-
.default_value("utf8"),
72+
.value_parser(["utf8", "ascii"]),
7473
)
7574
.arg(
7675
opt("format", "Format string used for printing dependencies")
@@ -101,6 +100,24 @@ pub fn cli() -> Command {
101100
))
102101
}
103102

103+
#[derive(Copy, Clone)]
104+
pub enum Charset {
105+
Utf8,
106+
Ascii,
107+
}
108+
109+
impl FromStr for Charset {
110+
type Err = &'static str;
111+
112+
fn from_str(s: &str) -> Result<Charset, &'static str> {
113+
match s {
114+
"utf8" => Ok(Charset::Utf8),
115+
"ascii" => Ok(Charset::Ascii),
116+
_ => Err("invalid charset"),
117+
}
118+
}
119+
}
120+
104121
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
105122
if args.flag("version") {
106123
let verbose = args.verbose() > 0;
@@ -181,8 +198,17 @@ subtree of the package given to -p.\n\
181198
print_available_packages(&ws)?;
182199
}
183200

184-
let charset = tree::Charset::from_str(args.get_one::<String>("charset").unwrap())
185-
.map_err(|e| anyhow::anyhow!("{}", e))?;
201+
let charset = args.get_one::<String>("charset");
202+
if let Some(charset) = charset
203+
.map(|c| Charset::from_str(c))
204+
.transpose()
205+
.map_err(|e| anyhow::anyhow!("{}", e))?
206+
{
207+
match charset {
208+
Charset::Utf8 => gctx.shell().set_unicode(true)?,
209+
Charset::Ascii => gctx.shell().set_unicode(false)?,
210+
}
211+
}
186212
let opts = tree::TreeOptions {
187213
cli_features: args.cli_features()?,
188214
packages,
@@ -193,7 +219,6 @@ subtree of the package given to -p.\n\
193219
prefix,
194220
no_dedupe,
195221
duplicates: args.flag("duplicates"),
196-
charset,
197222
format: args.get_one::<String>("format").cloned().unwrap(),
198223
graph_features,
199224
max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),

src/cargo/core/shell.rs

+45-13
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ impl Shell {
5353
color_choice: auto_clr,
5454
hyperlinks: supports_hyperlinks(),
5555
stderr_tty: std::io::stderr().is_terminal(),
56+
stdout_unicode: supports_unicode(&std::io::stdout()),
57+
stderr_unicode: supports_unicode(&std::io::stderr()),
5658
},
5759
verbosity: Verbosity::Verbose,
5860
needs_clear: false,
@@ -230,11 +232,11 @@ impl Shell {
230232
/// Updates the color choice (always, never, or auto) from a string..
231233
pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
232234
if let ShellOut::Stream {
233-
ref mut stdout,
234-
ref mut stderr,
235-
ref mut color_choice,
235+
stdout,
236+
stderr,
237+
color_choice,
236238
..
237-
} = self.output
239+
} = &mut self.output
238240
{
239241
let cfg = color
240242
.map(|c| c.parse())
@@ -249,16 +251,40 @@ impl Shell {
249251
Ok(())
250252
}
251253

252-
pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> {
254+
pub fn set_unicode(&mut self, yes: bool) -> CargoResult<()> {
253255
if let ShellOut::Stream {
254-
ref mut hyperlinks, ..
255-
} = self.output
256+
stdout_unicode,
257+
stderr_unicode,
258+
..
259+
} = &mut self.output
256260
{
261+
*stdout_unicode = yes;
262+
*stderr_unicode = yes;
263+
}
264+
Ok(())
265+
}
266+
267+
pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> {
268+
if let ShellOut::Stream { hyperlinks, .. } = &mut self.output {
257269
*hyperlinks = yes;
258270
}
259271
Ok(())
260272
}
261273

274+
pub fn out_unicode(&self) -> bool {
275+
match &self.output {
276+
ShellOut::Write(_) => true,
277+
ShellOut::Stream { stdout_unicode, .. } => *stdout_unicode,
278+
}
279+
}
280+
281+
pub fn err_unicode(&self) -> bool {
282+
match &self.output {
283+
ShellOut::Write(_) => true,
284+
ShellOut::Stream { stderr_unicode, .. } => *stderr_unicode,
285+
}
286+
}
287+
262288
/// Gets the current color choice.
263289
///
264290
/// If we are not using a color stream, this will always return `Never`, even if the color
@@ -384,6 +410,8 @@ enum ShellOut {
384410
stderr_tty: bool,
385411
color_choice: ColorChoice,
386412
hyperlinks: bool,
413+
stdout_unicode: bool,
414+
stderr_unicode: bool,
387415
},
388416
}
389417

@@ -416,17 +444,17 @@ impl ShellOut {
416444

417445
/// Gets stdout as a `io::Write`.
418446
fn stdout(&mut self) -> &mut dyn Write {
419-
match *self {
420-
ShellOut::Stream { ref mut stdout, .. } => stdout,
421-
ShellOut::Write(ref mut w) => w,
447+
match self {
448+
ShellOut::Stream { stdout, .. } => stdout,
449+
ShellOut::Write(w) => w,
422450
}
423451
}
424452

425453
/// Gets stderr as a `io::Write`.
426454
fn stderr(&mut self) -> &mut dyn Write {
427-
match *self {
428-
ShellOut::Stream { ref mut stderr, .. } => stderr,
429-
ShellOut::Write(ref mut w) => w,
455+
match self {
456+
ShellOut::Stream { stderr, .. } => stderr,
457+
ShellOut::Write(w) => w,
430458
}
431459
}
432460
}
@@ -519,6 +547,10 @@ fn supports_color(choice: anstream::ColorChoice) -> bool {
519547
}
520548
}
521549

550+
fn supports_unicode(stream: &dyn IsTerminal) -> bool {
551+
!stream.is_terminal() || supports_unicode::supports_unicode()
552+
}
553+
522554
fn supports_hyperlinks() -> bool {
523555
#[allow(clippy::disallowed_methods)] // We are reading the state of the system, not config
524556
if std::env::var_os("TERM_PROGRAM").as_deref() == Some(std::ffi::OsStr::new("iTerm.app")) {

src/cargo/ops/tree/mod.rs

+4-22
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ pub struct TreeOptions {
3939
/// appear with different versions, and report if any where found. Implies
4040
/// `invert`.
4141
pub duplicates: bool,
42-
/// The style of characters to use.
43-
pub charset: Charset,
4442
/// A format string indicating how each package should be displayed.
4543
pub format: String,
4644
/// Includes features in the tree as separate nodes.
@@ -68,23 +66,6 @@ impl Target {
6866
}
6967
}
7068

71-
pub enum Charset {
72-
Utf8,
73-
Ascii,
74-
}
75-
76-
impl FromStr for Charset {
77-
type Err = &'static str;
78-
79-
fn from_str(s: &str) -> Result<Charset, &'static str> {
80-
match s {
81-
"utf8" => Ok(Charset::Utf8),
82-
"ascii" => Ok(Charset::Ascii),
83-
_ => Err("invalid charset"),
84-
}
85-
}
86-
}
87-
8869
#[derive(Clone, Copy)]
8970
pub enum Prefix {
9071
None,
@@ -236,9 +217,10 @@ fn print(
236217
let format = Pattern::new(&opts.format)
237218
.with_context(|| format!("tree format `{}` not valid", opts.format))?;
238219

239-
let symbols = match opts.charset {
240-
Charset::Utf8 => &UTF8_SYMBOLS,
241-
Charset::Ascii => &ASCII_SYMBOLS,
220+
let symbols = if gctx.shell().out_unicode() {
221+
&UTF8_SYMBOLS
222+
} else {
223+
&ASCII_SYMBOLS
242224
};
243225

244226
// The visited deps is used to display a (*) whenever a dep has

src/cargo/util/context/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,9 @@ impl GlobalContext {
10491049
if let Some(hyperlinks) = term.hyperlinks {
10501050
self.shell().set_hyperlinks(hyperlinks)?;
10511051
}
1052+
if let Some(unicode) = term.unicode {
1053+
self.shell().set_unicode(unicode)?;
1054+
}
10521055

10531056
self.progress_config = term.progress.unwrap_or_default();
10541057

@@ -2646,6 +2649,7 @@ pub struct TermConfig {
26462649
pub quiet: Option<bool>,
26472650
pub color: Option<String>,
26482651
pub hyperlinks: Option<bool>,
2652+
pub unicode: Option<bool>,
26492653
#[serde(default)]
26502654
#[serde(deserialize_with = "progress_or_string")]
26512655
pub progress: Option<ProgressConfig>,

src/doc/man/cargo-tree.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ The default is the host platform. Use the value `all` to include *all* targets.
150150

151151
{{#option "`--charset` _charset_" }}
152152
Chooses the character set to use for the tree. Valid values are "utf8" or
153-
"ascii". Default is "utf8".
153+
"ascii". When unspecified, cargo will auto-select a value.
154154
{{/option}}
155155

156156
{{#option "`-f` _format_" "`--format` _format_" }}

src/doc/man/generated_txt/cargo-tree.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ OPTIONS
141141
Tree Formatting Options
142142
--charset charset
143143
Chooses the character set to use for the tree. Valid values are
144-
“utf8” or “ascii”. Default is “utf8”.
144+
“utf8” or “ascii”. When unspecified, cargo will auto-select
145+
a value.
145146

146147
-f format, --format format
147148
Set the format string for each package. The default is “{p}”.

src/doc/src/commands/cargo-tree.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ The default is the host platform. Use the value <code>all</code> to include <em>
146146

147147
<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>
148148
<dd class="option-desc">Chooses the character set to use for the tree. Valid values are “utf8” or
149-
“ascii”. Default is “utf8”.</dd>
149+
“ascii”. When unspecified, cargo will auto-select a value.</dd>
150150

151151

152152
<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>

src/doc/src/reference/config.md

+8
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ quiet = false # whether cargo output is quiet
182182
verbose = false # whether cargo provides verbose output
183183
color = 'auto' # whether cargo colorizes output
184184
hyperlinks = true # whether cargo inserts links into output
185+
unicode = true # whether cargo can render output using non-ASCII unicode characters
185186
progress.when = 'auto' # whether cargo shows progress bar
186187
progress.width = 80 # width of progress bar
187188
```
@@ -1298,6 +1299,13 @@ Can be overridden with the `--color` command-line option.
12981299

12991300
Controls whether or not hyperlinks are used in the terminal.
13001301

1302+
#### `term.unicode`
1303+
* Type: bool
1304+
* Default: auto-detect
1305+
* Environment: `CARGO_TERM_UNICODE`
1306+
1307+
Control whether output can be rendered using non-ASCII unicode characters.
1308+
13011309
#### `term.progress.when`
13021310
* Type: string
13031311
* Default: "auto"

src/etc/man/cargo-tree.1

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ The default is the host platform. Use the value \fBall\fR to include \fIall\fR t
175175
\fB\-\-charset\fR \fIcharset\fR
176176
.RS 4
177177
Chooses the character set to use for the tree. Valid values are \[lq]utf8\[rq] or
178-
\[lq]ascii\[rq]\&. Default is \[lq]utf8\[rq]\&.
178+
\[lq]ascii\[rq]\&. When unspecified, cargo will auto\-select a value.
179179
.RE
180180
.sp
181181
\fB\-f\fR \fIformat\fR,

0 commit comments

Comments
 (0)