use super::{
splitter::{split, Chunk},
Block, Doc, Skip, Token,
};
#[cfg(feature = "color")]
use super::Style;
const MAX_TAB: usize = 24;
pub(crate) const MAX_WIDTH: usize = 100;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(dead_code)] pub(crate) enum Color {
Monochrome,
#[cfg(feature = "color")]
Dull,
#[cfg(feature = "color")]
Bright,
}
impl Default for Color {
fn default() -> Self {
#![allow(clippy::let_and_return)]
#![allow(unused_mut)]
#![allow(unused_assignments)]
let mut res;
#[cfg(not(feature = "color"))]
{
res = Color::Monochrome;
}
#[cfg(feature = "color")]
{
res = Color::Dull;
}
#[cfg(feature = "bright-color")]
{
res = Color::Bright;
}
#[cfg(feature = "dull-color")]
{
res = Color::Dull;
}
#[cfg(feature = "color")]
{
use supports_color::{on, Stream};
if !(on(Stream::Stdout).is_some() && on(Stream::Stderr).is_some()) {
res = Color::Monochrome;
}
}
res
}
}
#[cfg(feature = "color")]
impl Color {
pub(crate) fn push_str(self, style: Style, res: &mut String, item: &str) {
use owo_colors::OwoColorize;
use std::fmt::Write;
match self {
Color::Monochrome => {
res.push_str(item);
Ok(())
}
Color::Dull => match style {
Style::Text => {
res.push_str(item);
Ok(())
}
Style::Emphasis => write!(res, "{}", item.underline().bold()),
Style::Literal => write!(res, "{}", item.bold()),
Style::Metavar => write!(res, "{}", item.underline()),
Style::Invalid => write!(res, "{}", item.bold().red()),
},
Color::Bright => match style {
Style::Text => {
res.push_str(item);
Ok(())
}
Style::Emphasis => write!(res, "{}", item.yellow().bold()),
Style::Literal => write!(res, "{}", item.green().bold()),
Style::Metavar => write!(res, "{}", item.blue().bold()),
Style::Invalid => write!(res, "{}", item.red().bold()),
},
}
.unwrap();
}
}
const PADDING: &str = " ";
impl Doc {
#[must_use]
pub fn monochrome(&self, full: bool) -> String {
self.render_console(full, Color::Monochrome, MAX_WIDTH)
}
#[allow(clippy::too_many_lines)] pub(crate) fn render_console(&self, full: bool, color: Color, max_width: usize) -> String {
let mut res = String::new();
let mut tabstop = 0;
let mut byte_pos = 0;
{
let mut current = 0;
let mut in_term = false;
for token in self.tokens.iter().copied() {
match token {
Token::Text { bytes, style: _ } => {
if in_term {
current += self.payload[byte_pos..byte_pos + bytes].chars().count();
}
byte_pos += bytes;
}
Token::BlockStart(Block::ItemTerm) => {
in_term = true;
current = 0;
}
Token::BlockEnd(Block::ItemTerm) => {
in_term = false;
if current > tabstop && current <= MAX_TAB {
tabstop = current;
}
}
_ => {}
}
}
byte_pos = 0;
}
let tabstop = tabstop + 4;
#[cfg(test)]
let mut stack = Vec::new();
let mut skip = Skip::default();
let mut char_pos = 0;
let mut margins: Vec<usize> = Vec::new();
let mut pending_newline = false;
let mut pending_blank_line = false;
let mut pending_margin = false;
for token in self.tokens.iter().copied() {
match token {
Token::Text { bytes, style } => {
let input = &self.payload[byte_pos..byte_pos + bytes];
byte_pos += bytes;
if skip.enabled() {
continue;
}
for chunk in split(input) {
match chunk {
Chunk::Raw(s, w) => {
let margin = margins.last().copied().unwrap_or(0usize);
if !res.is_empty() {
if (pending_newline || pending_blank_line)
&& !res.ends_with('\n')
{
char_pos = 0;
res.push('\n');
}
if pending_blank_line && !res.ends_with("\n\n") {
res.push('\n');
}
if char_pos + s.len() > max_width {
char_pos = 0;
res.truncate(res.trim_end().len());
res.push('\n');
if s == " " {
continue;
}
}
}
let mut pushed = 0;
if let Some(missing) = margin.checked_sub(char_pos) {
res.push_str(&PADDING[..missing]);
char_pos = margin;
pushed = missing;
}
if pending_margin && char_pos >= MAX_TAB + 4 && pushed < 2 {
let missing = 2 - pushed;
res.push_str(&PADDING[..missing]);
char_pos += missing;
}
pending_newline = false;
pending_blank_line = false;
pending_margin = false;
#[cfg(feature = "color")]
{
color.push_str(style, &mut res, s);
}
#[cfg(not(feature = "color"))]
{
let _ = style;
let _ = color;
res.push_str(s);
}
char_pos += w;
}
Chunk::Paragraph => {
res.push('\n');
char_pos = 0;
if !full {
skip.enable();
break;
}
}
Chunk::LineBreak => {
res.push('\n');
char_pos = 0;
}
}
}
}
Token::BlockStart(block) => {
#[cfg(test)]
stack.push(block);
let margin = margins.last().copied().unwrap_or(0usize);
match block {
Block::Header | Block::Section2 => {
pending_newline = true;
margins.push(margin);
}
Block::Section3 | Block::Section4 => {
pending_newline = true;
margins.push(margin + 2);
}
Block::ItemTerm => {
pending_newline = true;
margins.push(margin + 4);
}
Block::ItemBody => {
margins.push(margin + tabstop + 2);
pending_margin = true;
}
Block::InlineBlock => {
skip.push();
}
Block::Block => {
margins.push(margin);
}
Block::DefinitionList | Block::Meta | Block::Mono => {}
Block::TermRef => {
if color == Color::Monochrome {
res.push('`');
char_pos += 1;
}
}
}
}
Token::BlockEnd(block) => {
#[cfg(test)]
assert_eq!(stack.pop(), Some(block));
margins.pop();
match block {
Block::ItemBody => {
pending_margin = false;
}
Block::Header
| Block::Section2
| Block::Section3
| Block::Section4
| Block::ItemTerm
| Block::DefinitionList
| Block::Meta
| Block::Mono => {}
Block::InlineBlock => {
skip.pop();
}
Block::Block => {
pending_blank_line = true;
}
Block::TermRef => {
if color == Color::Monochrome {
res.push('`');
char_pos += 1;
}
}
}
}
}
}
if pending_newline || pending_blank_line {
res.push('\n');
}
#[cfg(test)]
assert_eq!(stack, &[]);
res
}
}