use super::color;
use crate::utils::measure::OutputStats;
use crate::utils::measure::{count_line_breaks, ends_with_break};
#[derive(Clone, Debug)]
pub struct SlotStatsRecorder {
per_slot: Vec<SlotRunning>,
measure_chars: bool,
}
#[derive(Clone, Debug)]
struct SlotRunning {
bytes: usize,
chars: usize,
breaks: usize,
ends_with_break: bool,
has_content: bool,
}
impl SlotRunning {
fn new() -> Self {
Self {
bytes: 0,
chars: 0,
breaks: 0,
ends_with_break: false,
has_content: false,
}
}
}
impl SlotStatsRecorder {
pub fn new(slot_count: usize, measure_chars: bool) -> Self {
Self {
per_slot: vec![SlotRunning::new(); slot_count],
measure_chars,
}
}
pub fn add_chunk(&mut self, slot: Option<usize>, chunk: &str) {
let Some(idx) = slot else { return };
if chunk.is_empty() {
return;
}
if let Some(running) = self.per_slot.get_mut(idx) {
running.bytes = running.bytes.saturating_add(chunk.len());
if self.measure_chars {
running.chars =
running.chars.saturating_add(chunk.chars().count());
}
let b = chunk.as_bytes();
running.breaks =
running.breaks.saturating_add(count_line_breaks(b));
running.ends_with_break = ends_with_break(b);
running.has_content = true;
}
}
pub fn into_output_stats(self) -> Vec<OutputStats> {
self.per_slot
.into_iter()
.map(|r| {
if !r.has_content {
return OutputStats {
bytes: 0,
chars: 0,
lines: 0,
};
}
let mut lines = r.breaks.saturating_add(1);
if r.ends_with_break && lines > 0 {
lines -= 1;
}
OutputStats {
bytes: r.bytes,
chars: if self.measure_chars { r.chars } else { 0 },
lines,
}
})
.collect()
}
}
pub struct Out<'a> {
buf: &'a mut String,
newline: String,
indent_unit: String,
role_colors_enabled: bool,
style: crate::serialization::types::Style,
line_number_width: Option<usize>,
recorder: Option<SlotStatsRecorder>,
current_slot: Option<usize>,
}
impl<'a> Out<'a> {
pub fn new(
buf: &'a mut String,
config: &crate::RenderConfig,
line_number_width: Option<usize>,
) -> Self {
Self::new_with_recorder(buf, config, line_number_width, None)
}
pub fn new_with_recorder(
buf: &'a mut String,
config: &crate::RenderConfig,
line_number_width: Option<usize>,
recorder: Option<SlotStatsRecorder>,
) -> Self {
let role_colors_enabled = matches!(
config.color_strategy(),
crate::serialization::types::ColorStrategy::Syntax
);
Self {
buf,
newline: config.newline.clone(),
indent_unit: config.indent_unit.clone(),
role_colors_enabled,
style: config.style,
line_number_width,
recorder,
current_slot: None,
}
}
pub fn set_current_slot(&mut self, slot: Option<usize>) {
self.current_slot = slot;
}
fn record_chunk(&mut self, s: &str) {
if let Some(rec) = self.recorder.as_mut() {
rec.add_chunk(self.current_slot, s);
}
}
pub fn push_str(&mut self, s: &str) {
self.buf.push_str(s);
self.record_chunk(s);
}
pub fn push_char(&mut self, c: char) {
self.buf.push(c);
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.record_chunk(s);
}
pub fn push_newline(&mut self) {
let nl = self.newline.clone();
self.buf.push_str(&nl);
self.record_chunk(&nl);
}
pub fn push_indent(&mut self, depth: usize) {
let s = self.indent_unit.repeat(depth);
self.record_chunk(&s);
self.buf.push_str(&s);
}
pub fn push_comment<S: Into<String>>(&mut self, body: S) {
let s = color::color_comment(body, self.role_colors_enabled);
self.buf.push_str(&s);
self.record_chunk(&s);
}
pub fn push_omission(&mut self) {
let s = color::omission_marker(self.role_colors_enabled);
self.record_chunk(s);
self.buf.push_str(s);
}
pub fn push_key(&mut self, quoted_key: &str) {
let s = color::wrap_role(
quoted_key,
color::ColorRole::Key,
self.role_colors_enabled,
);
self.buf.push_str(&s);
self.record_chunk(&s);
}
pub fn push_string_literal(&mut self, quoted_value: &str) {
let s = color::wrap_role(
quoted_value,
color::ColorRole::String,
self.role_colors_enabled,
);
self.buf.push_str(&s);
self.record_chunk(&s);
}
pub fn push_string_unquoted(&mut self, value: &str) {
let s = color::wrap_role(
value,
color::ColorRole::String,
self.role_colors_enabled,
);
self.buf.push_str(&s);
self.record_chunk(&s);
}
pub fn into_slot_stats(self) -> Option<Vec<OutputStats>> {
self.recorder.map(SlotStatsRecorder::into_output_stats)
}
pub fn is_compact_mode(&self) -> bool {
self.newline.is_empty() && self.indent_unit.is_empty()
}
pub fn style(&self) -> crate::serialization::types::Style {
self.style
}
pub fn line_number_width(&self) -> Option<usize> {
self.line_number_width
}
pub fn colors_enabled(&self) -> bool {
self.role_colors_enabled
}
}