#[derive(Debug, Clone, PartialEq)]
pub(super) struct ListItemIndent {
pub marker_padding: usize,
pub marker_width: usize,
pub spaces_after: usize,
pub checkbox_width: usize,
}
impl ListItemIndent {
pub fn content_offset(&self) -> usize {
self.marker_padding + self.marker_width + self.spaces_after + self.checkbox_width
}
pub fn hanging_indent(&self, base_indent: usize) -> usize {
base_indent + self.content_offset()
}
}
pub(super) fn calculate_list_item_indent(
marker: &str,
max_marker_width: usize,
has_checkbox: bool,
) -> ListItemIndent {
let is_alignable = is_alignable_marker(marker);
let marker_padding = if is_alignable && max_marker_width > 0 {
max_marker_width.saturating_sub(marker.len())
} else {
0
};
let spaces_after = if marker.len() == 2
&& marker.starts_with(|c: char| c.is_ascii_uppercase())
&& marker.ends_with('.')
{
2
} else {
1
};
let checkbox_width = if has_checkbox { 4 } else { 0 };
ListItemIndent {
marker_padding,
marker_width: marker.len(),
spaces_after,
checkbox_width,
}
}
pub(super) fn is_alignable_marker(marker: &str) -> bool {
if marker.starts_with("(@") {
return false;
}
if marker.len() == 1 && (marker == "-" || marker == "*" || marker == "+") {
return false;
}
if marker.len() < 2 {
return false;
}
marker.chars().any(|c| c.is_alphabetic())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bullet_marker_no_alignment() {
let indent = calculate_list_item_indent("-", 0, false);
assert_eq!(indent.marker_padding, 0);
assert_eq!(indent.marker_width, 1);
assert_eq!(indent.spaces_after, 1);
assert_eq!(indent.checkbox_width, 0);
assert_eq!(indent.content_offset(), 2);
}
#[test]
fn test_numeric_marker_no_alignment() {
let indent = calculate_list_item_indent("1.", 0, false);
assert_eq!(indent.marker_padding, 0);
assert_eq!(indent.marker_width, 2);
assert_eq!(indent.spaces_after, 1);
assert_eq!(indent.content_offset(), 3);
}
#[test]
fn test_roman_numeral_with_alignment() {
let indent = calculate_list_item_indent("i.", 4, false);
assert_eq!(indent.marker_padding, 2); assert_eq!(indent.marker_width, 2);
assert_eq!(indent.spaces_after, 1);
assert_eq!(indent.content_offset(), 5); }
#[test]
fn test_uppercase_letter_marker() {
let indent = calculate_list_item_indent("A.", 0, false);
assert_eq!(indent.marker_padding, 0);
assert_eq!(indent.marker_width, 2);
assert_eq!(indent.spaces_after, 2); assert_eq!(indent.content_offset(), 4);
}
#[test]
fn test_task_checkbox() {
let indent = calculate_list_item_indent("-", 0, true);
assert_eq!(indent.checkbox_width, 4);
assert_eq!(indent.content_offset(), 6); }
#[test]
fn test_is_alignable_marker() {
assert!(is_alignable_marker("i."));
assert!(is_alignable_marker("iv."));
assert!(is_alignable_marker("a."));
assert!(is_alignable_marker("z."));
assert!(is_alignable_marker("A."));
assert!(is_alignable_marker("(a)"));
assert!(is_alignable_marker("i)"));
assert!(!is_alignable_marker("-"));
assert!(!is_alignable_marker("*"));
assert!(!is_alignable_marker("+"));
assert!(!is_alignable_marker("1."));
assert!(!is_alignable_marker("10."));
assert!(!is_alignable_marker("(@)"));
assert!(!is_alignable_marker("(@label)"));
}
#[test]
fn test_hanging_indent() {
let indent = calculate_list_item_indent("i.", 4, false);
assert_eq!(indent.hanging_indent(0), 5); assert_eq!(indent.hanging_indent(2), 7); }
}