use core::fmt::{self, Write as _};
use alloc::vec::Vec;
use crate::node::internal::{DepthFirstLinkTraverser, NodeCoreLink};
use crate::traverse::DftEvent;
#[derive(Clone, Copy)]
struct IndentedBlockState {
is_last_item: bool,
is_first_line: bool,
}
impl IndentedBlockState {
#[inline]
fn as_str(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(false, true) => "|-- ",
(false, false) => "| ",
(true, true) => "`-- ",
(true, false) => " ",
}
}
#[inline]
fn as_str_leading(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(false, true) => "|--",
(false, false) => "|",
(true, true) => "`--",
(true, false) => "",
}
}
#[inline]
fn as_str_trailing_spaces(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(_, true) => " ",
(false, false) => " ",
(true, false) => " ",
}
}
#[inline]
#[must_use]
fn is_all_whitespace(self) -> bool {
self.is_last_item && !self.is_first_line
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LineState {
BeforeIndent,
PartialIndent,
Content,
}
struct IndentWriter<'a, 'b> {
fmt: &'b mut fmt::Formatter<'a>,
line_state: LineState,
indents: Vec<IndentedBlockState>,
pending_ws_only_indent_level: usize,
}
impl<'a, 'b> IndentWriter<'a, 'b> {
#[inline]
fn new(fmt: &'b mut fmt::Formatter<'a>) -> Self {
Self {
fmt,
line_state: LineState::BeforeIndent,
indents: Vec::new(),
pending_ws_only_indent_level: 0,
}
}
fn open_item(&mut self, is_last_item: bool) -> fmt::Result {
if self.line_state != LineState::BeforeIndent {
self.fmt.write_char('\n')?;
self.line_state = LineState::BeforeIndent;
self.pending_ws_only_indent_level = 0;
}
if let Some(indent) = self.indents.last_mut() {
indent.is_first_line = false;
}
self.indents.push(IndentedBlockState {
is_last_item,
is_first_line: true,
});
Ok(())
}
#[inline]
fn close_item(&mut self) -> Result<(), ()> {
match self.indents.pop() {
Some(_) => Ok(()),
None => Err(()),
}
}
fn write_indent_partial(&mut self) -> fmt::Result {
self.pending_ws_only_indent_level = self
.indents
.iter()
.rev()
.take_while(|i| i.is_all_whitespace())
.count();
let ws_indent_first_level = self.indents.len() - self.pending_ws_only_indent_level;
let indents_to_print = &self.indents[..ws_indent_first_level];
if let Some(last) = indents_to_print.last() {
for indent in &indents_to_print[..(indents_to_print.len() - 1)] {
self.fmt.write_str(indent.as_str())?;
}
self.fmt.write_str(last.as_str_leading())?;
}
Ok(())
}
fn complete_partial_indent(&mut self) -> fmt::Result {
debug_assert_eq!(self.line_state, LineState::PartialIndent);
if let Some(last_non_ws_indent_level) =
(self.indents.len() - self.pending_ws_only_indent_level).checked_sub(1)
{
self.fmt
.write_str(self.indents[last_non_ws_indent_level].as_str_trailing_spaces())?;
}
for _ in 0..self.pending_ws_only_indent_level {
self.fmt.write_str(" ")?;
}
self.pending_ws_only_indent_level = 0;
Ok(())
}
}
impl fmt::Write for IndentWriter<'_, '_> {
fn write_str(&mut self, mut s: &str) -> fmt::Result {
while !s.is_empty() {
if self.line_state == LineState::BeforeIndent {
self.write_indent_partial()?;
self.line_state = LineState::PartialIndent;
}
let (line_end, ends_with_newline) = match s.find('\n') {
Some(pos) => (pos + 1, true),
None => (s.len(), false),
};
let content = &s[..line_end];
if !content.is_empty() {
debug_assert_ne!(
self.line_state,
LineState::BeforeIndent,
"[consistency] indent must be written since there are something to write"
);
if self.line_state == LineState::PartialIndent {
self.complete_partial_indent()?;
}
if let Some(level) = self.indents.last_mut() {
level.is_first_line = level.is_first_line && !ends_with_newline;
}
self.fmt.write_str(content)?;
self.line_state = if ends_with_newline {
LineState::BeforeIndent
} else {
LineState::Content
};
}
s = &s[line_end..];
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct DebugPrettyPrint<'a, T> {
link: &'a NodeCoreLink<T>,
}
impl<'a, T> DebugPrettyPrint<'a, T> {
#[inline]
pub(crate) fn new(link: &'a NodeCoreLink<T>) -> Self {
Self { link }
}
}
impl<'a, T: fmt::Display> fmt::Display for DebugPrettyPrint<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_alternate = f.alternate();
let mut writer = IndentWriter::new(f);
let mut traverser = self.link.depth_first_traverse();
traverser.next();
match self.link.try_borrow_data() {
Ok(data) => {
if is_alternate {
write!(writer, "{:#}", *data)?
} else {
write!(writer, "{}", *data)?
}
}
Err(_) => writer.write_str("(borrowed)")?,
}
while let Some(link) = prepare_next_node_printing(&mut writer, &mut traverser)? {
match link.try_borrow_data() {
Ok(data) => {
if is_alternate {
write!(writer, "{:#}", *data)?
} else {
write!(writer, "{}", *data)?
}
}
Err(_) => writer.write_str("(borrowed)")?,
}
}
Ok(())
}
}
impl<'a, T: fmt::Debug> fmt::Debug for DebugPrettyPrint<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_alternate = f.alternate();
let mut writer = IndentWriter::new(f);
let mut traverser = self.link.depth_first_traverse();
traverser.next();
match self.link.try_borrow_data() {
Ok(data) => {
if is_alternate {
write!(writer, "{:#?}", *data)?
} else {
write!(writer, "{:?}", *data)?
}
}
Err(_) => writer.write_str("(borrowed)")?,
}
while let Some(link) = prepare_next_node_printing(&mut writer, &mut traverser)? {
match link.try_borrow_data() {
Ok(data) => {
if is_alternate {
write!(writer, "{:#?}", *data)?
} else {
write!(writer, "{:?}", *data)?
}
}
Err(_) => writer.write_str("(borrowed)")?,
}
}
Ok(())
}
}
fn prepare_next_node_printing<T>(
writer: &mut IndentWriter<'_, '_>,
traverser: &mut DepthFirstLinkTraverser<'_, T>,
) -> Result<Option<NodeCoreLink<T>>, fmt::Error> {
for ev in traverser {
let link = match ev {
DftEvent::Open(link) => link,
DftEvent::Close(_) => {
if writer.close_item().is_ok() {
continue;
} else {
break;
}
}
};
writer.open_item(link.is_last_sibling())?;
return Ok(Some(link));
}
Ok(None)
}