use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineEnding {
Lf,
Crlf,
Mixed,
}
impl LineEnding {
pub fn detect(text: &str) -> Self {
let mut lf = 0usize;
let mut crlf = 0usize;
let bytes = text.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'\n' {
if i > 0 && bytes[i - 1] == b'\r' {
crlf += 1;
} else {
lf += 1;
}
}
i += 1;
}
match (lf, crlf) {
(0, 0) | (_, 0) => LineEnding::Lf,
(0, _) => LineEnding::Crlf,
_ => LineEnding::Mixed,
}
}
pub fn as_str(&self) -> &'static str {
match self {
LineEnding::Crlf => "\r\n",
_ => "\n",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ListMarkerStyle {
Dash,
Asterisk,
Plus,
NumberDot,
NumberParen,
}
impl ListMarkerStyle {
pub fn detect(marker_text: &str) -> Option<Self> {
let t = marker_text.trim_start();
let first = t.chars().next()?;
match first {
'-' => Some(Self::Dash),
'*' => Some(Self::Asterisk),
'+' => Some(Self::Plus),
c if c.is_ascii_digit() => {
let rest = t.trim_start_matches(|c: char| c.is_ascii_digit());
match rest.chars().next() {
Some('.') => Some(Self::NumberDot),
Some(')') => Some(Self::NumberParen),
_ => None,
}
}
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeFenceStyle {
pub marker: char,
pub length: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceTrivia {
pub leading_blank_lines: u16,
pub trailing_blank_lines: u16,
pub indentation: String,
pub list_marker_style: Option<ListMarkerStyle>,
pub code_fence_style: Option<CodeFenceStyle>,
}
impl SourceTrivia {
pub fn compute(text: &str, start: usize, end: usize) -> Self {
let before = &text[..start];
let after = &text[end.min(text.len())..];
let leading_blank_lines = count_adjacent_blank_lines(before.lines().rev());
let trailing_blank_lines = count_adjacent_blank_lines(after.lines());
let first_line = text[start..].lines().next().unwrap_or("");
let indentation: String = first_line
.chars()
.take_while(|c| *c == ' ' || *c == '\t')
.collect();
Self {
leading_blank_lines,
trailing_blank_lines,
indentation,
list_marker_style: None,
code_fence_style: None,
}
}
}
fn count_adjacent_blank_lines<'a>(lines: impl Iterator<Item = &'a str>) -> u16 {
let mut n = 0u16;
for line in lines {
if line.trim().is_empty() {
n = n.saturating_add(1);
} else {
break;
}
}
n
}