#![deny(missing_docs)]
use std::cmp;
use std::error;
use std::fmt;
use std::io::{self, Write};
use std::iter;
use std::mem;
use std::str;
#[cfg(test)]
mod test;
#[derive(Debug)]
pub struct TabWriter<W> {
w: W,
buf: io::Cursor<Vec<u8>>,
lines: Vec<Vec<Cell>>,
curcell: Cell,
minwidth: usize,
padding: usize,
alignment: Alignment,
ansi: bool,
tab_indent: bool,
}
#[derive(Debug)]
pub enum Alignment {
Left,
Center,
Right,
}
#[derive(Debug)]
struct Cell {
start: usize, width: usize, size: usize, }
impl<W: io::Write> TabWriter<W> {
pub fn new(w: W) -> TabWriter<W> {
TabWriter {
w,
buf: io::Cursor::new(Vec::with_capacity(1024)),
lines: vec![vec![]],
curcell: Cell::new(0),
minwidth: 2,
padding: 2,
alignment: Alignment::Left,
ansi: cfg!(feature = "ansi_formatting"),
tab_indent: false,
}
}
pub fn minwidth(mut self, minwidth: usize) -> TabWriter<W> {
self.minwidth = minwidth;
self
}
pub fn padding(mut self, padding: usize) -> TabWriter<W> {
self.padding = padding;
self
}
pub fn alignment(mut self, alignment: Alignment) -> TabWriter<W> {
self.alignment = alignment;
self
}
pub fn ansi(mut self, yes: bool) -> TabWriter<W> {
self.ansi = yes;
self
}
pub fn tab_indent(mut self, yes: bool) -> TabWriter<W> {
self.tab_indent = yes;
self
}
pub fn into_inner(mut self) -> Result<W, IntoInnerError<TabWriter<W>>> {
match self.flush() {
Ok(()) => Ok(self.w),
Err(err) => Err(IntoInnerError(self, err)),
}
}
fn reset(&mut self) {
self.buf = io::Cursor::new(Vec::with_capacity(1024));
self.lines = vec![vec![]];
self.curcell = Cell::new(0);
}
fn add_bytes(&mut self, bytes: &[u8]) {
self.curcell.size += bytes.len();
let _ = self.buf.write_all(bytes); }
fn term_curcell(&mut self) {
let mut curcell = Cell::new(self.buf.position() as usize);
mem::swap(&mut self.curcell, &mut curcell);
if self.ansi {
curcell.update_width(&self.buf.get_ref(), count_columns_ansi);
} else {
curcell.update_width(&self.buf.get_ref(), count_columns_noansi);
}
self.curline_mut().push(curcell);
}
fn curline(&mut self) -> &[Cell] {
let i = self.lines.len() - 1;
&*self.lines[i]
}
fn curline_mut(&mut self) -> &mut Vec<Cell> {
let i = self.lines.len() - 1;
&mut self.lines[i]
}
}
impl Cell {
fn new(start: usize) -> Cell {
Cell { start, width: 0, size: 0 }
}
fn update_width(
&mut self,
buf: &[u8],
count_columns: impl Fn(&[u8]) -> usize,
) {
let end = self.start + self.size;
self.width = count_columns(&buf[self.start..end]);
}
}
impl<W: io::Write> io::Write for TabWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut lastterm = 0usize;
for (i, &c) in buf.iter().enumerate() {
match c {
b'\t' | b'\n' => {
self.add_bytes(&buf[lastterm..i]);
self.term_curcell();
lastterm = i + 1;
if c == b'\n' {
let ncells = self.curline().len();
self.lines.push(vec![]);
if ncells == 1 {
self.flush()?;
}
}
}
_ => {}
}
}
self.add_bytes(&buf[lastterm..]);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
if self.curcell.size > 0 {
self.term_curcell();
}
let widths = cell_widths(&self.lines, self.minwidth);
let biggest_width = widths
.iter()
.map(|ws| ws.iter().map(|&w| w).max().unwrap_or(0))
.max()
.unwrap_or(0);
let padding: String =
iter::repeat(' ').take(biggest_width + self.padding).collect();
let mut first = true;
for (line, widths) in self.lines.iter().zip(widths.iter()) {
if !first {
self.w.write_all(b"\n")?;
} else {
first = false
}
let mut use_tabs = self.tab_indent;
for (i, cell) in line.iter().enumerate() {
let bytes =
&self.buf.get_ref()[cell.start..cell.start + cell.size];
if i >= widths.len() {
assert_eq!(i, line.len() - 1);
self.w.write_all(bytes)?;
} else {
if use_tabs && cell.size == 0 {
write!(&mut self.w, "\t")?;
continue;
}
use_tabs = false;
assert!(widths[i] >= cell.width);
let extra_space = widths[i] - cell.width;
let (left_spaces, mut right_spaces) = match self.alignment
{
Alignment::Left => (0, extra_space),
Alignment::Right => (extra_space, 0),
Alignment::Center => {
(extra_space / 2, extra_space - extra_space / 2)
}
};
right_spaces += self.padding;
write!(&mut self.w, "{}", &padding[0..left_spaces])?;
self.w.write_all(bytes)?;
write!(&mut self.w, "{}", &padding[0..right_spaces])?;
}
}
}
self.reset();
Ok(())
}
}
pub struct IntoInnerError<W>(W, io::Error);
impl<W> IntoInnerError<W> {
pub fn error(&self) -> &io::Error {
&self.1
}
pub fn into_inner(self) -> W {
self.0
}
}
impl<W> fmt::Debug for IntoInnerError<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error().fmt(f)
}
}
impl<W> fmt::Display for IntoInnerError<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error().fmt(f)
}
}
impl<W: ::std::any::Any> error::Error for IntoInnerError<W> {
#[allow(deprecated)]
fn description(&self) -> &str {
self.error().description()
}
fn cause(&self) -> Option<&dyn error::Error> {
Some(self.error())
}
}
fn cell_widths(lines: &Vec<Vec<Cell>>, minwidth: usize) -> Vec<Vec<usize>> {
let mut ws: Vec<_> = (0..lines.len()).map(|_| vec![]).collect();
for (i, iline) in lines.iter().enumerate() {
if iline.is_empty() {
continue;
}
for col in ws[i].len()..(iline.len() - 1) {
let mut width = minwidth;
let mut contig_count = 0;
for line in lines[i..].iter() {
if col + 1 >= line.len() {
break;
}
contig_count += 1;
width = cmp::max(width, line[col].width);
}
assert!(contig_count >= 1);
for j in i..(i + contig_count) {
ws[j].push(width);
}
}
}
ws
}
fn count_columns_noansi(bytes: &[u8]) -> usize {
use unicode_width::UnicodeWidthChar;
match str::from_utf8(bytes) {
Err(_) => bytes.len(),
Ok(s) => s
.chars()
.map(|c| UnicodeWidthChar::width(c).unwrap_or(0))
.fold(0, |sum, width| sum + width),
}
}
fn count_columns_ansi(bytes: &[u8]) -> usize {
use unicode_width::UnicodeWidthChar;
match str::from_utf8(bytes) {
Err(_) => bytes.len(),
Ok(s) => strip_formatting(s)
.chars()
.map(|c| UnicodeWidthChar::width(c).unwrap_or(0))
.fold(0, |sum, width| sum + width),
}
}
fn strip_formatting<'t>(input: &'t str) -> std::borrow::Cow<'t, str> {
let mut escapes = find_ansi_escapes(input).peekable();
if escapes.peek().is_none() {
return std::borrow::Cow::Borrowed(input);
}
let mut without_escapes = String::with_capacity(input.len());
let mut last_end = 0;
for mat in escapes {
without_escapes.push_str(&input[last_end..mat.start]);
last_end = mat.end;
}
without_escapes.push_str(&input[last_end..]);
std::borrow::Cow::Owned(without_escapes)
}
fn find_ansi_escapes<'t>(
input: &'t str,
) -> impl Iterator<Item = std::ops::Range<usize>> + 't {
const ESCAPE_PREFIX: &str = "\x1B[";
let mut last_end = 0;
std::iter::from_fn(move || {
let start = last_end
+ input[last_end..].match_indices(ESCAPE_PREFIX).next()?.0;
let after_prefix = start + ESCAPE_PREFIX.len();
let end = after_prefix
+ input[after_prefix..].match_indices('m').next()?.0
+ 1;
last_end = end;
Some(start..end)
})
}