Add support for `annotate-snippets`'s `no-graphics` output format (#17735)

*[View all
comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/cargo/pull/17735)*

### What does this PR try to resolve?

This PR adds the initial support for `annotate-snippets`'s
[`no-graphics` output
format](https://docs.rs/annotate-snippets/latest/annotate_snippets/struct.Renderer.html#method.no_graphics).
The `no-graphics` output format doesn't show source snippets or use any
graphical decorations and is primarily designed for Text-To-Speech and
Braille displays. The goal at this time is to get `no-graphics` in front
of users so we can iterate on and improve the output. Rust added support
for it in [rust#165708](https://github.com/rust-lang/rust/pull/165708)
and made it in yesterday's nightly.

### Notes to reviewers

_What might the reviewers need to know about the change and how to
review it?_
_Example: the larger context this fits within_
_Example: manual testing performed and why it wasn't automated_

---

🤖 LLM involvement: An LLM helped review these changes
diff --git a/crates/cargo-util-config/config.schema.json b/crates/cargo-util-config/config.schema.json
index 6de67b9..6a7086e 100644
--- a/crates/cargo-util-config/config.schema.json
+++ b/crates/cargo-util-config/config.schema.json
@@ -1695,6 +1695,12 @@
               "type": "null"
             }
           ]
+        },
+        "graphics": {
+          "type": [
+            "boolean",
+            "null"
+          ]
         }
       }
     },
diff --git a/crates/cargo-util-config/src/schema.rs b/crates/cargo-util-config/src/schema.rs
index 0997af4..7b68ada 100644
--- a/crates/cargo-util-config/src/schema.rs
+++ b/crates/cargo-util-config/src/schema.rs
@@ -741,6 +741,7 @@
     pub color: Option<String>,
     pub hyperlinks: Option<bool>,
     pub unicode: Option<bool>,
+    pub graphics: Option<bool>,
     pub progress: Option<ProgressConfig>,
 }
 
diff --git a/crates/cargo-util-terminal/src/shell.rs b/crates/cargo-util-terminal/src/shell.rs
index ecea854..2b0eca5 100644
--- a/crates/cargo-util-terminal/src/shell.rs
+++ b/crates/cargo-util-terminal/src/shell.rs
@@ -48,11 +48,13 @@
         let auto_clr = ColorChoice::CargoAuto;
         let stdout_choice = auto_clr.to_anstream_color_choice();
         let stderr_choice = auto_clr.to_anstream_color_choice();
