Skip to content

Commit

Permalink
Auto merge of rust-lang#112697 - tgross35:explain-markdown, r=oli-obk
Browse files Browse the repository at this point in the history
Add simple markdown formatting to `rustc --explain` output

This is a second attempt at rust-lang#104540, which is rust-lang#63128 without dependencies.

This PR adds basic markdown formatting to `rustc --explain` output when available. Currently, the output just displays raw markdown: this works of course, but it really doesn't look very elegant. (output is `rustc --explain E0038`)

<img width="583" alt="image" src="https://github.com/rust-lang/rust/assets/13724985/ea418117-47af-455b-83c0-6fc59276efee">

After this patch, sample output from the same file:

<img width="693" alt="image" src="https://github.com/rust-lang/rust/assets/13724985/12f7bf9b-a3fe-4104-b74b-c3e5227f3de9">

This also obeys the `--color always/auto/never` command option. Behavior:

- If pager is available and supports color, print with formatting to the pager
- If pager is not available or fails print with formatting to stdout - otherwise without formatting
- Follow `--color always/never` if suppied
- If everything fails, just print plain text to stdout

r? `@oli-obk`
cc `@estebank`
(since the two of you were involved in the previous discussion)
  • Loading branch information
bors committed Jul 5, 2023
2 parents 9227ff2 + 6a1c10b commit 6dab6dc
Show file tree
Hide file tree
Showing 14 changed files with 1,406 additions and 17 deletions.
69 changes: 54 additions & 15 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rustc_data_structures::profiling::{
};
use rustc_data_structures::sync::SeqCst;
use rustc_errors::registry::{InvalidErrorCode, Registry};
use rustc_errors::{markdown, ColorConfig};
use rustc_errors::{
DiagnosticMessage, ErrorGuaranteed, Handler, PResult, SubdiagnosticMessage, TerminalUrl,
};
Expand Down Expand Up @@ -282,7 +283,7 @@ fn run_compiler(
interface::set_thread_safe_mode(&sopts.unstable_opts);

if let Some(ref code) = matches.opt_str("explain") {
handle_explain(&early_error_handler, diagnostics_registry(), code);
handle_explain(&early_error_handler, diagnostics_registry(), code, sopts.color);
return Ok(());
}

Expand Down Expand Up @@ -540,7 +541,7 @@ impl Compilation {
}
}

