@@ -5,11 +5,11 @@ use crate::{Rope, RopeGraphemes, RopeSlice};
5pub enum LineEnding {
5pub enum LineEnding {
6 Crlf, // CarriageReturn followed by LineFeed
6 Crlf, // CarriageReturn followed by LineFeed
7 LF, // U+000A -- LineFeed
7 LF, // U+000A -- LineFeed
8+ VT, // U+000B -- VerticalTab
9+ FF, // U+000C -- FormFeed
8 CR, // U+000D -- CarriageReturn
10 CR, // U+000D -- CarriageReturn
9 Nel, // U+0085 -- NextLine
11 Nel, // U+0085 -- NextLine
10 LS, // U+2028 -- Line Separator
12 LS, // U+2028 -- Line Separator
11− VT, // U+000B -- VerticalTab
12− FF, // U+000C -- FormFeed
13 PS, // U+2029 -- ParagraphSeparator
13 PS, // U+2029 -- ParagraphSeparator
@@ -21,74 +21,58 @@ impl LineEnding {
24− pub fn as_str(&self) -> &str {
24+ pub fn as_str(&self) -> &'static str {
25 match self {
25 match self {
26 Self::Crlf => "\u{000D}\u{000A}",
26 Self::Crlf => "\u{000D}\u{000A}",
27 Self::LF => "\u{000A}",
27 Self::LF => "\u{000A}",
28− Self::Nel => "\u{0085}",
29− Self::LS => "\u{2028}",
30− Self::CR => "\u{000D}",
31 Self::VT => "\u{000B}",
28 Self::VT => "\u{000B}",
32 Self::FF => "\u{000C}",
29 Self::FF => "\u{000C}",
30+ Self::CR => "\u{000D}",
31+ Self::Nel => "\u{0085}",
32+ Self::LS => "\u{2028}",
33 Self::PS => "\u{2029}",
33 Self::PS => "\u{2029}",
38−pub fn rope_slice_to_line_ending(g: &RopeSlice) -> Option<LineEnding> {
37+ pub fn from_str(g: &str) -> Option<LineEnding> {
39− if let Some(text) = g.as_str() {
38+ match g {
40− str_to_line_ending(text)
39+ "\u{000D}\u{000A}" => Some(LineEnding::Crlf),
41− }
40+ "\u{000A}" => Some(LineEnding::LF),
42− else {
41+ "\u{000B}" => Some(LineEnding::VT),
42+ "\u{000C}" => Some(LineEnding::FF),
43+ "\u{000D}" => Some(LineEnding::CR),
44+ "\u{0085}" => Some(LineEnding::Nel),
45+ "\u{2028}" => Some(LineEnding::LS),
46+ "\u{2029}" => Some(LineEnding::PS),
43− // Not a line ending
47+ // Not a line ending
48−pub fn str_to_line_ending(g: &str) -> Option<LineEnding> {
52+ pub fn from_rope_slice(g: &RopeSlice) -> Option<LineEnding> {
49− match g {
53+ if let Some(text) = g.as_str() {
50− "\u{000D}\u{000A}" => Some(LineEnding::Crlf),
51− "\u{000A}" => Some(LineEnding::LF),
52− "\u{000D}" => Some(LineEnding::CR),
54+ LineEnding::from_str(text)
53− "\u{0085}" => Some(LineEnding::Nel),
55+ } else {
54− "\u{2028}" => Some(LineEnding::LS),
55− "\u{000B}" => Some(LineEnding::VT),
56− "\u{000C}" => Some(LineEnding::FF),
57− "\u{2029}" => Some(LineEnding::PS),
58− // Not a line ending
56+ // Non-contiguous, so it can't be a line ending.
57+ // Specifically, Ropey guarantees that CRLF is always
58+ // contiguous. And the remaining line endings are all
59+ // single `char`s, and therefore trivially contiguous.
65+/// Attempts to detect what line ending the passed document uses.
63pub fn auto_detect_line_ending(doc: &Rope) -> Option<LineEnding> {
66pub fn auto_detect_line_ending(doc: &Rope) -> Option<LineEnding> {
64− // based on https://github.com/cessen/led/blob/27572c8838a1c664ee378a19358604063881cc1d/src/editor/mod.rs#L88-L162
66− let mut ending = None;
67− // return first matched line ending. Not all possible line endings are being matched, as they might be special-use only
67+ // Return first matched line ending. Not all possible line endings
68+ // are being matched, as they might be special-use only
68 for line in doc.lines().take(100) {
69 for line in doc.lines().take(100) {
69− ending = match line.len_chars() {
70+ match get_line_ending(&line) {
71− let g = RopeGraphemes::new(line.slice((line.len_chars() - 1)..))
74− rope_slice_to_line_ending(&g)
77− let g = RopeGraphemes::new(line.slice((line.len_chars() - 2)..))
80− rope_slice_to_line_ending(&g)
84− if ending.is_some() {
86− Some(LineEnding::VT) | Some(LineEnding::FF) | Some(LineEnding::PS) => {}
71+ None | Some(LineEnding::VT) | Some(LineEnding::FF) | Some(LineEnding::PS) => {}
87− _ => return ending,
72+ ending => return ending,
94/// Returns the passed line's line ending, if any.
78/// Returns the passed line's line ending, if any.
@@ -108,13 +92,16 @@ pub fn get_line_ending(line: &RopeSlice) -> Option<LineEnding> {
108 .unwrap_or("");
92 .unwrap_or("");
110 // First check the two-character case for CRLF, then check the single-character case.
94 // First check the two-character case for CRLF, then check the single-character case.
111− str_to_line_ending(g2).or_else(|| str_to_line_ending(g1))
95+ LineEnding::from_str(g2).or_else(|| LineEnding::from_str(g1))
98+/// Returns the char index of the end of the given line, not including its line ending.
114pub fn line_end(slice: &RopeSlice, line: usize) -> usize {
99pub fn line_end(slice: &RopeSlice, line: usize) -> usize {
115− slice.line_to_char(line + 1).saturating_sub(get_line_ending(&slice.line(line))
100+ slice.line_to_char(line + 1).saturating_sub(
101+ get_line_ending(&slice.line(line))
116− .map(|le| le.len_chars())
102+ .map(|le| le.len_chars())
117− .unwrap_or(0))
103+ .unwrap_or(0),
120#[cfg(target_os = "windows")]
107#[cfg(target_os = "windows")]