blob: 2204dd103367bf1472333dc52ac72daa1895792d [file] [log] [blame]
use std::cmp;
use std::fmt;
use std::io;
use std::ops::Deref;
#[cfg(feature = "termcolor")]
use termcolor::{ColorSpec, WriteColor};
use Doc;
/// Trait representing the operations necessary to render a document
pub trait Render {
type Error;
fn write_str(&mut self, s: &str) -> Result<usize, Self::Error>;
fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> {
while !s.is_empty() {
let count = self.write_str(s)?;
s = &s[count..];
}
Ok(())
}
}
/// Writes to something implementing `std::io::Write`
pub struct IoWrite<W> {
upstream: W,
}
impl<W> IoWrite<W> {
pub fn new(upstream: W) -> IoWrite<W> {
IoWrite { upstream }
}
}
impl<W> Render for IoWrite<W>
where
W: io::Write,
{
type Error = io::Error;
fn write_str(&mut self, s: &str) -> io::Result<usize> {
self.upstream.write(s.as_bytes())
}
fn write_str_all(&mut self, s: &str) -> io::Result<()> {
self.upstream.write_all(s.as_bytes())
}
}
/// Writes to something implementing `std::fmt::Write`
pub struct FmtWrite<W> {
upstream: W,
}
impl<W> FmtWrite<W> {
pub fn new(upstream: W) -> FmtWrite<W> {
FmtWrite { upstream }
}
}
impl<W> Render for FmtWrite<W>
where
W: fmt::Write,
{
type Error = fmt::Error;
fn write_str(&mut self, s: &str) -> Result<usize, fmt::Error> {
self.write_str_all(s).map(|_| s.len())
}
fn write_str_all(&mut self, s: &str) -> fmt::Result {
self.upstream.write_str(s)
}
}
/// Trait representing the operations necessary to write an annotated document.
pub trait RenderAnnotated<A>: Render {
fn push_annotation(&mut self, annotation: &A) -> Result<(), Self::Error>;
fn pop_annotation(&mut self) -> Result<(), Self::Error>;
}
impl<A, W> RenderAnnotated<A> for IoWrite<W>
where
W: io::Write,
{
fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> {
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
impl<A, W> RenderAnnotated<A> for FmtWrite<W>
where
W: fmt::Write,
{
fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> {
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
#[cfg(feature = "termcolor")]
pub struct TermColored<W> {
color_stack: Vec<ColorSpec>,
upstream: W,
}
#[cfg(feature = "termcolor")]
impl<W> TermColored<W> {
pub fn new(upstream: W) -> TermColored<W> {
TermColored {
color_stack: Vec::new(),
upstream,
}
}
}
#[cfg(feature = "termcolor")]
impl<W> Render for TermColored<W>
where
W: io::Write,
{
type Error = io::Error;
fn write_str(&mut self, s: &str) -> io::Result<usize> {
self.upstream.write(s.as_bytes())
}
fn write_str_all(&mut self, s: &str) -> io::Result<()> {
self.upstream.write_all(s.as_bytes())
}
}
#[cfg(feature = "termcolor")]
impl<W> RenderAnnotated<ColorSpec> for TermColored<W>
where
W: WriteColor,
{
fn push_annotation(&mut self, color: &ColorSpec) -> Result<(), Self::Error> {
self.color_stack.push(color.clone());
self.upstream.set_color(color)
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
self.color_stack.pop();
match self.color_stack.last() {
Some(previous) => self.upstream.set_color(previous),
None => self.upstream.reset(),
}
}
}
#[inline]
pub fn best<'a, W, T, A>(doc: &'a Doc<'a, T, A>, width: usize, out: &mut W) -> Result<(), W::Error>
where
T: Deref<Target = Doc<'a, T, A>>,
W: ?Sized + RenderAnnotated<A>,
{
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
enum Mode {
Break,
Flat,
}
type Cmd<'a, T, A> = (usize, Mode, &'a Doc<'a, T, A>);
fn write_newline<W>(ind: usize, out: &mut W) -> Result<(), W::Error>
where
W: ?Sized + Render,
{
out.write_str_all("\n")?;
write_spaces(ind, out)
}
fn write_spaces<W>(spaces: usize, out: &mut W) -> Result<(), W::Error>
where
W: ?Sized + Render,
{
macro_rules! make_spaces {
() => { "" };
($s: tt $($t: tt)*) => { concat!(" ", make_spaces!($($t)*)) };
}
const SPACES: &str = make_spaces!(,,,,,,,,,,);
let mut inserted = 0;
while inserted < spaces {
let insert = cmp::min(SPACES.len(), spaces - inserted);
inserted += out.write_str(&SPACES[..insert])?;
}
Ok(())
}
#[inline]
fn fitting<'a, T, A>(
next: Cmd<'a, T, A>,
bcmds: &[Cmd<'a, T, A>],
fcmds: &mut Vec<Cmd<'a, T, A>>,
mut rem: isize,
) -> bool
where
T: Deref<Target = Doc<'a, T, A>>,
{
let mut bidx = bcmds.len();
fcmds.clear(); // clear from previous calls from best
fcmds.push(next);
while rem >= 0 {
match fcmds.pop() {
None => {
if bidx == 0 {
// All commands have been processed
return true;
} else {
fcmds.push(bcmds[bidx - 1]);
bidx -= 1;
}
}
Some((ind, mode, doc)) => {
match *doc {
Doc::Nil => {}
Doc::Append(ref ldoc, ref rdoc) => {
fcmds.push((ind, mode, rdoc));
// Since appended documents often appear in sequence on the left side we
// gain a slight performance increase by batching these pushes (avoiding
// to push and directly pop `Append` documents)
let mut doc = ldoc;
while let Doc::Append(ref l, ref r) = **doc {
fcmds.push((ind, mode, r));
doc = l;
}
fcmds.push((ind, mode, doc));
}
Doc::Group(ref doc) => {
fcmds.push((ind, mode, doc));
}
Doc::Nest(off, ref doc) => {
fcmds.push((ind + off, mode, doc));
}
Doc::Space => match mode {
Mode::Flat => {
rem -= 1;
}
Mode::Break => {
return true;
}
},
Doc::Newline => return true,
Doc::Text(ref str) => {
rem -= str.len() as isize;
}
Doc::Annotated(_, ref doc) => fcmds.push((ind, mode, doc)),
}
}
}
}
false
}
let mut pos = 0;
let mut bcmds = vec![(0, Mode::Break, doc)];
let mut fcmds = vec![];
let mut annotation_levels = vec![];
while let Some((ind, mode, doc)) = bcmds.pop() {
match *doc {
Doc::Nil => {}
Doc::Append(ref ldoc, ref rdoc) => {
bcmds.push((ind, mode, rdoc));
let mut doc = ldoc;
while let Doc::Append(ref l, ref r) = **doc {
bcmds.push((ind, mode, r));
doc = l;
}
bcmds.push((ind, mode, doc));
}
Doc::Group(ref doc) => match mode {
Mode::Flat => {
bcmds.push((ind, Mode::Flat, doc));
}
Mode::Break => {
let next = (ind, Mode::Flat, &**doc);
let rem = width as isize - pos as isize;
if fitting(next, &bcmds, &mut fcmds, rem) {
bcmds.push(next);
} else {
bcmds.push((ind, Mode::Break, doc));
}
}
},
Doc::Nest(off, ref doc) => {
bcmds.push((ind + off, mode, doc));
}
Doc::Space => match mode {
Mode::Flat => {
write_spaces(1, out)?;
}
Mode::Break => {
write_newline(ind, out)?;
pos = ind;
}
},
Doc::Newline => {
write_newline(ind, out)?;
pos = ind;
// Since this newline caused an early break we don't know if the remaining
// documents fit the next line so recalculate if they fit
fcmds.clear();
let docs = bcmds.len()
- bcmds
.iter()
.rev()
.position(|t| t.1 == Mode::Break)
.unwrap_or_else(|| bcmds.len());
fcmds.extend_from_slice(&bcmds[docs..]);
if let Some(next) = fcmds.pop() {
let rem = width as isize - pos as isize;
if !fitting(next, &bcmds, &mut fcmds, rem) {
for &mut (_, ref mut mode, _) in &mut bcmds[docs..] {
*mode = Mode::Break;
}
}
}
}
Doc::Text(ref s) => {
out.write_str_all(s)?;
pos += s.len();
}
Doc::Annotated(ref ann, ref doc) => {
out.push_annotation(ann)?;
annotation_levels.push(bcmds.len());
bcmds.push((ind, mode, doc));
}
}
if annotation_levels.last() == Some(&bcmds.len()) {
annotation_levels.pop();
out.pop_annotation()?;
}
}
Ok(())
}