fn handle_explain(handler: &EarlyErrorHandler, registry: Registry, code: &str) {
fn handle_explain(handler: &EarlyErrorHandler, registry: Registry, code: &str, color: ColorConfig) {
let upper_cased_code = code.to_ascii_uppercase();
let normalised =
if upper_cased_code.starts_with('E') { upper_cased_code } else { format!("E{code:0>4}") };
Expand All @@ -564,7 +565,7 @@ fn handle_explain(handler: &EarlyErrorHandler, registry: Registry, code: &str) {
text.push('\n');
}
if io::stdout().is_terminal() {
show_content_with_pager(&text);
show_md_content_with_pager(&text, color);
} else {
safe_print!("{text}");
}
Expand All @@ -575,34 +576,72 @@ fn handle_explain(handler: &EarlyErrorHandler, registry: Registry, code: &str) {
}
}

fn show_content_with_pager(content: &str) {
/// If color is always or auto, print formatted & colorized markdown. If color is never or
/// if formatted printing fails, print the raw text.
///
/// Prefers a pager, falls back standard print
fn show_md_content_with_pager(content: &str, color: ColorConfig) {
let mut fallback_to_println = false;
let pager_name = env::var_os("PAGER").unwrap_or_else(|| {
if cfg!(windows) { OsString::from("more.com") } else { OsString::from("less") }
});

let mut fallback_to_println = false;
let mut cmd = Command::new(&pager_name);
// FIXME: find if other pagers accept color options
let mut print_formatted = if pager_name == "less" {
cmd.arg("-r");
true
} else if ["bat", "catbat", "delta"].iter().any(|v| *v == pager_name) {
true
} else {
false
};

match Command::new(pager_name).stdin(Stdio::piped()).spawn() {
Ok(mut pager) => {
if let Some(pipe) = pager.stdin.as_mut() {
if pipe.write_all(content.as_bytes()).is_err() {
fallback_to_println = true;
}
}
if color == ColorConfig::Never {
print_formatted = false;
} else if color == ColorConfig::Always {
print_formatted = true;
}

let mdstream = markdown::MdStream::parse_str(content);
let bufwtr = markdown::create_stdout_bufwtr();
let mut mdbuf = bufwtr.buffer();
if mdstream.write_termcolor_buf(&mut mdbuf).is_err() {
print_formatted = false;
}

if pager.wait().is_err() {
if let Ok(mut pager) = cmd.stdin(Stdio::piped()).spawn() {
if let Some(pipe) = pager.stdin.as_mut() {
let res = if print_formatted {
pipe.write_all(mdbuf.as_slice())
} else {
pipe.write_all(content.as_bytes())
};

if res.is_err() {
fallback_to_println = true;
}
}
Err(_) => {

if pager.wait().is_err() {
fallback_to_println = true;
}
} else {
fallback_to_println = true;
}

// If pager fails for whatever reason, we should still print the content
// to standard output
if fallback_to_println {
safe_print!("{content}");
let fmt_success = match color {
ColorConfig::Auto => io::stdout().is_terminal() && bufwtr.print(&mdbuf).is_ok(),
ColorConfig::Always => bufwtr.print(&mdbuf).is_ok(),
ColorConfig::Never => false,
};

if !fmt_success {
safe_print!("{content}");
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ rustc_hir = { path = "../rustc_hir" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
rustc_type_ir = { path = "../rustc_type_ir" }
unicode-width = "0.1.4"
termcolor = "1.0"
termcolor = "1.2.0"
annotate-snippets = "0.9"
termize = "0.1.1"
serde = { version = "1.0.125", features = [ "derive" ] }
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ pub enum ColorConfig {
}

impl ColorConfig {
fn to_color_choice(self) -> ColorChoice {
pub fn to_color_choice(self) -> ColorChoice {
match self {
ColorConfig::Always => {
if io::stderr().is_terminal() {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub mod emitter;
pub mod error;
pub mod json;
mod lock;
pub mod markdown;
pub mod registry;
mod snippet;
mod styled_buffer;
Expand Down
76 changes: 76 additions & 0 deletions compiler/rustc_errors/src/markdown/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! A simple markdown parser that can write formatted text to the terminal
//!
//! Entrypoint is `MdStream::parse_str(...)`
use std::io;

use termcolor::{Buffer, BufferWriter, ColorChoice};
mod parse;
mod term;

/// An AST representation of a Markdown document
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MdStream<'a>(Vec<MdTree<'a>>);

impl<'a> MdStream<'a> {
/// Parse a markdown string to a tokenstream
#[must_use]
pub fn parse_str(s: &str) -> MdStream<'_> {
parse::entrypoint(s)
}

/// Write formatted output to a termcolor buffer
pub fn write_termcolor_buf(&self, buf: &mut Buffer) -> 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)
}

/// A single tokentree within a Markdown document
#[derive(Clone, Debug, PartialEq)]
pub enum MdTree<'a> {
/// Leaf types
Comment(&'a str),
CodeBlock {
txt: &'a str,
lang: Option<&'a str>,
},
CodeInline(&'a str),
Strong(&'a str),
Emphasis(&'a str),
Strikethrough(&'a str),
PlainText(&'a str),
/// [Foo](www.foo.com) or simple anchor <www.foo.com>
Link {
disp: &'a str,
link: &'a str,
},
/// `[Foo link][ref]`
RefLink {
disp: &'a str,
id: Option<&'a str>,
},
/// [ref]: www.foo.com
LinkDef {
id: &'a str,
link: &'a str,
},
/// Break bewtween two paragraphs (double `\n`), not directly parsed but
/// added later
ParagraphBreak,
/// Break bewtween two lines (single `\n`)
LineBreak,
HorizontalRule,
Heading(u8, MdStream<'a>),
OrderedListItem(u16, MdStream<'a>),
UnorderedListItem(MdStream<'a>),
}

impl<'a> From<Vec<MdTree<'a>>> for MdStream<'a> {
fn from(value: Vec<MdTree<'a>>) -> Self {
Self(value)
}
}
Loading

0 comments on commit 6dab6dc

Please sign in to comment.