typ_buffer/line_ending.rs
1//! Which line terminator a file uses.
2//!
3//! Detected on load, recorded here, and written back by `save`. The rope holds
4//! LF only, so nothing between those two points has to know about `\r`.
5
6/// The line terminator a buffer was loaded with.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum LineEnding {
9 /// `\n`. The default for a new or newline-free file on every platform,
10 /// including Windows — a file TYPE creates has no existing convention to
11 /// honour, and LF is what the tools around it emit.
12 #[default]
13 Lf,
14 /// `\r\n`.
15 Crlf,
16}
17
18impl LineEnding {
19 /// What a status bar shows. The names every editor uses.
20 pub fn label(self) -> &'static str {
21 match self {
22 LineEnding::Lf => "LF",
23 LineEnding::Crlf => "CRLF",
24 }
25 }
26
27 /// The characters themselves, as `save` writes them.
28 pub fn as_str(self) -> &'static str {
29 match self {
30 LineEnding::Lf => "\n",
31 LineEnding::Crlf => "\r\n",
32 }
33 }
34
35 /// Detect from a file's contents.
36 ///
37 /// The **first** terminator decides. A mixed file is not a third kind of
38 /// file: whatever line one did is what the file is, and it is what gets
39 /// written back. Taking a majority instead would mean a save that silently
40 /// rewrites every line break in somebody's file because the count went the
41 /// other way.
42 ///
43 /// A lone `\r` is not a line ending. Classic Mac endings died with Mac OS 9
44 /// and a stray carriage return inside a line is far likelier, so treating
45 /// one as a terminator would misread an ordinary file badly.
46 pub fn detect(text: &str) -> Self {
47 match text.find('\n') {
48 Some(0) => LineEnding::Lf,
49 Some(index) if text.as_bytes()[index - 1] == b'\r' => LineEnding::Crlf,
50 Some(_) => LineEnding::Lf,
51 None => LineEnding::Lf,
52 }
53 }
54}