From c0d05c82b7bf3ca3355f2cf82a76537f0338a7b4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 21 Mar 2022 17:37:36 -0600 Subject: [PATCH] WIP: Start on previous_subword_start Co-Authored-By: Max Brunsfeld Co-Authored-By: Keith Simmons --- crates/editor/src/movement.rs | 98 +++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index fe66a23c3d..c8f217b57e 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -138,40 +138,22 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa }) } +pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + find_boundary_reversed(map, point, |left, right| { + (char_kind(left) != char_kind(right) + || left == '_' && right != '_' + || left.is_lowercase() && right.is_uppercase()) + && !right.is_whitespace() + }) +} + pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { find_boundary(map, point, |left, right| { char_kind(left) != char_kind(right) && !left.is_whitespace() }) } -fn find_boundary( - map: &DisplaySnapshot, - mut start: DisplayPoint, - is_boundary: impl Fn(char, char) -> bool, -) -> DisplayPoint { - let mut prev_ch = None; - for ch in map.chars_at(start) { - if let Some(prev_ch) = prev_ch { - if ch == '\n' { - break; - } - if is_boundary(prev_ch, ch) { - break; - } - } - - if ch == '\n' { - *start.row_mut() += 1; - *start.column_mut() = 0; - } else { - *start.column_mut() += ch.len_utf8() as u32; - } - prev_ch = Some(ch); - } - map.clip_point(start, Bias::Right) -} - -fn find_boundary_reversed( +pub fn find_boundary_reversed( map: &DisplaySnapshot, mut start: DisplayPoint, is_boundary: impl Fn(char, char) -> bool, @@ -212,6 +194,33 @@ fn find_boundary_reversed( boundary } +pub fn find_boundary( + map: &DisplaySnapshot, + mut start: DisplayPoint, + is_boundary: impl Fn(char, char) -> bool, +) -> DisplayPoint { + let mut prev_ch = None; + for ch in map.chars_at(start) { + if let Some(prev_ch) = prev_ch { + if ch == '\n' { + break; + } + if is_boundary(prev_ch, ch) { + break; + } + } + + if ch == '\n' { + *start.row_mut() += 1; + *start.column_mut() = 0; + } else { + *start.column_mut() += ch.len_utf8() as u32; + } + prev_ch = Some(ch); + } + map.clip_point(start, Bias::Right) +} + pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); let text = &map.buffer_snapshot; @@ -266,6 +275,39 @@ mod tests { assert(" ab|——|cd", cx); } + #[gpui::test] + fn test_previous_subword_start(cx: &mut gpui::MutableAppContext) { + fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) { + let (snapshot, display_points) = marked_snapshot(marked_text, cx); + assert_eq!( + previous_subword_start(&snapshot, display_points[1]), + display_points[0] + ); + } + + // Subword boundaries are respected + assert("lorem_|ip|sum", cx); + assert("lorem_|ipsum|", cx); + assert("|lorem_|ipsum", cx); + assert("lorem_|ipsum_|dolor", cx); + assert("lorem|Ip|sum", cx); + assert("lorem|Ipsum|", cx); + + // Word boundaries are still respected + assert("\n| |lorem", cx); + assert(" |lorem|", cx); + assert(" |lor|em", cx); + assert("\nlorem\n| |ipsum", cx); + assert("\n\n|\n|", cx); + assert(" |lorem |ipsum", cx); + assert("lorem|-|ipsum", cx); + assert("lorem|-#$@|ipsum", cx); + assert(" |defγ|", cx); + assert(" bc|Δ|", cx); + assert(" |bcδ|", cx); + assert(" ab|——|cd", cx); + } + #[gpui::test] fn test_next_word_end(cx: &mut gpui::MutableAppContext) { fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {