use std::ops::RangeInclusive;
#[derive(Default, Debug, Copy, Clone)]
pub enum WrapMethod {
Width,
Capped(usize),
Word,
#[default]
NoWrap,
}
impl WrapMethod {
#[must_use]
pub fn is_no_wrap(&self) -> bool {
matches!(self, Self::NoWrap)
}
pub fn wrapping_cap(&self, width: usize) -> usize {
match self {
WrapMethod::Capped(cap) => *cap,
_ => width,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TabStops(pub usize);
impl TabStops {
#[inline]
pub fn spaces_at(&self, x: usize) -> usize {
self.0 - (x % self.0)
}
}
impl Default for TabStops {
fn default() -> Self {
TabStops(4)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewLine {
AlwaysAs(char),
AfterSpaceAs(char),
#[default]
Hidden,
}
impl NewLine {
#[inline]
pub fn char(&self, last_char: Option<char>) -> Option<char> {
match self {
NewLine::AlwaysAs(char) => Some(*char),
NewLine::AfterSpaceAs(char) => {
if last_char.is_some_and(|char| char.is_whitespace() && char != '\n') {
Some(*char)
} else {
Some(' ')
}
}
NewLine::Hidden => None,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct ScrollOff {
pub x: usize,
pub y: usize,
}
impl Default for ScrollOff {
fn default() -> Self {
ScrollOff { y: 3, x: 3 }
}
}
#[derive(Debug, Clone)]
pub struct WordChars(Vec<std::ops::RangeInclusive<char>>);
impl WordChars {
pub fn new(ranges: Vec<std::ops::RangeInclusive<char>>) -> Self {
let word_chars = WordChars(ranges);
assert!(
![' ', '\t', '\n']
.into_iter()
.any(|char| word_chars.contains(char)),
"WordChars cannot contain ' ', '\\n' or '\\t'."
);
word_chars
}
#[inline]
pub fn contains(&self, char: char) -> bool {
self.0.iter().any(|chars| chars.contains(&char))
}
}
#[derive(Debug, Clone)]
pub struct PrintCfg {
pub wrap_method: WrapMethod,
pub indent_wrap: bool,
pub tab_stops: TabStops,
pub new_line: NewLine,
pub scrolloff: ScrollOff,
pub word_chars: WordChars,
}
impl PrintCfg {
pub fn new() -> Self {
Self {
wrap_method: WrapMethod::default(),
indent_wrap: true,
tab_stops: TabStops(4),
new_line: NewLine::default(),
scrolloff: ScrollOff::default(),
word_chars: WordChars::new(vec!['A'..='Z', 'a'..='z', '0'..='9', '_'..='_']),
}
}
pub fn with_no_wrapping(self) -> Self {
Self {
wrap_method: WrapMethod::NoWrap,
..self
}
}
pub fn width_wrapped(self) -> Self {
Self {
wrap_method: WrapMethod::Width,
..self
}
}
pub fn word_wrapped(self) -> Self {
Self {
wrap_method: WrapMethod::Word,
..self
}
}
pub fn wrapped_on_cap(self, cap: usize) -> Self {
Self {
wrap_method: WrapMethod::Capped(cap),
..self
}
}
pub fn indenting_wrap(self) -> Self {
Self {
indent_wrap: true,
..self
}
}
pub fn with_tabs_size(self, tab_size: usize) -> Self {
Self {
tab_stops: TabStops(tab_size),
..self
}
}
pub fn with_new_line_as(self, char: char) -> Self {
Self {
new_line: NewLine::AlwaysAs(char),
..self
}
}
pub fn with_trailing_new_line_as(self, char: char) -> Self {
Self {
new_line: NewLine::AfterSpaceAs(char),
..self
}
}
pub fn with_scrolloff(self, x: usize, y: usize) -> Self {
Self {
scrolloff: ScrollOff { x, y },
..self
}
}
pub fn with_x_scrolloff(self, x_gap: usize) -> Self {
Self {
scrolloff: ScrollOff {
y: self.scrolloff.y,
x: x_gap,
},
..self
}
}
pub fn with_y_scrolloff(self, y_gap: usize) -> Self {
Self {
scrolloff: ScrollOff {
x: self.scrolloff.x,
y: y_gap,
},
..self
}
}
pub fn with_word_chars(self, word_chars: impl Iterator<Item = RangeInclusive<char>>) -> Self {
let word_chars = WordChars::new(word_chars.collect());
Self { word_chars, ..self }
}
pub(crate) fn default_for_files() -> Self {
Self {
wrap_method: WrapMethod::default(),
indent_wrap: true,
tab_stops: TabStops(4),
new_line: NewLine::AlwaysAs(' '),
scrolloff: ScrollOff::default(),
word_chars: WordChars::new(vec!['a'..='z', 'A'..='Z', '0'..='9', '_'..='_']),
}
}
}
impl Default for PrintCfg {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct IterCfg<'a> {
cfg: &'a PrintCfg,
iter_lfs: bool,
force_wrap: Option<WrapMethod>,
no_indent_wrap: bool,
}
impl<'a> IterCfg<'a> {
pub fn new(cfg: &'a PrintCfg) -> Self {
Self {
cfg,
iter_lfs: true,
force_wrap: None,
no_indent_wrap: false,
}
}
pub fn outsource_lfs(self) -> Self {
Self {
iter_lfs: false,
..self
}
}
pub fn dont_wrap(self) -> Self {
Self {
force_wrap: Some(WrapMethod::NoWrap),
..self
}
}
pub fn no_word_wrap(self) -> Self {
match self.cfg.wrap_method {
WrapMethod::Word if matches!(self.force_wrap, Some(WrapMethod::NoWrap)) => self,
WrapMethod::Word => Self {
force_wrap: Some(WrapMethod::Width),
..self
},
WrapMethod::Width | WrapMethod::Capped(_) | WrapMethod::NoWrap => self,
}
}
pub fn no_indent_wrap(self) -> Self {
Self {
no_indent_wrap: true,
..self
}
}
#[inline]
pub fn show_lf(&self) -> bool {
self.iter_lfs
}
#[inline]
pub fn wrap_method(&self) -> WrapMethod {
self.force_wrap.unwrap_or(self.cfg.wrap_method)
}
#[inline]
pub fn indent_wrap(&self) -> bool {
!self.no_indent_wrap && self.cfg.indent_wrap
}
#[inline]
pub fn tab_stops(&self) -> TabStops {
self.cfg.tab_stops
}
#[inline]
pub fn new_line(&self) -> NewLine {
if self.iter_lfs {
NewLine::Hidden
} else {
self.cfg.new_line
}
}
#[inline]
pub fn scrolloff(&self) -> ScrollOff {
self.cfg.scrolloff
}
#[inline]
pub fn word_chars(&self) -> &WordChars {
&self.cfg.word_chars
}
}