rumdl_lib/utils/
line_ending.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum LineEnding {
3    Lf,
4    Crlf,
5    Mixed,
6}
7
8pub fn detect_line_ending_enum(content: &str) -> LineEnding {
9    let has_crlf = content.contains("\r\n");
10    // Check if there are LF characters that are NOT part of CRLF
11    let content_without_crlf = content.replace("\r\n", "");
12    let has_standalone_lf = content_without_crlf.contains('\n');
13
14    match (has_crlf, has_standalone_lf) {
15        (true, true) => LineEnding::Mixed, // Has both CRLF and standalone LF
16        (true, false) => LineEnding::Crlf, // Only CRLF
17        (false, true) => LineEnding::Lf,   // Only LF
18        (false, false) => LineEnding::Lf,  // No line endings, default to LF
19    }
20}
21
22pub fn detect_line_ending(content: &str) -> &'static str {
23    // Compatibility function matching the old signature
24    let crlf_count = content.matches("\r\n").count();
25    let lf_count = content.matches('\n').count() - crlf_count;
26
27    if crlf_count > lf_count { "\r\n" } else { "\n" }
28}
29
30pub fn normalize_line_ending(content: &str, target: LineEnding) -> String {
31    match target {
32        LineEnding::Lf => content.replace("\r\n", "\n"),
33        LineEnding::Crlf => {
34            // First normalize everything to LF, then convert to CRLF
35            let normalized = content.replace("\r\n", "\n");
36            normalized.replace('\n', "\r\n")
37        }
38        LineEnding::Mixed => content.to_string(), // Don't change mixed endings
39    }
40}
41
42pub fn ensure_consistent_line_endings(original: &str, modified: &str) -> String {
43    let original_ending = detect_line_ending_enum(original);
44
45    // For mixed line endings, normalize to the most common one (like detect_line_ending does)
46    let target_ending = if original_ending == LineEnding::Mixed {
47        // Use the same logic as detect_line_ending: prefer the more common one
48        let crlf_count = original.matches("\r\n").count();
49        let lf_count = original.matches('\n').count() - crlf_count;
50        if crlf_count > lf_count {
51            LineEnding::Crlf
52        } else {
53            LineEnding::Lf
54        }
55    } else {
56        original_ending
57    };
58
59    let modified_ending = detect_line_ending_enum(modified);
60
61    if target_ending != modified_ending {
62        normalize_line_ending(modified, target_ending)
63    } else {
64        modified.to_string()
65    }
66}
67
68pub fn get_line_ending_str(ending: LineEnding) -> &'static str {
69    match ending {
70        LineEnding::Lf => "\n",
71        LineEnding::Crlf => "\r\n",
72        LineEnding::Mixed => "\n", // Default to LF for mixed
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_detect_line_ending_enum() {
82        assert_eq!(detect_line_ending_enum("hello\nworld"), LineEnding::Lf);
83        assert_eq!(detect_line_ending_enum("hello\r\nworld"), LineEnding::Crlf);
84        assert_eq!(detect_line_ending_enum("hello\r\nworld\nmixed"), LineEnding::Mixed);
85        assert_eq!(detect_line_ending_enum("no line endings"), LineEnding::Lf);
86    }
87
88    #[test]
89    fn test_detect_line_ending() {
90        assert_eq!(detect_line_ending("hello\nworld"), "\n");
91        assert_eq!(detect_line_ending("hello\r\nworld"), "\r\n");
92        assert_eq!(detect_line_ending("hello\r\nworld\nmixed"), "\n"); // More LF than CRLF
93        assert_eq!(detect_line_ending("no line endings"), "\n");
94    }
95
96    #[test]
97    fn test_normalize_line_ending() {
98        assert_eq!(normalize_line_ending("hello\r\nworld", LineEnding::Lf), "hello\nworld");
99        assert_eq!(
100            normalize_line_ending("hello\nworld", LineEnding::Crlf),
101            "hello\r\nworld"
102        );
103        assert_eq!(
104            normalize_line_ending("hello\r\nworld\nmixed", LineEnding::Lf),
105            "hello\nworld\nmixed"
106        );
107    }
108
109    #[test]
110    fn test_ensure_consistent_line_endings() {
111        let original = "hello\r\nworld";
112        let modified = "hello\nworld\nextra";
113        assert_eq!(
114            ensure_consistent_line_endings(original, modified),
115            "hello\r\nworld\r\nextra"
116        );
117
118        let original = "hello\nworld";
119        let modified = "hello\r\nworld\r\nextra";
120        assert_eq!(
121            ensure_consistent_line_endings(original, modified),
122            "hello\nworld\nextra"
123        );
124    }
125}