From 7c7d10a9fe816918cf9a054026798419c859320c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 22 Jan 2017 12:59:49 -0800 Subject: [PATCH 1/3] Move code-highlight methods from rustdoc to own crate --- src/Cargo.lock | 10 ++++++ src/librustc_highlight/Cargo.toml | 14 ++++++++ .../html => librustc_highlight}/escape.rs | 0 .../html => librustc_highlight}/highlight.rs | 4 +-- src/librustc_highlight/lib.rs | 32 +++++++++++++++++++ src/librustdoc/Cargo.toml | 1 + src/librustdoc/lib.rs | 6 ++-- 7 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 src/librustc_highlight/Cargo.toml rename src/{librustdoc/html => librustc_highlight}/escape.rs (100%) rename src/{librustdoc/html => librustc_highlight}/highlight.rs (99%) create mode 100644 src/librustc_highlight/lib.rs diff --git a/src/Cargo.lock b/src/Cargo.lock index d153945dc091f..d07beae61f486 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -376,6 +376,15 @@ dependencies = [ "syntax_pos 0.0.0", ] +[[package]] +name = "rustc_highlight" +version = "0.0.0" +dependencies = [ + "log 0.0.0", + "syntax 0.0.0", + "syntax_pos 0.0.0", +] + [[package]] name = "rustc_i128" version = "0.0.0" @@ -572,6 +581,7 @@ dependencies = [ "rustc_data_structures 0.0.0", "rustc_driver 0.0.0", "rustc_errors 0.0.0", + "rustc_highlight 0.0.0", "rustc_lint 0.0.0", "rustc_metadata 0.0.0", "rustc_resolve 0.0.0", diff --git a/src/librustc_highlight/Cargo.toml b/src/librustc_highlight/Cargo.toml new file mode 100644 index 0000000000000..c205ed5ab396a --- /dev/null +++ b/src/librustc_highlight/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The Rust Project Developers"] +name = "rustc_highlight" +version = "0.0.0" + +[lib] +name = "rustc_highlight" +path = "lib.rs" +crate-type = ["dylib"] + +[dependencies] +syntax = { path = "../libsyntax" } +syntax_pos = { path = "../libsyntax_pos" } +log = { path = "../liblog" } diff --git a/src/librustdoc/html/escape.rs b/src/librustc_highlight/escape.rs similarity index 100% rename from src/librustdoc/html/escape.rs rename to src/librustc_highlight/escape.rs diff --git a/src/librustdoc/html/highlight.rs b/src/librustc_highlight/highlight.rs similarity index 99% rename from src/librustdoc/html/highlight.rs rename to src/librustc_highlight/highlight.rs index 0629e93e7ef5d..71a8ff59db68d 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustc_highlight/highlight.rs @@ -1,4 +1,4 @@ -// Copyright 2014-2016 The Rust Project Developers. See the COPYRIGHT +// Copyright 2014-2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -20,7 +20,7 @@ //! other then HTML), then you should implement the `Writer` trait and use a //! `Classifier`. -use html::escape::Escape; +use escape::Escape; use std::fmt::Display; use std::io; diff --git a/src/librustc_highlight/lib.rs b/src/librustc_highlight/lib.rs new file mode 100644 index 0000000000000..6d8026657c4ff --- /dev/null +++ b/src/librustc_highlight/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_name = "rustc_highlight"] +#![unstable(feature = "rustc_highlight", issue = "9999")] +#![crate_type = "dylib"] +#![crate_type = "rlib"] +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + html_playground_url = "https://play.rust-lang.org/")] +#![deny(warnings)] + +#![feature(box_patterns)] +#![feature(box_syntax)] +#![feature(rustc_private)] +#![feature(slice_patterns)] +#![feature(staged_api)] + +#[macro_use] extern crate syntax; +extern crate syntax_pos; +#[macro_use] extern crate log; + +pub mod highlight; +pub mod escape; diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 93c0bd6d6d836..534843be396bb 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -21,6 +21,7 @@ rustc_lint = { path = "../librustc_lint" } rustc_metadata = { path = "../librustc_metadata" } rustc_resolve = { path = "../librustc_resolve" } rustc_trans = { path = "../librustc_trans" } +rustc_highlight = { path = "../librustc_highlight" } serialize = { path = "../libserialize" } syntax = { path = "../libsyntax" } syntax_pos = { path = "../libsyntax_pos" } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 503ef4c3183d2..60797b226d24f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -27,6 +27,7 @@ #![feature(staged_api)] #![feature(test)] #![feature(unicode)] +#![feature(rustc_highlight)] extern crate arena; extern crate getopts; @@ -40,6 +41,7 @@ extern crate rustc_resolve; extern crate rustc_lint; extern crate rustc_back; extern crate rustc_metadata; +extern crate rustc_highlight; extern crate serialize; #[macro_use] extern crate syntax; extern crate syntax_pos; @@ -73,8 +75,8 @@ pub mod core; pub mod doctree; pub mod fold; pub mod html { - pub mod highlight; - pub mod escape; + pub use rustc_highlight::highlight; + pub use rustc_highlight::escape; pub mod item_type; pub mod format; pub mod layout; From 6eb3dfbdc59610fe3290ceff54e80d3236387ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 23 Jan 2017 22:57:57 -0800 Subject: [PATCH 2/3] Add stdout syntax highlighter and highlight `--explain` code --- src/Cargo.lock | 1 + src/librustc_driver/Cargo.toml | 1 + src/librustc_driver/lib.rs | 49 +++++- src/librustc_highlight/highlight.rs | 252 +++++++++++++++++++++------- src/librustc_highlight/lib.rs | 4 +- 5 files changed, 236 insertions(+), 71 deletions(-) diff --git a/src/Cargo.lock b/src/Cargo.lock index d07beae61f486..2a12828f433fd 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -349,6 +349,7 @@ dependencies = [ "rustc_const_eval 0.0.0", "rustc_data_structures 0.0.0", "rustc_errors 0.0.0", + "rustc_highlight 0.0.0", "rustc_incremental 0.0.0", "rustc_lint 0.0.0", "rustc_llvm 0.0.0", diff --git a/src/librustc_driver/Cargo.toml b/src/librustc_driver/Cargo.toml index 99d3e155e8936..93cdeda5b089f 100644 --- a/src/librustc_driver/Cargo.toml +++ b/src/librustc_driver/Cargo.toml @@ -32,6 +32,7 @@ rustc_resolve = { path = "../librustc_resolve" } rustc_save_analysis = { path = "../librustc_save_analysis" } rustc_trans = { path = "../librustc_trans" } rustc_typeck = { path = "../librustc_typeck" } +rustc_highlight = { path = "../librustc_highlight" } serialize = { path = "../libserialize" } syntax = { path = "../libsyntax" } syntax_ext = { path = "../libsyntax_ext" } diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs index 48eb6f68564f0..f059c5931b5d2 100644 --- a/src/librustc_driver/lib.rs +++ b/src/librustc_driver/lib.rs @@ -30,6 +30,7 @@ #![feature(rustc_private)] #![feature(set_stdio)] #![feature(staged_api)] +#![feature(rustc_highlight)] extern crate arena; extern crate flate; @@ -53,6 +54,7 @@ extern crate rustc_resolve; extern crate rustc_save_analysis; extern crate rustc_trans; extern crate rustc_typeck; +extern crate rustc_highlight; extern crate serialize; extern crate rustc_llvm as llvm; #[macro_use] @@ -78,6 +80,7 @@ use rustc::lint::Lint; use rustc::lint; use rustc_metadata::locator; use rustc_metadata::cstore::CStore; +use rustc_highlight::highlight::render_to_stdout_with_highlighting; use rustc::util::common::time; use serialize::json::ToJson; @@ -349,6 +352,43 @@ pub trait CompilerCalls<'a> { #[derive(Copy, Clone)] pub struct RustcDefaultCalls; +fn print_msg(text: &str) { + let mut code = String::new(); + let mut in_code = false; + let mut code_is_rust = false; + for (i, line) in text.split('\n').enumerate() { + // Slice off the leading newline and print. + if i == 0 && line.len() == 0 { + continue; + } + if line.starts_with("```") { + if in_code { + if code_is_rust { + println!("rust"); + render_to_stdout_with_highlighting(code); + } else { + render_to_stdout_with_highlighting(code); + } + code = String::new(); + code_is_rust = false; + } else { + if line.starts_with("```rust") { + code_is_rust = true; + } + } + in_code = !in_code; + println!("```"); + } else { + if in_code { + code.push_str(&line); + code.push_str("\n"); + } else { + println!("{}", line); + } + } + } +} + fn handle_explain(code: &str, descriptions: &errors::registry::Registry, output: ErrorOutputType) { @@ -359,14 +399,7 @@ fn handle_explain(code: &str, }; match descriptions.find_description(&normalised) { Some(ref description) => { - // Slice off the leading newline and print. - print!("{}", &(&description[1..]).split("\n").map(|x| { - format!("{}\n", if x.starts_with("```") { - "```" - } else { - x - }) - }).collect::()); + print_msg(description); } None => { early_error(output, &format!("no extended information for {}", code)); diff --git a/src/librustc_highlight/highlight.rs b/src/librustc_highlight/highlight.rs index 71a8ff59db68d..3b950c9388974 100644 --- a/src/librustc_highlight/highlight.rs +++ b/src/librustc_highlight/highlight.rs @@ -31,41 +31,166 @@ use syntax::parse::lexer::{self, TokenAndSpan}; use syntax::parse::token; use syntax::parse; use syntax_pos::Span; +use term::{color, Terminal, stdout, Attr}; +use term::Attr::{ForegroundColor, Bold, Dim, Underline}; + +pub trait Output { + fn render>(&self, src: Str) -> io::Result { + let src = src.into(); + let mut output = Vec::with_capacity(src.len()); + self.render_to(src, &mut output)?; + Ok(String::from_utf8_lossy(&output[..]).into_owned()) + } -/// Highlights `src`, returning the HTML output. -pub fn render_with_highlighting(src: &str, class: Option<&str>, id: Option<&str>, - extension: Option<&str>) -> String { - debug!("highlighting: ================\n{}\n==============", src); - let sess = parse::ParseSess::new(); - let fm = sess.codemap().new_filemap("".to_string(), None, src.to_string()); + fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { + write!(target, "{}", src) + } - let mut out = Vec::new(); - write_header(class, id, &mut out).unwrap(); + /// Highlights `src`, returning the HTML output. If any errors have happened, return the + /// original code surrounded by `pre` tags. + fn render_always>(&self, src: Str) -> String { + let src = src.into(); + match self.render(src.clone()) { + Ok(s) => s, + Err(_) => self.escape(src), + } + } - let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap()); - if let Err(_) = classifier.write_source(&mut out) { - return format!("
{}
", src); + fn escape(&self, src: String) -> String { + src } +} - if let Some(extension) = extension { - write!(out, "{}", extension).unwrap(); +// Implement `Writer` for anthing that can be written to, this just implements +// the default rustdoc behaviour for HTML output. +impl Writer for Vec { + fn string(&mut self, + text: T, + klass: Class, + _tas: Option<&TokenAndSpan>) + -> io::Result<()> { + match klass { + Class::None => write!(self, "{}", text), + klass => write!(self, + "{}", + klass.rustdoc_class(), + Escape(&format!("{}", text))), + } + } + + fn enter_span(&mut self, klass: Class) -> io::Result<()> { + write!(self, "", klass.rustdoc_class()) + } + + fn exit_span(&mut self) -> io::Result<()> { + write!(self, "") } - write_footer(&mut out).unwrap(); - String::from_utf8_lossy(&out[..]).into_owned() } -/// Highlights `src`, returning the HTML output. Returns only the inner html to -/// be inserted into an element. C.f., `render_with_highlighting` which includes -/// an enclosing `
` block.
-pub fn render_inner_with_highlighting(src: &str) -> io::Result {
-    let sess = parse::ParseSess::new();
-    let fm = sess.codemap().new_filemap("".to_string(), None, src.to_string());
+// This implement behaviour for terminal output.
+impl Writer for Box + Send> {
+    fn string(&mut self,
+                          text: T,
+                          klass: Class,
+                          _tas: Option<&TokenAndSpan>)
+                          -> io::Result<()> {
+        for attr in klass.term_style() {
+            self.attr(attr)?;
+        }
+        write!(self, "{}", text)?;
+        let _ = self.reset();
+        Ok(())
+    }
+
+    fn enter_span(&mut self, klass: Class) -> io::Result<()> {
+        for attr in klass.term_style() {
+            self.attr(attr)?;
+        }
+        Ok(())
+    }
 
-    let mut out = Vec::new();
-    let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap());
-    classifier.write_source(&mut out)?;
+    fn exit_span(&mut self) -> io::Result<()> {
+        let _ = self.reset();
+        Ok(())
+    }
+}
 
-    Ok(String::from_utf8_lossy(&out).into_owned())
+struct HtmlOutput<'a> {
+    /// Highlights `src`, returning the HTML output. If false, `render` returns only
+    /// the inner html to be inserted into an element, otherwise include an enclosing
+    /// `
` block.
+    pub enclose: bool,
+    pub class: Option<&'a str>,
+    pub id: Option<&'a str>,
+    pub extension: Option<&'a str>,
+}
+
+impl<'a> HtmlOutput<'a> {
+    fn write_header(&self, out: &mut Write) -> io::Result<()> {
+        write!(out, "
\n", self.class.unwrap_or(""))
+    }
+
+    fn write_footer(&self, out: &mut Write) -> io::Result<()> {
+        write!(out, "
\n") + } +} + +impl<'a> Output for HtmlOutput<'a> { + /// Highlights `src`, returning the HTML output. + fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { + let src = src.into(); + debug!("html highlighting: ================\n{}\n==============", src); + let mut out = Vec::new(); + if self.enclose { + self.write_header(&mut out)?; + } + + let sess = parse::ParseSess::new(); + let fm = sess.codemap().new_filemap("".to_string(), None, src); + let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap()); + classifier.write_source(&mut out)?; + + if let Some(extension) = self.extension { + write!(out, "{}", extension)?; + } + if self.enclose { + self.write_footer(&mut out)?; + } + write!(target, "{}", String::from_utf8_lossy(&out[..])) + } + + /// Highlights `src`, returning the HTML output. If any errors have happened, return the + /// original code surrounded by `pre` tags. + fn render_always>(&self, src: Str) -> String { + let src = src.into(); + match self.render(src.clone()) { + Ok(s) => s, + Err(_) => format!("
{}
", src), + } + } + + fn escape(&self, src: String) -> String { + format!("{}", Escape(&src)) + } +} + +pub struct TermOutput {} + +impl Output for TermOutput { + /// Highlights `src`, returning the HTML output. + fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { + let src = src.into(); + debug!("term highlighting: ================\n{}\n==============", src); + + let sess = parse::ParseSess::new(); + let fm = sess.codemap().new_filemap("".to_string(), None, src); + let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap()); + classifier.write_source(target) + } } /// Processes a program (nested in the internal `lexer`), classifying strings of @@ -131,30 +256,11 @@ pub trait Writer { /// ``` /// The latter can be thought of as a shorthand for the former, which is /// more flexible. - fn string(&mut self, T, Class, Option<&TokenAndSpan>) -> io::Result<()>; -} - -// Implement `Writer` for anthing that can be written to, this just implements -// the default rustdoc behaviour. -impl Writer for U { fn string(&mut self, text: T, klass: Class, _tas: Option<&TokenAndSpan>) - -> io::Result<()> { - match klass { - Class::None => write!(self, "{}", text), - klass => write!(self, "{}", klass.rustdoc_class(), text), - } - } - - fn enter_span(&mut self, klass: Class) -> io::Result<()> { - write!(self, "", klass.rustdoc_class()) - } - - fn exit_span(&mut self) -> io::Result<()> { - write!(self, "") - } + -> io::Result<()>; } impl<'a> Classifier<'a> { @@ -175,7 +281,7 @@ impl<'a> Classifier<'a> { /// is used. All source code emission is done as slices from the source map, /// not from the tokens themselves, in order to stay true to the original /// source. - pub fn write_source(&mut self, + pub fn write_source(&mut self, out: &mut W) -> io::Result<()> { loop { @@ -202,13 +308,13 @@ impl<'a> Classifier<'a> { } // Handles an individual token from the lexer. - fn write_token(&mut self, + fn write_token(&mut self, out: &mut W, tas: TokenAndSpan) -> io::Result<()> { let klass = match tas.tok { token::Shebang(s) => { - out.string(Escape(&s.as_str()), Class::None, Some(&tas))?; + out.string(&s.as_str(), Class::None, Some(&tas))?; return Ok(()); }, @@ -320,7 +426,7 @@ impl<'a> Classifier<'a> { // Anything that didn't return above is the simple case where we the // class just spans a single token, so we can use the `string` method. - out.string(Escape(&self.snip(tas.sp)), klass, Some(&tas)) + out.string(&self.snip(tas.sp), klass, Some(&tas)) } // Helper function to get a snippet from the codemap. @@ -353,19 +459,45 @@ impl Class { Class::QuestionMark => "question-mark" } } -} -fn write_header(class: Option<&str>, - id: Option<&str>, - out: &mut Write) - -> io::Result<()> { - write!(out, "
 Vec {
+        match self {
+            Class::None => vec![],
+            Class::Comment => vec![ForegroundColor(color::GREEN)],
+            Class::DocComment => vec![ForegroundColor(color::MAGENTA)],
+            Class::Attribute => vec![Bold],
+            Class::KeyWord => vec![Dim, ForegroundColor(color::YELLOW)],
+            Class::RefKeyWord => vec![Dim, ForegroundColor(color::MAGENTA)],
+            Class::Self_ => vec![ForegroundColor(color::CYAN)],
+            Class::Op => vec![ForegroundColor(color::YELLOW)],
+            Class::Macro => vec![ForegroundColor(color::MAGENTA), Bold],
+            Class::MacroNonTerminal => vec![ForegroundColor(color::MAGENTA), Bold, Underline(true)],
+            Class::String => vec![Underline(true)],
+            Class::Number => vec![ForegroundColor(color::CYAN)],
+            Class::Bool => vec![ForegroundColor(color::CYAN)],
+            Class::Ident => vec![],
+            Class::Lifetime => vec![Dim, ForegroundColor(color::MAGENTA)],
+            Class::PreludeTy => vec![ForegroundColor(color::GREEN)],
+            Class::PreludeVal => vec![ForegroundColor(color::CYAN)],
+            Class::QuestionMark => vec![Bold, ForegroundColor(color::GREEN)],
+        }
+
     }
-    write!(out, "class='rust {}'>\n", class.unwrap_or(""))
 }
 