+        let no_graphics = std::env::var_os("NO_GRAPHICS").is_some_and(|v| !v.is_empty());
         Shell {
             output: ShellOut::Stream {
                 stdout: AutoStream::new(std::io::stdout(), stdout_choice),
                 stderr: AutoStream::new(std::io::stderr(), stderr_choice),
                 color_choice: auto_clr,
+                graphics: !no_graphics,
                 hyperlinks: supports_hyperlinks(),
                 stderr_tty: std::io::stderr().is_terminal(),
                 stdout_unicode: supports_unicode(&std::io::stdout()),
@@ -399,6 +401,20 @@
         Ok(())
     }
 
+    pub fn graphics(&self) -> bool {
+        match &self.output {
+            ShellOut::Write(_) => true,
+            ShellOut::Stream { graphics, .. } => *graphics,
+        }
+    }
+
+    pub fn set_graphics(&mut self, yes: bool) -> CargoResult<()> {
+        if let ShellOut::Stream { graphics, .. } = &mut self.output {
+            *graphics = yes;
+        }
+        Ok(())
+    }
+
     /// Prints a message to stderr and translates ANSI escape code into console colors.
     pub fn print_ansi_stderr(&mut self, message: &[u8]) -> CargoResult<()> {
         if self.needs_clear {
@@ -446,6 +462,7 @@
         let rendered = Renderer::styled()
             .term_width(term_width)
             .decor_style(decor_style)
+            .no_graphics(!self.graphics())
             .render(report);
         self.err().write_all(rendered.as_bytes())?;
         self.err().write_all(b"\n")?;
@@ -469,6 +486,7 @@
         stderr: AutoStream<std::io::Stderr>,
         stderr_tty: bool,
         color_choice: ColorChoice,
+        graphics: bool,
         hyperlinks: bool,
         stdout_unicode: bool,
         stderr_unicode: bool,
diff --git a/doc/book/src/reference/unstable.md b/doc/book/src/reference/unstable.md
index f386ef9..92bed21 100644
--- a/doc/book/src/reference/unstable.md
+++ b/doc/book/src/reference/unstable.md
@@ -123,6 +123,7 @@
     * [`cargo rustc --print`](#rustc---print) --- Calls rustc with `--print` to display information from rustc.
     * [Build analysis](#build-analysis) --- Record and persist detailed build metrics across runs, with new commands to query past builds.
     * [`rustc-unicode`](#rustc-unicode) --- Enables `rustc`'s unicode error format in Cargo's error messages
+    * [`term-graphics`](#term-graphics) --- Controls  graphics in its terminal output
 * Configuration
     * [`cargo config`](#cargo-config) --- Adds a new subcommand for viewing config files.
 * Registries
@@ -2047,6 +2048,27 @@
 `builtin://.#<name>`. For example, `builtin://.#core` identifies the builtin
 core package, distinguishing it from packages named "core" from other sources.
 
+## term-graphics
+* Tracking Issue: [rust-lang/rust#165164](https://github.com/rust-lang/rust/issues/165164)
+
+The `-Z term-graphics` enables the `term.graphics` configuration option to
+control whether Cargo and `rustc` use graphical decorations and source
+snippets in their diagnostic output.
+If the `-Z term-graphics` unstable flag is not enabled, then the
+`term.term-graphics` configuration will be ignored.
+
+
+### Added to Configuration
+
+- Type: boolean
+- Default: auto-detect
+- Environment: `CARGO_TERM_GRAPHICS`
+
+Controls the use of graphical decorations and source snippets in diagnostic
+output. The output when this is `false`, was primarily designed to be usable by
+users relying on Text-To-Speech and Braille displays.
+
+
 # Stabilized and removed features
 
 ## Compile progress
diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs
index 4ee0358..b6bf9ab 100644
--- a/src/compiler/mod.rs
+++ b/src/compiler/mod.rs
@@ -1311,6 +1311,13 @@
     {
         json.push_str(",diagnostic-unicode");
     }
+
+    if !build_runner.bcx.gctx.shell().graphics()
+        && build_runner.bcx.gctx.cli_unstable().term_graphics
+    {
+        json.push_str(",diagnostic-no-graphics");
+    }
+
     if enable_timings {
         json.push_str(",timings");
     }
diff --git a/src/context/mod.rs b/src/context/mod.rs
index 8b56c3e..5d81695 100644
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -954,6 +954,21 @@
         self.shell()
             .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
 
+        // Without this a user could set `NO_GRAPHICS=1` and be able to use an
+        // unstable feature without it being enabled
+        if !self.unstable_flags.term_graphics {
+            self.shell().set_graphics(true)?;
+        }
+
+        if let Some(graphics) = term.graphics {
+            if self.unstable_flags.term_graphics {
+                self.shell().set_graphics(graphics)?;
+            } else {
+                self.shell()
+                    .warn("ignoring `term.graphics` without `-Zterm-graphics`")?;
+            }
+        }
+
         Ok(())
     }
 
diff --git a/src/workspace/features.rs b/src/workspace/features.rs
index 70e4253..6befb82 100644
--- a/src/workspace/features.rs
+++ b/src/workspace/features.rs
@@ -933,6 +933,7 @@
     separate_nightlies: bool,
     skip_rustdoc_fingerprint: bool,
     target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
+    term_graphics: bool = ("Enable the `term.graphics` key in the .cargo/config.toml file"),
     unstable_options: bool = ("Allow the usage of unstable options"),
 );
 
@@ -1477,6 +1478,7 @@
             "script" => self.script = parse_empty(k, v)?,
             "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
             "panic-immediate-abort" => self.panic_immediate_abort = parse_empty(k, v)?,
+            "term-graphics" => self.term_graphics = parse_empty(k, v)?,
             "unstable-options" => self.unstable_options = parse_empty(k, v)?,
             _ => bail!(
                 "\
diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg
index 462ed15..7906709 100644
--- a/tests/testsuite/cargo/z_help/stdout.term.svg
+++ b/tests/testsuite/cargo/z_help/stdout.term.svg
@@ -1,4 +1,4 @@
-<svg width="1255px" height="992px" xmlns="http://www.w3.org/2000/svg">
+<svg width="1255px" height="1010px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { fill: #000000 }
@@ -112,17 +112,19 @@
 </tspan>
     <tspan x="10px" y="874px"><tspan>    -Z target-applies-to-host      Enable the `target-applies-to-host` key in the .cargo/config.toml file</tspan>
 </tspan>
-    <tspan x="10px" y="892px"><tspan>    -Z unstable-options            Allow the usage of unstable options</tspan>
+    <tspan x="10px" y="892px"><tspan>    -Z term-graphics               Enable the `term.graphics` key in the .cargo/config.toml file</tspan>
 </tspan>
-    <tspan x="10px" y="910px">
+    <tspan x="10px" y="910px"><tspan>    -Z unstable-options            Allow the usage of unstable options</tspan>
 </tspan>
-    <tspan x="10px" y="928px"><tspan>Run with `cargo -Z [FLAG] [COMMAND]`</tspan>
+    <tspan x="10px" y="928px">
 </tspan>
-    <tspan x="10px" y="946px">
+    <tspan x="10px" y="946px"><tspan>Run with `cargo -Z [FLAG] [COMMAND]`</tspan>
 </tspan>
-    <tspan x="10px" y="964px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
+    <tspan x="10px" y="964px">
 </tspan>
-    <tspan x="10px" y="982px">
+    <tspan x="10px" y="982px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
+</tspan>
+    <tspan x="10px" y="1000px">
 </tspan>
   </text>
 
diff --git a/tests/testsuite/lints/mod.rs b/tests/testsuite/lints/mod.rs
index 2c24dac..5f40c5d 100644
--- a/tests/testsuite/lints/mod.rs
+++ b/tests/testsuite/lints/mod.rs
@@ -484,3 +484,45 @@
 "#]])
         .run();
 }
+
+#[cargo_test(nightly, reason = "diagnostic-no-graphics is unstable")]
+fn lint_rendering_respects_no_graphics() {
+    let p = project()
+        .file(
+            ".cargo/config.toml",
+            r#"
+[term]
+graphics = false
+            "#,
+        )
+        .file(
+            "Cargo.toml",
+            r#"
+cargo-features = ["test-dummy-unstable"]
+
+[package]
+name = "foo"
+version = "0.0.1"
+edition = "2015"
+authors = []
+im-a-teapot = true
+
+[lints.cargo]
+im_a_teapot = "warn"
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    p.cargo("fetch")
+        .arg("-Zterm-graphics")
+        .masquerade_as_nightly_cargo(&["test-dummy-unstable", "term-graphics"])
+        .with_stderr_data(str![[r#"
+[WARNING] `im_a_teapot` is specified
+ at Cargo.toml:9:1
+ [NOTE] `cargo::im_a_teapot` is set to `warn` in `[lints]`
+[WARNING] `foo` (manifest) generated 1 warning
+
+"#]])
+        .run();
+}
diff --git a/tests/testsuite/message_format.rs b/tests/testsuite/message_format.rs
index 4383bc6..1d767a7 100644
--- a/tests/testsuite/message_format.rs
+++ b/tests/testsuite/message_format.rs
@@ -202,3 +202,85 @@
 "#]])
         .run();
 }
+
+#[cargo_test(nightly, reason = "diagnostic-no-graphics is unstable")]
+fn cargo_passes_no_graphics() {
+    let p = project()
+        .file(
+            ".cargo/config.toml",
+            r#"
+[term]
+graphics = false
+        "#,
+        )
+        .file(
+            "Cargo.toml",
+            r#"
+[package]
+name = "foo"
+version = "0.0.1"
+edition = "2015"
+"#,
+        )
+        .file(
+            "src/lib.rs",
+            "\
+mod tests {
+    #[test]
+    fn t1() {
+        use std::io;
+    }
+}
+            ",
+        )
+        .build();
+
+    p.cargo("check -v")
+        .arg("-Zterm-graphics")
+        .masquerade_as_nightly_cargo(&["term-graphics"])
+        .with_stderr_data(str![[r#"
+[CHECKING] foo v0.0.1 ([ROOT]/foo)
+[RUNNING] `rustc [..] --json=diagnostic-rendered-ansi,artifacts,future-incompat,unused-externs-silent,diagnostic-no-graphics [..]`
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+}
+
+#[cargo_test(nightly, reason = "diagnostic-no-graphics is unstable")]
+fn cargo_respects_no_graphics_env_var() {
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+[package]
+name = "foo"
+version = "0.0.1"
+edition = "2015"
+"#,
+        )
+        .file(
+            "src/lib.rs",
+            "\
+mod tests {
+    #[test]
+    fn t1() {
+        use std::io;
+    }
+}
+            ",
+        )
+        .build();
+
+    p.cargo("check -v")
+        .env("NO_GRAPHICS", "1")
+        .arg("-Zterm-graphics")
+        .masquerade_as_nightly_cargo(&["term-graphics"])
+        .with_stderr_data(str![[r#"
+[CHECKING] foo v0.0.1 ([ROOT]/foo)
+[RUNNING] `rustc [..] --json=diagnostic-rendered-ansi,artifacts,future-incompat,unused-externs-silent,diagnostic-no-graphics [..]`
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+}