]> git.lizzy.rs Git - rust.git/blob - src/formatting/newline_style.rs
Fix auto detection of windows newlines for inputs with multibyte chars
[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     match effective_newline_style(newline_style, raw_input_text) {
15         EffectiveNewlineStyle::Windows => {
16             *formatted_text = convert_to_windows_newlines(formatted_text);
17         }
18         EffectiveNewlineStyle::Unix => {}
19     }
20 }
21
22 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
23 enum EffectiveNewlineStyle {
24     Windows,
25     Unix,
26 }
27
28 fn effective_newline_style(
29     newline_style: NewlineStyle,
30     raw_input_text: &str,
31 ) -> EffectiveNewlineStyle {
32     match newline_style {
33         NewlineStyle::Auto => auto_detect_newline_style(raw_input_text),
34         NewlineStyle::Native => native_newline_style(),
35         NewlineStyle::Windows => EffectiveNewlineStyle::Windows,
36         NewlineStyle::Unix => EffectiveNewlineStyle::Unix,
37     }
38 }
39
40 const LINE_FEED: char = '\n';
41 const CARRIAGE_RETURN: char = '\r';
42
43 fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle {
44     if let Some(pos) = raw_input_text.chars().position(|ch| ch == LINE_FEED) {
45         let pos = pos.saturating_sub(1);
46         if let Some(CARRIAGE_RETURN) = raw_input_text.chars().nth(pos) {
47             EffectiveNewlineStyle::Windows
48         } else {
49             EffectiveNewlineStyle::Unix
50         }
51     } else {
52         native_newline_style()
53     }
54 }
55
56 fn native_newline_style() -> EffectiveNewlineStyle {
57     if cfg!(windows) {
58         EffectiveNewlineStyle::Windows
59     } else {
60         EffectiveNewlineStyle::Unix
61     }
62 }
63
64 fn convert_to_windows_newlines(formatted_text: &String) -> String {
65     let mut transformed = String::with_capacity(2 * formatted_text.capacity());
66     for c in formatted_text.chars() {
67         const WINDOWS_NEWLINE: &str = "\r\n";
68         match c {
69             LINE_FEED => transformed.push_str(WINDOWS_NEWLINE),
70             CARRIAGE_RETURN => continue,
71             c => transformed.push(c),
72         }
73     }
74     transformed
75 }
76
77 #[cfg(test)]
78 mod tests {
79     use super::*;
80
81     #[test]
82     fn auto_detects_unix_newlines() {
83         assert_eq!(
84             EffectiveNewlineStyle::Unix,
85             auto_detect_newline_style("One\nTwo\nThree")
86         );
87     }
88
89     #[test]
90     fn auto_detects_windows_newlines() {
91         assert_eq!(
92             EffectiveNewlineStyle::Windows,
93             auto_detect_newline_style("One\r\nTwo\r\nThree")
94         );
95     }
96
97     #[test]
98     fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() {
99         assert_eq!(
100             EffectiveNewlineStyle::Windows,
101             auto_detect_newline_style("A ðŸŽ¢ of a first line\r\nTwo\r\nThree")
102         );
103     }
104
105     #[test]
106     fn falls_back_to_native_newlines_if_no_newlines_are_found() {
107         let expected_newline_style = if cfg!(windows) {
108             EffectiveNewlineStyle::Windows
109         } else {
110             EffectiveNewlineStyle::Unix
111         };
112         assert_eq!(
113             expected_newline_style,
114             auto_detect_newline_style("One Two Three")
115         );
116     }
117
118     #[test]
119     fn test_newline_style_auto_apply() {
120         let auto = NewlineStyle::Auto;
121
122         let formatted_text = "One\nTwo\nThree";
123         let raw_input_text = "One\nTwo\nThree";
124
125         let mut out = String::from(formatted_text);
126         apply_newline_style(auto, &mut out, raw_input_text);
127         assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
128
129         let formatted_text = "One\nTwo\nThree";
130         let raw_input_text = "One\r\nTwo\r\nThree";
131
132         let mut out = String::from(formatted_text);
133         apply_newline_style(auto, &mut out, raw_input_text);
134         assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
135
136         #[cfg(not(windows))]
137         {
138             let formatted_text = "One\nTwo\nThree";
139             let raw_input_text = "One Two Three";
140
141             let mut out = String::from(formatted_text);
142             apply_newline_style(auto, &mut out, raw_input_text);
143             assert_eq!(
144                 "One\nTwo\nThree", &out,
145                 "auto-native-unix should detect 'lf'"
146             );
147         }
148
149         #[cfg(windows)]
150         {
151             let formatted_text = "One\nTwo\nThree";
152             let raw_input_text = "One Two Three";
153
154             let mut out = String::from(formatted_text);
155             apply_newline_style(auto, &mut out, raw_input_text);
156             assert_eq!(
157                 "One\r\nTwo\r\nThree", &out,
158                 "auto-native-windows should detect 'crlf'"
159             );
160         }
161     }
162 }