diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 55365500c..3f6bea5a9 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -111,6 +111,6 @@ pub use diagnostic::Diagnostic; pub use state::State; pub use line_ending::{ - auto_detect_line_ending, rope_slice_to_line_ending, LineEnding, DEFAULT_LINE_ENDING, + auto_detect_line_ending, rope_slice_to_line_ending, LineEnding, DEFAULT_LINE_ENDING, get_line_ending }; pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction}; diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index f9d67b573..423f4b929 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -14,7 +14,7 @@ pub enum LineEnding { } impl LineEnding { - pub fn len(&self) -> usize { + pub fn len_chars(&self) -> usize { match self { Self::Crlf => 2, _ => 1, @@ -28,10 +28,9 @@ impl LineEnding { Self::Nel => "\u{0085}", Self::LS => "\u{2028}", Self::CR => "\u{000D}", - _ => panic!( - "Unexpected line ending: {:?}, expected Crlf, LF, CR, Nel, or LS.", - self - ), + Self::VT => "\u{000B}", + Self::FF => "\u{000C}", + Self::PS => "\u{2029}", } } } @@ -93,6 +92,20 @@ pub fn auto_detect_line_ending(doc: &Rope) -> Option { ending } +/// Returns the passed line's line ending, if any. +pub fn get_line_ending(line: &RopeSlice) -> Option { + // Last character as str. + let g1 = line.slice(line.len_chars().saturating_sub(1)..).as_str().unwrap(); + + // Last two characters as str, or empty str if they're not contiguous. + // It's fine to punt on the non-contiguous case, because Ropey guarantees + // that CRLF is always contiguous. + let g2 = line.slice(line.len_chars().saturating_sub(2)..).as_str().unwrap_or(""); + + // First check the two-character case for CRLF, then check the single-character case. + str_to_line_ending(g2).or_else(|| str_to_line_ending(g1)) +} + #[cfg(target_os = "windows")] pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf; #[cfg(not(target_os = "windows"))] diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 62faadf9b..d894a646d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,7 +5,7 @@ use helix_core::{ object, pos_at_coords, regex::{self, Regex}, register::{self, Register, Registers}, - search, selection, Change, ChangeSet, LineEnding, Position, Range, Rope, RopeSlice, Selection, + search, selection, Change, ChangeSet, LineEnding, Position, Range, Rope, RopeSlice, Selection, get_line_ending, SmallVec, Tendril, Transaction, }; @@ -183,11 +183,10 @@ pub fn move_line_end(cx: &mut Context) { let text = doc.text(); let line = text.char_to_line(range.head); - // Line end is pos at the start of next line - 1 - // subtract another 1 because the line ends with \n let pos = text .line_to_char(line + 1) - .saturating_sub(doc.line_ending().len() + 1); + .saturating_sub(get_line_ending(&text.line(line)).map(|le| le.len_chars()).unwrap_or(0)); + Range::new(pos, pos) }); @@ -607,11 +606,10 @@ pub fn extend_line_end(cx: &mut Context) { let text = doc.text(); let line = text.char_to_line(range.head); - // Line end is pos at the start of next line - 1 - // subtract another 1 because the line ends with \n let pos = text .line_to_char(line + 1) - .saturating_sub(doc.line_ending().len() + 1); + .saturating_sub(get_line_ending(&text.line(line)).map(|le| le.len_chars()).unwrap_or(0)); + Range::new(range.anchor, pos) });