]> git.lizzy.rs Git - rust.git/blob - src/formatting/newline_style.rs
Implement conversion to unix newlines
[rust.git] / src / formatting / newline_style.rs
1 use crate::NewlineStyle;
2
3 /// Apply this newline style to the formatted text. When the style is set
4 /// to `Auto`, the `raw_input_text` is used to detect the existing line
5 /// endings.
6 ///
7 /// If the style is set to `Auto` and `raw_input_text` contains no
8 /// newlines, the `Native` style will be used.
9 pub(crate) fn apply_newline_style(
10     newline_style: NewlineStyle,
11     formatted_text: &mut String,
12     raw_input_text: &str,
13 ) {
14     *formatted_text = match effective_newline_style(newline_style, raw_input_text) {
15         EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text),
16         EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text),
17     }
18 }
19
20 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
21 enum EffectiveNewlineStyle {
22     Windows,
23     Unix,
24 }
25
26 fn effective_newline_style(
27     newline_style: NewlineStyle,
28     raw_input_text: &str,
29 ) -> EffectiveNewlineStyle {
30     match newline_style {
31         NewlineStyle::Auto => auto_detect_newline_style(raw_input_text),
32         NewlineStyle::Native => native_newline_style(),
33         NewlineStyle::Windows => EffectiveNewlineStyle::Windows,
34         NewlineStyle::Unix => EffectiveNewlineStyle::Unix,
35     }
36 }
37
38 const LINE_FEED: char = '\n';
39 const CARRIAGE_RETURN: char = '\r';
40 const WINDOWS_NEWLINE: &str = "\r\n";
41 const UNIX_NEWLINE: &str = "\n";
42
43 fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle {
44     let first_line_feed_pos = raw_input_text.chars().position(|ch| ch == LINE_FEED);
45     match first_line_feed_pos {
46         Some(first_line_feed_pos) => {
47             let char_before_line_feed_pos = first_line_feed_pos.saturating_sub(1);
48             let char_before_line_feed = raw_input_text.chars().nth(char_before_line_feed_pos);
49             match char_before_line_feed {
50                 Some(CARRIAGE_RETURN) => EffectiveNewlineStyle::Windows,
51                 _ => EffectiveNewlineStyle::Unix,
52             }
53         }
54         None => native_newline_style(),
55     }
56 }
57
58 fn native_newline_style() -> EffectiveNewlineStyle {
59     if cfg!(windows) {
60         EffectiveNewlineStyle::Windows
61     } else {
62         EffectiveNewlineStyle::Unix
63     }
64 }
65
66 fn convert_to_windows_newlines(formatted_text: &String) -> String {
67     let mut transformed = String::with_capacity(2 * formatted_text.capacity());
68     for c in formatted_text.chars() {
69         match c {
70             LINE_FEED => transformed.push_str(WINDOWS_NEWLINE),
71             c => transformed.push(c),
72         }
73     }
74     transformed
75 }
76
77 fn convert_to_unix_newlines(formatted_text: &String) -> String {
78     formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE)
79 }
80
81 #[cfg(test)]
82 mod tests {
83     use super::*;
84
85     #[test]
86     fn auto_detects_unix_newlines() {
87         assert_eq!(
88             EffectiveNewlineStyle::Unix,
89             auto_detect_newline_style("One\nTwo\nThree")
90         );
91     }
92
93     #[test]
94     fn auto_detects_windows_newlines() {
95         assert_eq!(
96             EffectiveNewlineStyle::Windows,
97             auto_detect_newline_style("One\r\nTwo\r\nThree")
98         );
99     }
100
101     #[test]
102     fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() {
103         assert_eq!(
104             EffectiveNewlineStyle::Windows,
105             auto_detect_newline_style("A ðŸŽ¢ of a first line\r\nTwo\r\nThree")
106         );
107     }
108
109     #[test]
110     fn falls_back_to_native_newlines_if_no_newlines_are_found() {
111         let expected_newline_style = if cfg!(windows) {
112             EffectiveNewlineStyle::Windows
113         } else {
114             EffectiveNewlineStyle::Unix
115         };
116         assert_eq!(
117             expected_newline_style,
118             auto_detect_newline_style("One Two Three")
119         );
120     }
121
122     #[test]
123     fn auto_detects_and_applies_unix_newlines() {
124         let formatted_text = "One\nTwo\nThree";
125         let raw_input_text = "One\nTwo\nThree";
126
127         let mut out = String::from(formatted_text);
128         apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
129         assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
130     }
131
132     #[test]
133     fn auto_detects_and_applies_windows_newlines() {
134         let formatted_text = "One\nTwo\nThree";
135         let raw_input_text = "One\r\nTwo\r\nThree";
136
137         let mut out = String::from(formatted_text);
138         apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
139         assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
140     }
141
142     #[test]
143     fn auto_detects_and_applies_native_newlines() {
144         let formatted_text = "One\nTwo\nThree";
145         let raw_input_text = "One Two Three";
146
147         let mut out = String::from(formatted_text);
148         apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
149
150         if cfg!(windows) {
151             assert_eq!(
152                 "One\r\nTwo\r\nThree", &out,
153                 "auto-native-windows should detect 'crlf'"
154             );
155         } else {
156             assert_eq!(
157                 "One\nTwo\nThree", &out,
158                 "auto-native-unix should detect 'lf'"
159             );
160         }
161     }
162
163     #[test]
164     fn applies_unix_newlines() {
165         let formatted_text = "One\r\nTwo\nThree";
166         let raw_input_text = formatted_text;
167
168         let mut out = String::from(formatted_text);
169         apply_newline_style(NewlineStyle::Unix, &mut out, raw_input_text);
170
171         assert_eq!("One\nTwo\nThree", &out);
172     }
173
174     #[test]
175     fn preserves_standalone_carriage_returns_when_applying_windows_newlines() {
176         let formatted_text = "One\nTwo\nThree\rDrei";
177         let raw_input_text = "One\nTwo\nThree\rDrei";
178
179         let mut out = String::from(formatted_text);
180         apply_newline_style(NewlineStyle::Windows, &mut out, raw_input_text);
181
182         assert_eq!("One\r\nTwo\r\nThree\rDrei", &out);
183     }
184
185     #[test]
186     fn preserves_standalone_carriage_returns_when_applying_unix_newlines() {
187         let formatted_text = "One\nTwo\nThree\rDrei";
188         let raw_input_text = "One\nTwo\nThree\rDrei";
189
190         let mut out = String::from(formatted_text);
191         apply_newline_style(NewlineStyle::Unix, &mut out, raw_input_text);
192
193         assert_eq!("One\nTwo\nThree\rDrei", &out);
194     }
195 }