refactor: Move to anstream + anstyle for styling

This commit is contained in:
Scott Schafer
2025-06-03 19:27:03 -06:00
parent bd4a8004c2
commit 926d4535cd
14 changed files with 166 additions and 203 deletions

View File

@@ -6,6 +6,8 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
annotate-snippets = "0.11"
anstream = "0.6.20"
anstyle = "1.0.13"
derive_setters = "0.1.6"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
@@ -22,7 +24,6 @@ rustc_serialize = { path = "../rustc_serialize" }
rustc_span = { path = "../rustc_span" }
serde = { version = "1.0.125", features = ["derive"] }
serde_json = "1.0.59"
termcolor = "1.2.0"
termize = "0.2"
tracing = "0.1"
# tidy-alphabetical-end

View File

@@ -16,6 +16,8 @@ use std::iter;
use std::path::Path;
use std::sync::Arc;
use anstream::{AutoStream, ColorChoice};
use anstyle::{Ansi256Color, AnsiColor, Effects};
use derive_setters::Setters;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
@@ -25,7 +27,6 @@ use rustc_lint_defs::pluralize;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use tracing::{debug, instrument, trace, warn};
use crate::registry::Registry;
@@ -525,10 +526,6 @@ impl Emitter for HumanEmitter {
!self.short_message
}
fn supports_color(&self) -> bool {
self.dst.supports_color()
}
fn translator(&self) -> &Translator {
&self.translator
}
@@ -1701,7 +1698,6 @@ impl HumanEmitter {
} else {
col_sep_before_no_show_source = true;
}
// print out the span location and spacer before we print the annotated source
// to do this, we need to know if this span will be primary
let is_primary = primary_lo.file.name == annotated_file.file.name;
@@ -3127,7 +3123,6 @@ impl FileWithAnnotatedLines {
multiline_depth: 0,
});
}
let mut output = vec![];
let mut multiline_annotations = vec![];
@@ -3361,7 +3356,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
('\u{2069}', "<EFBFBD>"),
];
fn normalize_whitespace(s: &str) -> String {
pub(crate) fn normalize_whitespace(s: &str) -> String {
const {
let mut i = 1;
while i < OUTPUT_REPLACEMENTS.len() {
@@ -3406,13 +3401,14 @@ fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
)
}
fn emit_to_destination(
pub(crate) fn emit_to_destination(
rendered_buffer: &[Vec<StyledString>],
lvl: &Level,
dst: &mut Destination,
short_message: bool,
) -> io::Result<()> {
use crate::lock;
const RESET: anstyle::Reset = anstyle::Reset;
// In order to prevent error message interleaving, where multiple error lines get intermixed
// when multiple compiler processes error simultaneously, we emit errors with additional
@@ -3429,10 +3425,8 @@ fn emit_to_destination(
let _buffer_lock = lock::acquire_global_lock("rustc_errors");
for (pos, line) in rendered_buffer.iter().enumerate() {
for part in line {
let style = part.style.color_spec(*lvl);
dst.set_color(&style)?;
write!(dst, "{}", part.text)?;
dst.reset()?;
let style = part.style.anstyle(*lvl);
write!(dst, "{RESET}{style}{}{RESET}", part.text)?;
}
if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
writeln!(dst)?;
@@ -3442,11 +3436,11 @@ fn emit_to_destination(
Ok(())
}
pub type Destination = Box<dyn WriteColor + Send>;
pub type Destination = AutoStream<Box<dyn Write + Send>>;
struct Buffy {
buffer_writer: BufferWriter,
buffer: Buffer,
buffer_writer: std::io::Stderr,
buffer: Vec<u8>,
}
impl Write for Buffy {
@@ -3455,7 +3449,7 @@ impl Write for Buffy {
}
fn flush(&mut self) -> io::Result<()> {
self.buffer_writer.print(&self.buffer)?;
self.buffer_writer.write_all(&self.buffer)?;
self.buffer.clear();
Ok(())
}
@@ -3470,22 +3464,16 @@ impl Drop for Buffy {
}
}
impl WriteColor for Buffy {
fn supports_color(&self) -> bool {
self.buffer.supports_color()
}
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
self.buffer.set_color(spec)
}
fn reset(&mut self) -> io::Result<()> {
self.buffer.reset()
}
}
pub fn stderr_destination(color: ColorConfig) -> Destination {
let buffer_writer = std::io::stderr();
let choice = color.to_color_choice();
// We need to resolve `ColorChoice::Auto` before `Box`ing since
// `ColorChoice::Auto` on `dyn Write` will always resolve to `Never`
let choice = if matches!(choice, ColorChoice::Auto) {
AutoStream::choice(&buffer_writer)
} else {
choice
};
// On Windows we'll be performing global synchronization on the entire
// system for emitting rustc errors, so there's no need to buffer
// anything.
@@ -3493,60 +3481,42 @@ pub fn stderr_destination(color: ColorConfig) -> Destination {
// On non-Windows we rely on the atomicity of `write` to ensure errors
// don't get all jumbled up.
if cfg!(windows) {
Box::new(StandardStream::stderr(choice))
AutoStream::new(Box::new(buffer_writer), choice)
} else {
let buffer_writer = BufferWriter::stderr(choice);
let buffer = buffer_writer.buffer();
Box::new(Buffy { buffer_writer, buffer })
let buffer = Vec::new();
AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
}
}
/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
///
/// See #36178.
const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) {
Ansi256Color::from_ansi(AnsiColor::BrightCyan).on_default()
} else {
Ansi256Color::from_ansi(AnsiColor::BrightBlue).on_default()
};
impl Style {
fn color_spec(&self, lvl: Level) -> ColorSpec {
let mut spec = ColorSpec::new();
pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
match self {
Style::Addition => {
spec.set_fg(Some(Color::Green)).set_intense(true);
}
Style::Removal => {
spec.set_fg(Some(Color::Red)).set_intense(true);
}
Style::LineAndColumn => {}
Style::LineNumber => {
spec.set_bold(true);
spec.set_intense(true);
spec.set_fg(Some(BRIGHT_BLUE));
}
Style::Quotation => {}
Style::MainHeaderMsg => {
spec.set_bold(true);
if cfg!(windows) {
spec.set_intense(true).set_fg(Some(Color::White));
}
}
Style::UnderlinePrimary | Style::LabelPrimary => {
spec = lvl.color();
spec.set_bold(true);
}
Style::UnderlineSecondary | Style::LabelSecondary => {
spec.set_bold(true).set_intense(true);
spec.set_fg(Some(BRIGHT_BLUE));
}
Style::HeaderMsg | Style::NoStyle => {}
Style::Level(lvl) => {
spec = lvl.color();
spec.set_bold(true);
}
Style::Highlight => {
spec.set_bold(true).set_fg(Some(Color::Magenta));
Style::Addition => Ansi256Color::from_ansi(AnsiColor::BrightGreen).on_default(),
Style::Removal => Ansi256Color::from_ansi(AnsiColor::BrightRed).on_default(),
Style::LineAndColumn => anstyle::Style::new(),
Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
Style::Quotation => anstyle::Style::new(),
Style::MainHeaderMsg => if cfg!(windows) {
Ansi256Color::from_ansi(AnsiColor::BrightWhite).on_default()
} else {
anstyle::Style::new()
}
.effects(Effects::BOLD),
Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
}
spec
}
}

View File

@@ -15,6 +15,7 @@ use std::path::Path;
use std::sync::{Arc, Mutex};
use std::vec;
use anstream::{AutoStream, ColorChoice};
use derive_setters::Setters;
use rustc_data_structures::sync::IntoDynSyncSend;
use rustc_error_messages::FluentArgs;
@@ -23,7 +24,6 @@ use rustc_span::Span;
use rustc_span::hygiene::ExpnData;
use rustc_span::source_map::{FilePathMapping, SourceMap};
use serde::Serialize;
use termcolor::{ColorSpec, WriteColor};
use crate::diagnostic::IsLint;
use crate::emitter::{
@@ -333,7 +333,7 @@ impl Diagnostic {
// generate regular command line output and store it in the json
// A threadsafe buffer for writing.
#[derive(Default, Clone)]
#[derive(Clone)]
struct BufWriter(Arc<Mutex<Vec<u8>>>);
impl Write for BufWriter {
@@ -344,19 +344,6 @@ impl Diagnostic {
self.0.lock().unwrap().flush()
}
}
impl WriteColor for BufWriter {
fn supports_color(&self) -> bool {
false
}
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
Ok(())
}
fn reset(&mut self) -> io::Result<()> {
Ok(())
}
}
let translated_message = je.translator.translate_messages(&diag.messages, &args);
@@ -382,13 +369,15 @@ impl Diagnostic {
children
.insert(0, Diagnostic::from_sub_diagnostic(&diag.emitted_at_sub_diag(), &args, je));
}
let buf = BufWriter::default();
let mut dst: Destination = Box::new(buf.clone());
let buf = BufWriter(Arc::new(Mutex::new(Vec::new())));
let short = je.json_rendered.short();
match je.color_config {
ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)),
ColorConfig::Never => {}
}
let dst: Destination = AutoStream::new(
Box::new(buf.clone()),
match je.color_config.to_color_choice() {
ColorChoice::Auto => ColorChoice::Always,
choice => choice,
},
);
HumanEmitter::new(dst, je.translator.clone())
.short_message(short)
.sm(je.sm.clone())

View File

@@ -39,6 +39,12 @@ use std::path::{Path, PathBuf};
use std::{fmt, panic};
use Level::*;
// Used by external projects such as `rust-gpu`.
// See https://github.com/rust-lang/rust/pull/115393.
pub use anstream::{AutoStream, ColorChoice};
pub use anstyle::{
Ansi256Color, AnsiColor, Color, EffectIter, Effects, Reset, RgbColor, Style as Anstyle,
};
pub use codes::*;
pub use decorate_diag::{BufferedEarlyLint, DecorateDiagCompat, LintBuffer};
pub use diagnostic::{
@@ -69,9 +75,6 @@ pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, DUMMY_SP, Loc, Span};
pub use snippet::Style;
// Used by external projects such as `rust-gpu`.
// See https://github.com/rust-lang/rust/pull/115393.
pub use termcolor::{Color, ColorSpec, WriteColor};
use tracing::debug;
use crate::emitter::TimingEvent;
@@ -1961,25 +1964,23 @@ impl fmt::Display for Level {
}
impl Level {
fn color(self) -> ColorSpec {
let mut spec = ColorSpec::new();
fn color(self) -> anstyle::Style {
match self {
Bug | Fatal | Error | DelayedBug => {
spec.set_fg(Some(Color::Red)).set_intense(true);
Ansi256Color::from_ansi(AnsiColor::BrightRed).on_default()
}
ForceWarning | Warning => {
spec.set_fg(Some(Color::Yellow)).set_intense(cfg!(windows));
if cfg!(windows) {
Ansi256Color::from_ansi(AnsiColor::BrightYellow).on_default()
} else {
AnsiColor::Yellow.on_default()
}
}
Note | OnceNote => {
spec.set_fg(Some(Color::Green)).set_intense(true);
}
Help | OnceHelp => {
spec.set_fg(Some(Color::Cyan)).set_intense(true);
}
FailureNote => {}
Note | OnceNote => Ansi256Color::from_ansi(AnsiColor::BrightGreen).on_default(),
Help | OnceHelp => Ansi256Color::from_ansi(AnsiColor::BrightCyan).on_default(),
FailureNote => anstyle::Style::new(),
Allow | Expect => unreachable!(),
}
spec
}
pub fn to_str(self) -> &'static str {

View File

@@ -4,7 +4,6 @@
use std::io;
use termcolor::{Buffer, BufferWriter, ColorChoice};
mod parse;
mod term;
@@ -19,15 +18,15 @@ impl<'a> MdStream<'a> {
parse::entrypoint(s)
}
/// Write formatted output to a termcolor buffer
pub fn write_termcolor_buf(&self, buf: &mut Buffer) -> io::Result<()> {
/// Write formatted output to an anstream buffer
pub fn write_anstream_buf(&self, buf: &mut Vec<u8>) -> io::Result<()> {
term::entrypoint(self, buf)
}
}
/// Create a termcolor buffer with the `Always` color choice
pub fn create_stdout_bufwtr() -> BufferWriter {
BufferWriter::stdout(ColorChoice::Always)
/// Create an anstream buffer with the `Always` color choice
pub fn create_stdout_bufwtr() -> anstream::Stdout {
anstream::Stdout::always(std::io::stdout())
}
/// A single tokentree within a Markdown document

View File

@@ -1,11 +1,12 @@
use std::cell::Cell;
use std::io::{self, Write};
use termcolor::{Buffer, Color, ColorSpec, WriteColor};
use anstyle::{Ansi256Color, AnsiColor, Effects, Style};
use crate::markdown::{MdStream, MdTree};
const DEFAULT_COLUMN_WIDTH: usize = 140;
const RESET: anstyle::Reset = anstyle::Reset;
thread_local! {
/// Track the position of viewable characters in our buffer
@@ -15,7 +16,7 @@ thread_local! {
}
/// Print to terminal output to a buffer
pub(crate) fn entrypoint(stream: &MdStream<'_>, buf: &mut Buffer) -> io::Result<()> {
pub(crate) fn entrypoint(stream: &MdStream<'_>, buf: &mut Vec<u8>) -> io::Result<()> {
#[cfg(not(test))]
if let Some((w, _)) = termize::dimensions() {
WIDTH.set(std::cmp::min(w, DEFAULT_COLUMN_WIDTH));
@@ -23,57 +24,66 @@ pub(crate) fn entrypoint(stream: &MdStream<'_>, buf: &mut Buffer) -> io::Result<
write_stream(stream, buf, None, 0)?;
buf.write_all(b"\n")
}
/// Write the buffer, reset to the default style after each
fn write_stream(
MdStream(stream): &MdStream<'_>,
buf: &mut Buffer,
default: Option<&ColorSpec>,
buf: &mut Vec<u8>,
default: Option<Style>,
indent: usize,
) -> io::Result<()> {
match default {
Some(c) => buf.set_color(c)?,
None => buf.reset()?,
Some(c) => write!(buf, "{c:#}{c}")?,
None => write!(buf, "{RESET}")?,
}
for tt in stream {
write_tt(tt, buf, indent)?;
write_tt(tt, buf, default, indent)?;
if let Some(c) = default {
buf.set_color(c)?;
write!(buf, "{c:#}{c}")?;
}
}
buf.reset()?;
write!(buf, "{RESET}")?;
Ok(())
}
fn write_tt(tt: &MdTree<'_>, buf: &mut Buffer, indent: usize) -> io::Result<()> {
fn write_tt(
tt: &MdTree<'_>,
buf: &mut Vec<u8>,
_default: Option<Style>,
indent: usize,
) -> io::Result<()> {
match tt {
MdTree::CodeBlock { txt, lang: _ } => {
buf.set_color(ColorSpec::new().set_dimmed(true))?;
buf.write_all(txt.as_bytes())?;
write!(buf, "{RESET}")?;
let style = Style::new().effects(Effects::DIMMED);
write!(buf, "{style}{txt}")?;
}
MdTree::CodeInline(txt) => {
buf.set_color(ColorSpec::new().set_dimmed(true))?;
write_wrapping(buf, txt, indent, None)?;
write!(buf, "{RESET}")?;
let style = Style::new().effects(Effects::DIMMED);
write_wrapping(buf, txt, indent, None, Some(style))?;
}
MdTree::Strong(txt) => {
buf.set_color(ColorSpec::new().set_bold(true))?;
write_wrapping(buf, txt, indent, None)?;
write!(buf, "{RESET}")?;
let style = Style::new().effects(Effects::BOLD);
write_wrapping(buf, txt, indent, None, Some(style))?;
}
MdTree::Emphasis(txt) => {
buf.set_color(ColorSpec::new().set_italic(true))?;
write_wrapping(buf, txt, indent, None)?;
write!(buf, "{RESET}")?;
let style = Style::new().effects(Effects::ITALIC);
write_wrapping(buf, txt, indent, None, Some(style))?;
}
MdTree::Strikethrough(txt) => {
buf.set_color(ColorSpec::new().set_strikethrough(true))?;
write_wrapping(buf, txt, indent, None)?;
write!(buf, "{RESET}")?;
let style = Style::new().effects(Effects::STRIKETHROUGH);
write_wrapping(buf, txt, indent, None, Some(style))?;
}
MdTree::PlainText(txt) => {
write_wrapping(buf, txt, indent, None)?;
write_wrapping(buf, txt, indent, None, None)?;
}
MdTree::Link { disp, link } => {
write_wrapping(buf, disp, indent, Some(link))?;
write_wrapping(buf, disp, indent, Some(link), None)?;
}
MdTree::ParagraphBreak => {
buf.write_all(b"\n\n")?;
@@ -88,33 +98,37 @@ fn write_tt(tt: &MdTree<'_>, buf: &mut Buffer, indent: usize) -> io::Result<()>
reset_cursor();
}
MdTree::Heading(n, stream) => {
let mut cs = ColorSpec::new();
cs.set_fg(Some(Color::Cyan));
match n {
1 => cs.set_intense(true).set_bold(true).set_underline(true),
2 => cs.set_intense(true).set_underline(true),
3 => cs.set_intense(true).set_italic(true),
4.. => cs.set_underline(true).set_italic(true),
let cs = match n {
1 => Ansi256Color::from_ansi(AnsiColor::BrightCyan)
.on_default()
.effects(Effects::BOLD | Effects::UNDERLINE),
2 => Ansi256Color::from_ansi(AnsiColor::BrightCyan)
.on_default()
.effects(Effects::UNDERLINE),
3 => Ansi256Color::from_ansi(AnsiColor::BrightCyan)
.on_default()
.effects(Effects::ITALIC),
4.. => AnsiColor::Cyan.on_default().effects(Effects::UNDERLINE | Effects::ITALIC),
0 => unreachable!(),
};
write_stream(stream, buf, Some(&cs), 0)?;
write_stream(stream, buf, Some(cs), 0)?;
buf.write_all(b"\n")?;
}
MdTree::OrderedListItem(n, stream) => {
let base = format!("{n}. ");
write_wrapping(buf, &format!("{base:<4}"), indent, None)?;
write_wrapping(buf, &format!("{base:<4}"), indent, None, None)?;
write_stream(stream, buf, None, indent + 4)?;
}
MdTree::UnorderedListItem(stream) => {
let base = "* ";
write_wrapping(buf, &format!("{base:<4}"), indent, None)?;
write_wrapping(buf, &format!("{base:<4}"), indent, None, None)?;
write_stream(stream, buf, None, indent + 4)?;
}
// Patterns popped in previous step
MdTree::Comment(_) | MdTree::LinkDef { .. } | MdTree::RefLink { .. } => unreachable!(),
}
buf.reset()?;
write!(buf, "{RESET}")?;
Ok(())
}
@@ -126,12 +140,16 @@ fn reset_cursor() {
/// Change to be generic on Write for testing. If we have a link URL, we don't
/// count the extra tokens to make it clickable.
fn write_wrapping<B: io::Write>(
buf: &mut B,
fn write_wrapping(
buf: &mut Vec<u8>,
text: &str,
indent: usize,
link_url: Option<&str>,
style: Option<Style>,
) -> io::Result<()> {
if let Some(style) = &style {
write!(buf, "{style}")?;
}
let ind_ws = &b" "[..indent];
let mut to_write = text;
if let Some(url) = link_url {
@@ -179,7 +197,6 @@ fn write_wrapping<B: io::Write>(
if link_url.is_some() {
buf.write_all(b"\x1b]8;;\x1b\\")?;
}
Ok(())
})
}

View File

@@ -1,8 +1,5 @@
use std::io::BufWriter;
use std::path::PathBuf;
use termcolor::{BufferWriter, ColorChoice};
use super::*;
const INPUT: &str = include_str!("input.md");
@@ -35,19 +32,20 @@ quis dolor non venenatis. Aliquam ut. ";
#[test]
fn test_wrapping_write() {
WIDTH.with(|w| w.set(TEST_WIDTH));
let mut buf = BufWriter::new(Vec::new());
let mut buf = Vec::new();
let txt = TXT.replace("-\n", "-").replace("_\n", "_").replace('\n', " ").replace(" ", "");
write_wrapping(&mut buf, &txt, 0, None).unwrap();
write_wrapping(&mut buf, &txt, 4, None).unwrap();
write_wrapping(&mut buf, &txt, 0, None, None).unwrap();
write_wrapping(&mut buf, &txt, 4, None, None).unwrap();
write_wrapping(
&mut buf,
"Sample link lorem ipsum dolor sit amet. ",
4,
Some("link-address-placeholder"),
None,
)
.unwrap();
write_wrapping(&mut buf, &txt, 0, None).unwrap();
let out = String::from_utf8(buf.into_inner().unwrap()).unwrap();
write_wrapping(&mut buf, &txt, 0, None, None).unwrap();
let out = String::from_utf8(buf).unwrap();
let out = out
.replace("\x1b\\", "")
.replace('\x1b', "")
@@ -66,18 +64,17 @@ fn test_output() {
// Capture `--bless` when run via ./x
let bless = std::env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
let ast = MdStream::parse_str(INPUT);
let bufwtr = BufferWriter::stderr(ColorChoice::Always);
let mut buffer = bufwtr.buffer();
ast.write_termcolor_buf(&mut buffer).unwrap();
let mut buffer = Vec::new();
ast.write_anstream_buf(&mut buffer).unwrap();
let mut blessed = PathBuf::new();
blessed.extend(OUTPUT_PATH);
if bless {
std::fs::write(&blessed, buffer.into_inner()).unwrap();
std::fs::write(&blessed, buffer.as_slice()).unwrap();
eprintln!("blessed output at {}", blessed.display());
} else {
let output = buffer.into_inner();
let output = buffer.as_slice();
if std::fs::read(blessed).unwrap() != output {
// hack: I don't know any way to write bytes to the captured stdout
// that cargo test uses