use {
crate::{
styles::Styles,
writer::Writer,
},
owo_colors::OwoColorize,
std::{
cell::{
RefCell,
RefMut,
},
fmt::Write,
rc::Rc,
sync::Arc,
},
unicode_width::UnicodeWidthStr,
};
macro_rules! prefix_chars {
($($dbgid:ident : $name:ident -> $value:expr $( => $color:expr)? ),* $(,)?) => {
paste::paste! {
#[derive(Clone, PartialEq, Eq,)]
pub(crate) enum Glyph {
$($name),*
}
impl std::fmt::Display for Glyph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
| Glyph::$name => write!(f, "{}", $value),
)*
}
}
}
impl std::fmt::Debug for Glyph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
| Glyph::$name => write!(f, "{:}", stringify!($dbgid)),
)*
}
}
}
impl<'a> Prefix<'a> {
$(
pub fn [<write_glyph_ $name:snake>](&self, w: &mut Writer, style: &'a Styles
) -> std::fmt::Result {
w.write_prefix(format!("{}", $value))
}
)*
}
}
};
}
prefix_chars! {
BNK: Blank -> ' ',
DBG: Debug -> '⏺',
DB2: Debug2 -> '○',
RUT: Root -> '◈',
N__: NodeCollapse -> "",
NC_: NodeNodesCollapse -> "",
_C_: NodesCollapse -> "",
R__: RootCollapse -> "",
__F: FieldCollapse -> "",
__L: LeafCollapse -> "",
CHD: DebugNodes -> 'C',
NDE: DebugNode -> 'N',
NCD: DebugNodeNodes -> 'M',
FLD: DebugField -> 'F',
LAF: DebugLeaf -> 'L',
RMV: Remove -> "",
DSH: Dash -> '·',
LH: LightHorizontal -> '─',
HH: HeavyHorizontal -> '━',
LV: LightVertical -> '│',
HV: HeavyVertical -> '┃',
LTDH: LightTripleDashHorizontal -> '┄',
HTDH: HeavyTripleDashHorizontal -> '┅',
LTDV: LightTripleDashVertical -> '┆',
HTDV: HeavyTripleDashVertical -> '┇',
LQDH: LightQuadrupleDashHorizontal -> '┈',
HQDH: HeavyQuadrupleDashHorizontal -> '┉',
LQDV: LightQuadrupleDashVertical -> '┊',
HQDV: HeavyQuadrupleDashVertical -> '┋',
LD_R: LightDownRight -> '┌',
DL_RH: DownLightRightHeavy -> '┍',
DH_RL: DownHeavyRightLight -> '┎',
HD_R: HeavyDownRight -> '┏',
LD_L: LightDownLeft -> '┐',
DL_LH: DownLightLeftHeavy -> '┑',
DH_LL: DownHeavyLeftLight -> '┒',
HD_L: HeavyDownLeft -> '┓',
LU_R: LightUpRight -> '└',
UL_RH: UpLightRightHeavy -> '┕',
UH_RL: UpHeavyRightLight -> '┖',
HU_R: HeavyUpRight -> '┗',
LU_L: LightUpLeft -> '┘',
UL_LH: UpLightLeftHeavy -> '┙',
UH_LL: UpHeavyLeftLight -> '┚',
HU_L: HeavyUpLeft -> '┛',
LV_R: LightVerticalRight -> '├',
VL_RH: VerticalLightRightHeavy -> '┝',
UH_RDL: UpHeavyRightDownLight -> '┞',
DH_RUL: DownHeavyRightUpLight -> '┟',
VH_RL: VerticalHeavyRightLight -> '┠',
DL_RUH: DownLightRightUpHeavy -> '┡',
UL_RDH: UpLightRightDownHeavy -> '┢',
HV_R: HeavyVerticalRight -> '┣',
LV_L: LightVerticalLeft -> '┤',
VL_LH: VerticalLightLeftHeavy -> '┥',
UH_LDL: UpHeavyLeftDownLight -> '┦',
DH_LUL: DownHeavyLeftUpLight -> '┧',
VH_LL: VerticalHeavyLeftLight -> '┨',
DL_LUH: DownLightLeftUpHeavy -> '┩',
UL_LDH: UpLightLeftDownHeavy -> '┪',
HV_L: HeavyVerticalLeft -> '┫',
LD_H: LightDownHorizontal -> '┬',
LH_RDL: LeftHeavyRightDownLight -> '┭',
RH_LDL: RightHeavyLeftDownLight -> '┮',
DL_HH: DownLightHorizontalHeavy -> '┯',
DH_HL: DownHeavyHorizontalLight -> '┰',
RL_LDH: RightLightLeftDownHeavy -> '┱',
LL_RDH: LeftLightRightDownHeavy -> '┲',
HD_H: HeavyDownHorizontal -> '┳',
LU_H: LightUpHorizontal -> '┴',
LH_RUL: LeftHeavyRightUpLight -> '┵',
RH_LUL: RightHeavyLeftUpLight -> '┶',
UL_HH: UpLightHorizontalHeavy -> '┷',
UH_HL: UpHeavyHorizontalLight -> '┸',
RL_LUH: RightLightLeftUpHeavy -> '┹',
LL_RUH: LeftLightRightUpHeavy -> '┺',
HU_H: HeavyUpHorizontal -> '┻',
LV_H: LightVerticalHorizontal -> '┼',
LH_RVL: LeftHeavyRightVerticalLight -> '┽',
RH_LVL: RightHeavyLeftVerticalLight -> '┾',
VL_HH: VerticalLightHorizontalHeavy -> '┿',
UH_DHL: UpHeavyDownHorizontalLight -> '╀',
DH_UHL: DownHeavyUpHorizontalLight -> '╁',
VH_HL: VerticalHeavyHorizontalLight -> '╂',
LUH_RDL: LeftUpHeavyRightDownLight -> '╃',
RUH_LDL: RightUpHeavyLeftDownLight -> '╄',
LDH_RUL: LeftDownHeavyRightUpLight -> '╅',
RDH_LUL: RightDownHeavyLeftUpLight -> '╆',
DL_UHH: DownLightUpHorizontalHeavy -> '╇',
UL_DHH: UpLightDownHorizontalHeavy -> '╈',
RL_LVH: RightLightLeftVerticalHeavy -> '╉',
LL_RVH: LeftLightRightVerticalHeavy -> '╊',
HV_H: HeavyVerticalHorizontal -> '╋',
LDDH: LightDoubleDashHorizontal -> '╌',
HDDH: HeavyDoubleDashHorizontal -> '╍',
LDDV: LightDoubleDashVertical -> '╎',
HDDV: HeavyDoubleDashVertical -> '╏',
DH: DoubleHorizontal -> '═',
DV: DoubleVertical -> '║',
DS_RD: DownSingleRightDouble -> '╒',
DD_RS: DownDoubleRightSingle -> '╓',
DD_R: DoubleDownRight -> '╔',
DS_LD: DownSingleLeftDouble -> '╕',
DD_LS: DownDoubleLeftSingle -> '╖',
DD_L: DoubleDownLeft -> '╗',
US_RD: UpSingleRightDouble -> '╘',
UD_RS: UpDoubleRightSingle -> '╙',
DU_R: DoubleUpRight -> '╚',
US_LD: UpSingleLeftDouble -> '╛',
UD_LS: UpDoubleLeftSingle -> '╜',
DU_L: DoubleUpLeft -> '╝',
VS_RD: VerticalSingleRightDouble -> '╞',
VD_RS: VerticalDoubleRightSingle -> '╟',
DV_R: DoubleVerticalRight -> '╠',
VS_LD: VerticalSingleLeftDouble -> '╡',
VD_LS: VerticalDoubleLeftSingle -> '╢',
DV_L: DoubleVerticalLeft -> '╣',
DS_HD: DownSingleHorizontalDouble -> '╤',
DD_HS: DownDoubleHorizontalSingle -> '╥',
DD_H: DoubleDownHorizontal -> '╦',
US_HD: UpSingleHorizontalDouble -> '╧',
UD_HS: UpDoubleHorizontalSingle -> '╨',
DU_H: DoubleUpHorizontal -> '╩',
VS_HD: VerticalSingleHorizontalDouble -> '╪',
VD_HS: VerticalDoubleHorizontalSingle -> '╫',
DV_H: DoubleVerticalHorizontal -> '╬',
LAD_R: LightArcDownRight -> '╭',
LAU_L: LightArcUpLeft -> '╮',
LAD_L: LightArcDownLeft -> '╯',
LDURTLL: LightDiagonalUpperRightToLowerLeft -> '╰',
LAU_R: LightArcUpRight -> '╱',
LDC: LightDiagonalCross -> '╲',
LDULTLR: LightDiagonalUpperLeftToLowerRight -> '╳',
LU: LightUp -> '╴',
LL: LightLeft -> '╵',
LD: LightDown -> '╶',
LR: LightRight -> '╷',
HU: HeavyUp -> '╸',
HL: HeavyLeft -> '╹',
HD: HeavyDown -> '╺',
HR: HeavyRight -> '╻',
LU_HD: LightUpHeavyDown -> '╼',
LL_HR: LightLeftHeavyRight -> '╽',
HU_LD: HeavyUpLightDown -> '╾',
HL_LR: HeavyLeftLightRight -> '╿',
}
#[derive(Clone)]
pub(crate) enum Stack {
Root,
BlankLine,
Divider,
Node {
has_fields: bool,
last_node: bool,
},
Leaf {
has_fields: bool,
last_node: bool,
},
LeafField {
last_node: bool,
first_field: bool,
last_field: bool,
},
NodeField {
last_node: bool,
first_field: bool,
last_field: bool,
},
Nodes {
first_node: bool,
last_node: bool,
has_nodes: bool,
},
}
fn format_flag(label: &str, active: &bool) -> String {
match *active {
| true => {
format!("{sep}{}", label.bright_white(), sep = ":".dimmed())
},
| false => {
format!(":{}", label.dimmed())
},
}
}
impl std::fmt::Debug for Stack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (ty, flags): (String, String) = match self {
| Stack::Root => (format!("{}", "R".bright_magenta()), "".to_string()),
| Stack::Node {
has_fields,
last_node,
} => {
(
format!("{}", "N".bright_green()),
[format_flag("LC", last_node), format_flag("HF", has_fields)]
.join(":"),
)
},
| Stack::Leaf {
has_fields,
last_node,
} => {
(
format!("{}", "L".bright_yellow()),
[format_flag("LC", last_node), format_flag("HF", has_fields)]
.join(":"),
)
},
| Stack::Divider => (format!("{}", "D".bright_cyan()), "".to_string()),
| Stack::LeafField {
last_node,
first_field,
last_field,
} => {
(
format!("{}", "F".bright_red()),
[
format_flag("LC", last_node),
format_flag("FF", first_field),
format_flag("LF", last_field),
]
.join(":"),
)
},
| Stack::NodeField {
last_node,
first_field,
last_field,
} => {
(
format!("{}", "NF".bright_blue()),
[
format_flag("LC", last_node),
format_flag("FF", first_field),
format_flag("LF", last_field),
]
.join(":"),
)
},
| Stack::Nodes {
first_node,
last_node,
has_nodes,
} => {
(
format!("{}", "C".bright_blue()),
[
format_flag("FC", first_node),
format_flag("LC", last_node),
format_flag("HC", last_node),
]
.join(":"),
)
},
| Stack::BlankLine => (format!("{}", "B".bright_cyan()), "".to_string()),
};
write!(
f,
"{open}{ty}{flags}{close}",
open = "[".dimmed(),
close = "]".dimmed(),
)
}
}
#[derive(Clone, PartialEq)]
enum GlyphWithColor {
None(Glyph),
Node(Glyph),
Leaf(Glyph),
Field(Glyph),
Nodes(Glyph),
}
impl GlyphWithColor {
fn style(&self, styles: &Styles) -> String {
match self {
| GlyphWithColor::None(glyph) => glyph.to_string(),
| GlyphWithColor::Node(glyph) => {
glyph.clone().style(styles.trivia_node).to_string()
},
| GlyphWithColor::Leaf(glyph) => {
glyph.clone().style(styles.trivia_leaf).to_string()
},
| GlyphWithColor::Field(glyph) => {
glyph.clone().style(styles.trivia_field).to_string()
},
| GlyphWithColor::Nodes(glyph) => {
glyph.clone().style(styles.trivia_nodes).to_string()
},
}
}
}
impl std::fmt::Display for GlyphWithColor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
| GlyphWithColor::None(glyph) => glyph,
| GlyphWithColor::Node(glyph) => glyph,
| GlyphWithColor::Leaf(glyph) => glyph,
| GlyphWithColor::Field(glyph) => glyph,
| GlyphWithColor::Nodes(glyph) => glyph,
})
}
}
impl std::fmt::Debug for GlyphWithColor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", match self {
| GlyphWithColor::None(glyph) => glyph,
| GlyphWithColor::Node(glyph) => glyph,
| GlyphWithColor::Leaf(glyph) => glyph,
| GlyphWithColor::Field(glyph) => glyph,
| GlyphWithColor::Nodes(glyph) => glyph,
})
}
}
pub(crate) struct Prefix<'a> {
styles: &'a Styles,
pub stack: Vec<Stack>,
}
impl<'a> Prefix<'a> {
pub fn new(styles: &'a Styles) -> Self {
Self {
styles,
stack: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn len(&self) -> usize {
self.stack.len()
}
}
impl<'a> Prefix<'a> {
pub fn push_stack_node(&mut self, has_fields: bool, last_mpde: bool) {
self.stack.push(Stack::Node {
has_fields,
last_node: last_mpde,
});
}
pub fn push_stack_leaf(&mut self, has_fields: bool, last_node: bool) {
self.stack.push(Stack::Leaf {
has_fields,
last_node,
});
}
pub fn push_stack_divider(&mut self) {
self.stack.push(Stack::Divider);
}
pub fn push_stack_blank(&mut self) {
self.stack.push(Stack::BlankLine);
}
pub fn push_stack_field(
&mut self,
is_for_node: bool,
first_field: bool,
last_field: bool,
last_node: bool,
) {
if is_for_node {
self.stack.push(Stack::NodeField {
last_node,
first_field,
last_field,
});
} else {
self.stack.push(Stack::LeafField {
last_node,
first_field,
last_field,
});
}
}
pub fn push_stack_nodes(
&mut self,
first_node: bool,
last_node: bool,
has_nodes: bool,
) {
self.stack.push(Stack::Nodes {
first_node,
last_node,
has_nodes,
});
}
pub fn pop_stack(&mut self) {
self.stack.pop();
}
pub fn render_prefix_stack(
&mut self,
w: &mut Writer,
style: &Styles,
) -> std::fmt::Result {
let len = self.stack.len();
let mut debug_buf: Vec<String> = vec![];
let mut g: Vec<GlyphWithColor> = vec![];
let mut prefix_stack = self.stack.clone();
for (i, stack) in self.stack.iter().enumerate() {
let is_first = i == 0;
let is_last = i == (len - 1);
debug_buf.push(format!("{i}:{:?}{}", stack, {
let last = if is_last {
"L".bright_white().to_string()
} else {
"L".dimmed().to_string()
};
last
}));
match stack {
| Stack::Root => {
g.push(GlyphWithColor::None(Glyph::RootCollapse));
g.push(GlyphWithColor::None(Glyph::RootCollapse));
},
| Stack::Node {
has_fields,
last_node,
} => {
if is_last {
if i == 0 && len == 1 {
g.push(GlyphWithColor::Node(Glyph::Root));
g.push(GlyphWithColor::Node(Glyph::LightHorizontal));
} else if *last_node {
g.push(GlyphWithColor::Node(Glyph::LightUpRight));
g.push(GlyphWithColor::Node(Glyph::LightHorizontal));
} else {
g.push(GlyphWithColor::Node(Glyph::LightVerticalRight));
g.push(GlyphWithColor::Node(Glyph::LightHorizontal));
}
} else if *last_node {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
} else {
g.push(GlyphWithColor::Node(Glyph::LightVertical));
g.push(GlyphWithColor::None(Glyph::Blank));
}
},
| Stack::Leaf {
has_fields,
last_node,
} => {
if is_last {
if i == 0 && len == 1 {
g.push(GlyphWithColor::Leaf(Glyph::Root));
g.push(GlyphWithColor::Leaf(Glyph::LightHorizontal));
} else if *last_node {
g.push(GlyphWithColor::Leaf(Glyph::LightUpRight));
g.push(GlyphWithColor::Leaf(Glyph::LightHorizontal));
} else {
g.push(GlyphWithColor::Leaf(Glyph::LightVerticalRight));
g.push(GlyphWithColor::Leaf(Glyph::LightHorizontal));
}
} else if *last_node {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
} else {
g.push(GlyphWithColor::Leaf(Glyph::LightVertical));
g.push(GlyphWithColor::None(Glyph::Blank));
}
},
| Stack::Divider => {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
},
| Stack::LeafField {
last_node,
first_field,
last_field,
} => {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::Field(Glyph::Dash));
g.push(GlyphWithColor::None(Glyph::Blank));
},
| Stack::NodeField { last_node, .. } => {
if is_last {
g.push(GlyphWithColor::Field(Glyph::LightVertical));
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::Field(Glyph::Dash));
g.push(GlyphWithColor::None(Glyph::Blank));
} else {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::Field(Glyph::Dash));
g.push(GlyphWithColor::None(Glyph::Blank));
}
},
| Stack::Nodes {
first_node,
last_node,
has_nodes,
} => {
if is_last {
match (*first_node, *last_node) {
| (true, true) => {
g.push(GlyphWithColor::Nodes(Glyph::LightUpRight)); g.push(GlyphWithColor::Nodes(Glyph::LightHorizontal));
},
| (true, false) => {
g.push(GlyphWithColor::Nodes(Glyph::LightVerticalRight));
g.push(GlyphWithColor::Nodes(Glyph::LightHorizontal));
},
| (false, true) => {
g.push(GlyphWithColor::Nodes(Glyph::LightUpRight));
g.push(GlyphWithColor::Nodes(Glyph::LightHorizontal));
},
| (false, false) => {
g.push(GlyphWithColor::Nodes(Glyph::LightVerticalRight));
g.push(GlyphWithColor::Nodes(Glyph::LightHorizontal));
},
}
} else if *last_node {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
} else {
g.push(GlyphWithColor::Nodes(Glyph::LightVertical));
g.push(GlyphWithColor::None(Glyph::Blank));
}
},
| Stack::BlankLine => {
g.push(GlyphWithColor::None(Glyph::Blank));
g.push(GlyphWithColor::None(Glyph::Blank));
while g.last() == Some(&GlyphWithColor::None(Glyph::Blank)) {
g.pop();
}
},
}
}
w.write_prefix(format!(
"{}",
g.iter()
.map(|gl| { gl.style(self.styles) })
.collect::<Vec<String>>()
.join("")
.style(style.lines),
))?;
let show_prefix_debug = false;
if show_prefix_debug {
w.write_debug({
let mut out = String::new();
let g_chunks = g.chunks(2).map(|gvec| format!("{gvec:?}"));
let mut d_chunks = debug_buf.iter();
for chunk in g_chunks {
if let Some(chnk) = d_chunks.next() {
out.push_str(chnk);
out.push(' ');
}
out.push_str(chunk.to_string().as_str());
out.push(' ');
}
for chnk in d_chunks {
out.push_str(chnk);
out.push(' ');
}
out
})?;
}
Ok(())
}
}