-fn write_footer(out: &mut Write) -> io::Result<()> {
-    write!(out, "
\n") +pub fn render_with_highlighting(src: &str, class: Option<&str>, id: Option<&str>, + extension: Option<&str>) -> String { + let output = HtmlOutput { + enclose: false, + class: class, + id: id, + extension: extension, + }; + output.render_always(src) +} + +pub fn render_to_stdout_with_highlighting(src: String) { + let output = TermOutput {}; + let mut t = stdout().unwrap(); + output.render_to(src, &mut t).unwrap(); } diff --git a/src/librustc_highlight/lib.rs b/src/librustc_highlight/lib.rs index 6d8026657c4ff..b79b3d6bc6dde 100644 --- a/src/librustc_highlight/lib.rs +++ b/src/librustc_highlight/lib.rs @@ -18,15 +18,13 @@ html_playground_url = "https://play.rust-lang.org/")] #![deny(warnings)] -#![feature(box_patterns)] -#![feature(box_syntax)] #![feature(rustc_private)] -#![feature(slice_patterns)] #![feature(staged_api)] #[macro_use] extern crate syntax; extern crate syntax_pos; #[macro_use] extern crate log; +extern crate term; pub mod highlight; pub mod escape; From 7c7ad9f687699640b4b2e49b1b370f4df0fe4384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 24 Jan 2017 23:53:34 -0800 Subject: [PATCH 3/3] Simplify higlighting logic back while keeping term output --- src/librustc_highlight/highlight.rs | 281 +++++++++++----------------- 1 file changed, 109 insertions(+), 172 deletions(-) diff --git a/src/librustc_highlight/highlight.rs b/src/librustc_highlight/highlight.rs index 3b950c9388974..f547a07243cce 100644 --- a/src/librustc_highlight/highlight.rs +++ b/src/librustc_highlight/highlight.rs @@ -34,165 +34,6 @@ use syntax_pos::Span; use term::{color, Terminal, stdout, Attr}; use term::Attr::{ForegroundColor, Bold, Dim, Underline}; -pub trait Output { - fn render>(&self, src: Str) -> io::Result { - let src = src.into(); - let mut output = Vec::with_capacity(src.len()); - self.render_to(src, &mut output)?; - Ok(String::from_utf8_lossy(&output[..]).into_owned()) - } - - fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { - write!(target, "{}", src) - } - - /// Highlights `src`, returning the HTML output. If any errors have happened, return the - /// original code surrounded by `pre` tags. - fn render_always>(&self, src: Str) -> String { - let src = src.into(); - match self.render(src.clone()) { - Ok(s) => s, - Err(_) => self.escape(src), - } - } - - fn escape(&self, src: String) -> String { - src - } -} - -// Implement `Writer` for anthing that can be written to, this just implements -// the default rustdoc behaviour for HTML output. -impl Writer for Vec { - fn string(&mut self, - text: T, - klass: Class, - _tas: Option<&TokenAndSpan>) - -> io::Result<()> { - match klass { - Class::None => write!(self, "{}", text), - klass => write!(self, - "{}", - klass.rustdoc_class(), - Escape(&format!("{}", text))), - } - } - - fn enter_span(&mut self, klass: Class) -> io::Result<()> { - write!(self, "", klass.rustdoc_class()) - } - - fn exit_span(&mut self) -> io::Result<()> { - write!(self, "") - } -} - -// This implement behaviour for terminal output. -impl Writer for Box + Send> { - fn string(&mut self, - text: T, - klass: Class, - _tas: Option<&TokenAndSpan>) - -> io::Result<()> { - for attr in klass.term_style() { - self.attr(attr)?; - } - write!(self, "{}", text)?; - let _ = self.reset(); - Ok(()) - } - - fn enter_span(&mut self, klass: Class) -> io::Result<()> { - for attr in klass.term_style() { - self.attr(attr)?; - } - Ok(()) - } - - fn exit_span(&mut self) -> io::Result<()> { - let _ = self.reset(); - Ok(()) - } -} - -struct HtmlOutput<'a> { - /// Highlights `src`, returning the HTML output. If false, `render` returns only - /// the inner html to be inserted into an element, otherwise include an enclosing - /// `
` block.
-    pub enclose: bool,
-    pub class: Option<&'a str>,
-    pub id: Option<&'a str>,
-    pub extension: Option<&'a str>,
-}
-
-impl<'a> HtmlOutput<'a> {
-    fn write_header(&self, out: &mut Write) -> io::Result<()> {
-        write!(out, "
\n", self.class.unwrap_or(""))
-    }
-
-    fn write_footer(&self, out: &mut Write) -> io::Result<()> {
-        write!(out, "
\n") - } -} - -impl<'a> Output for HtmlOutput<'a> { - /// Highlights `src`, returning the HTML output. - fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { - let src = src.into(); - debug!("html highlighting: ================\n{}\n==============", src); - let mut out = Vec::new(); - if self.enclose { - self.write_header(&mut out)?; - } - - let sess = parse::ParseSess::new(); - let fm = sess.codemap().new_filemap("".to_string(), None, src); - let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap()); - classifier.write_source(&mut out)?; - - if let Some(extension) = self.extension { - write!(out, "{}", extension)?; - } - if self.enclose { - self.write_footer(&mut out)?; - } - write!(target, "{}", String::from_utf8_lossy(&out[..])) - } - - /// Highlights `src`, returning the HTML output. If any errors have happened, return the - /// original code surrounded by `pre` tags. - fn render_always>(&self, src: Str) -> String { - let src = src.into(); - match self.render(src.clone()) { - Ok(s) => s, - Err(_) => format!("
{}
", src), - } - } - - fn escape(&self, src: String) -> String { - format!("{}", Escape(&src)) - } -} - -pub struct TermOutput {} - -impl Output for TermOutput { - /// Highlights `src`, returning the HTML output. - fn render_to(&self, src: String, target: &mut W) -> io::Result<()> { - let src = src.into(); - debug!("term highlighting: ================\n{}\n==============", src); - - let sess = parse::ParseSess::new(); - let fm = sess.codemap().new_filemap("".to_string(), None, src); - let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap()); - classifier.write_source(target) - } -} - /// Processes a program (nested in the internal `lexer`), classifying strings of /// text by highlighting category (`Class`). Calls out to a `Writer` to write /// each span of text in sequence. @@ -470,8 +311,8 @@ impl Class { Class::RefKeyWord => vec![Dim, ForegroundColor(color::MAGENTA)], Class::Self_ => vec![ForegroundColor(color::CYAN)], Class::Op => vec![ForegroundColor(color::YELLOW)], - Class::Macro => vec![ForegroundColor(color::MAGENTA), Bold], - Class::MacroNonTerminal => vec![ForegroundColor(color::MAGENTA), Bold, Underline(true)], + Class::Macro => vec![ForegroundColor(color::MAGENTA)], + Class::MacroNonTerminal => vec![ForegroundColor(color::MAGENTA), Bold], Class::String => vec![Underline(true)], Class::Number => vec![ForegroundColor(color::CYAN)], Class::Bool => vec![ForegroundColor(color::CYAN)], @@ -485,19 +326,115 @@ impl Class { } } -pub fn render_with_highlighting(src: &str, class: Option<&str>, id: Option<&str>, - extension: Option<&str>) -> String { - let output = HtmlOutput { - enclose: false, - class: class, - id: id, - extension: extension, - }; - output.render_always(src) +// You can implement `Writer` for anthing that can be written to, this just implements +// the default rustdoc behaviour for HTML output. +impl Writer for Vec { + fn string(&mut self, + text: T, + klass: Class, + _tas: Option<&TokenAndSpan>) + -> io::Result<()> { + match klass { + Class::None => write!(self, "{}", text), + klass => write!(self, + "{}", + klass.rustdoc_class(), + Escape(&format!("{}", text))), + } + } + + fn enter_span(&mut self, klass: Class) -> io::Result<()> { + write!(self, "", klass.rustdoc_class()) + } + + fn exit_span(&mut self) -> io::Result<()> { + write!(self, "") + } } +// This implements behaviour for terminal output. +impl Writer for Box + Send> { + fn string(&mut self, + text: T, + klass: Class, + _tas: Option<&TokenAndSpan>) + -> io::Result<()> { + for attr in klass.term_style() { + self.attr(attr)?; + } + write!(self, "{}", text)?; + let _ = self.reset(); + Ok(()) + } + + fn enter_span(&mut self, klass: Class) -> io::Result<()> { + for attr in klass.term_style() { + self.attr(attr)?; + } + Ok(()) + } + + fn exit_span(&mut self) -> io::Result<()> { + let _ = self.reset(); + Ok(()) + } +} + +fn render_maybe_with_highlighting(src: String, class: Option<&str>, id: Option<&str>, + extension: Option<&str>, enclose: bool) -> io::Result { + debug!("html highlighting: ================\n{}\n==============", src); + let mut out = Vec::new(); + + if enclose { + write!(out, "
\n", class.unwrap_or(""))?;
+    }
+
+    let sess = parse::ParseSess::new();
+    let fm = sess.codemap().new_filemap("".to_string(), None, src);
+    let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap());
+    classifier.write_source(&mut out)?;
+
+    if let Some(extension) = extension {
+        write!(out, "{}", extension)?;
+    }
+
+    if enclose {
+        write!(out, "
\n")?; + } + + Ok(String::from_utf8_lossy(&out[..]).to_string()) +} + +/// Highlights `src`, returning the HTML output. Returns only the inner html to +/// be inserted into an element. C.f., `render_with_highlighting` which includes +/// an enclosing `
` block.
+pub fn render_inner_with_highlighting(src: &str) -> io::Result {
+    render_maybe_with_highlighting(src.to_string(), None, None, None, false)
+}
+
+/// Highlights `src`, returning the HTML output.
+pub fn render_with_highlighting(src: &str,
+                                class: Option<&str>,
+                                id: Option<&str>,
+                                extension: Option<&str>)
+                                -> String {
+    match render_maybe_with_highlighting(src.to_string(), class, id, extension, true) {
+        Ok(s) => s,
+        Err(_) => src.to_string(),
+    }
+}
+
+/// Highlights `src` and prints it out to stderr.
 pub fn render_to_stdout_with_highlighting(src: String) {
-    let output = TermOutput {};
+    debug!("term highlighting: ================\n{}\n==============", src);
     let mut t = stdout().unwrap();
-    output.render_to(src, &mut t).unwrap();
+
+    let sess = parse::ParseSess::new();
+    let fm = sess.codemap().new_filemap("".to_string(), None, src);
+    let mut classifier = Classifier::new(lexer::StringReader::new(&sess, fm), sess.codemap());
+    let _ = classifier.write_source(&mut t);
 }