#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FormatStyle {
pub line_width: usize,
pub indent_width: usize,
pub line_ending: LineEnding,
}
impl Default for FormatStyle {
fn default() -> Self {
Self {
line_width: 80,
indent_width: 2,
line_ending: LineEnding::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineEnding {
#[default]
Auto,
Lf,
Crlf,
Native,
}
impl LineEnding {
pub fn resolve(self, source: &str) -> &'static str {
match self {
LineEnding::Lf => "\n",
LineEnding::Crlf => "\r\n",
LineEnding::Native => {
if cfg!(windows) {
"\r\n"
} else {
"\n"
}
}
LineEnding::Auto => {
if source_is_crlf(source) {
"\r\n"
} else {
"\n"
}
}
}
}
}
fn source_is_crlf(source: &str) -> bool {
match source.find('\n') {
Some(idx) => idx > 0 && source.as_bytes()[idx - 1] == b'\r',
None => false,
}
}
pub(crate) fn apply_line_ending(formatted: &str, eol: &str) -> String {
let lf = if formatted.contains('\r') {
formatted.replace("\r\n", "\n")
} else {
formatted.to_string()
};
if eol == "\n" {
lf
} else {
lf.replace('\n', eol)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auto_detects_crlf_from_first_line_ending() {
assert_eq!(LineEnding::Auto.resolve("a\r\nb\n"), "\r\n");
assert_eq!(LineEnding::Auto.resolve("a\nb\r\n"), "\n");
assert_eq!(LineEnding::Auto.resolve("no newline"), "\n");
assert_eq!(LineEnding::Auto.resolve(""), "\n");
}
#[test]
fn explicit_endings_ignore_source() {
assert_eq!(LineEnding::Lf.resolve("a\r\n"), "\n");
assert_eq!(LineEnding::Crlf.resolve("a\n"), "\r\n");
}
#[test]
fn apply_canonicalizes_then_expands() {
assert_eq!(apply_line_ending("a\nb\r\nc\n", "\r\n"), "a\r\nb\r\nc\r\n");
assert_eq!(apply_line_ending("a\nb\r\nc\n", "\n"), "a\nb\nc\n");
}
}