diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index b9b7cb2e58..87b731b182 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -4,7 +4,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{DisplayRow, EditorStyle, ToOffset, ToPoint, scroll::ScrollAnchor}; use gpui::{Pixels, WindowTextSystem}; -use language::Point; +use language::{CharClassifier, Point}; use multi_buffer::{MultiBufferRow, MultiBufferSnapshot}; use serde::Deserialize; use workspace::searchable::Direction; @@ -303,15 +303,18 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis let classifier = map.buffer_snapshot.char_classifier_at(raw_point); find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| { - let is_word_start = - classifier.kind(left) != classifier.kind(right) && !right.is_whitespace(); - let is_subword_start = classifier.is_word('-') && left == '-' && right != '-' - || left == '_' && right != '_' - || left.is_lowercase() && right.is_uppercase(); - is_word_start || is_subword_start || left == '\n' + is_subword_start(left, right, &classifier) || left == '\n' }) } +pub fn is_subword_start(left: char, right: char, classifier: &CharClassifier) -> bool { + let is_word_start = classifier.kind(left) != classifier.kind(right) && !right.is_whitespace(); + let is_subword_start = classifier.is_word('-') && left == '-' && right != '-' + || left == '_' && right != '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_start || is_subword_start +} + /// Returns a position of the next word boundary, where a word character is defined as either /// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS). pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { @@ -361,15 +364,19 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo let classifier = map.buffer_snapshot.char_classifier_at(raw_point); find_boundary(map, point, FindRange::MultiLine, |left, right| { - let is_word_end = - (classifier.kind(left) != classifier.kind(right)) && !classifier.is_whitespace(left); - let is_subword_end = classifier.is_word('-') && left != '-' && right == '-' - || left != '_' && right == '_' - || left.is_lowercase() && right.is_uppercase(); - is_word_end || is_subword_end || right == '\n' + is_subword_end(left, right, &classifier) || right == '\n' }) } +pub fn is_subword_end(left: char, right: char, classifier: &CharClassifier) -> bool { + let is_word_end = + (classifier.kind(left) != classifier.kind(right)) && !classifier.is_whitespace(left); + let is_subword_end = classifier.is_word('-') && left != '-' && right == '-' + || left != '_' && right == '_' + || left.is_lowercase() && right.is_uppercase(); + is_word_end || is_subword_end +} + /// Returns a position of the start of the current paragraph, where a paragraph /// is defined as a run of non-blank lines. pub fn start_of_paragraph( diff --git a/crates/vim/src/helix/boundary.rs b/crates/vim/src/helix/boundary.rs index 290ae19374..40cd00bb46 100644 --- a/crates/vim/src/helix/boundary.rs +++ b/crates/vim/src/helix/boundary.rs @@ -3,6 +3,7 @@ use std::{error::Error, fmt::Display}; use editor::{ DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, + movement, }; use language::{CharClassifier, CharKind}; use text::Bias; @@ -19,7 +20,8 @@ impl Display for UnboundedErr { impl Error for UnboundedErr {} impl Object { - /// Returns the beginning of the inside of the closest object after the cursor if it can easily be found. Follows helix convention; + /// Returns the beginning of the inside of the closest object after the cursor if it can easily be found. + /// Follows helix convention. pub fn helix_next_start( self, map: &DisplaySnapshot, @@ -32,7 +34,8 @@ impl Object { self.helix_is_start(right, left, classifier) }) } - /// Returns the end of the inside of the closest object after the cursor if it can easily be found. Follows helix convention; + /// Returns the end of the inside of the closest object after the cursor if it can easily be found. + /// Follows helix convention. pub fn helix_next_end( self, map: &DisplaySnapshot, @@ -45,7 +48,8 @@ impl Object { self.helix_is_end(right, left, classifier) }) } - /// Returns the beginning of the inside of the closest object before the cursor if it can easily be found. Follows helix convention; + /// Returns the beginning of the inside of the closest object before the cursor if it can easily be found. + /// Follows helix convention. pub fn helix_previous_start( self, map: &DisplaySnapshot, @@ -58,7 +62,8 @@ impl Object { self.helix_is_start(right, left, classifier) }) } - /// Returns the end of the inside of the closest object before the cursor if it can easily be found. Follows helix convention; + /// Returns the end of the inside of the closest object before the cursor if it can easily be found. + /// Follows helix convention. pub fn helix_previous_end( self, map: &DisplaySnapshot, @@ -81,10 +86,11 @@ impl Object { match self { Self::Word { ignore_punctuation } => { let classifier = classifier.ignore_punctuation(ignore_punctuation); - Ok(is_word_start(left, right, classifier)) + Ok(is_word_start(left, right, classifier) || is_buffer_start(left)) } Self::Subword { ignore_punctuation } => { - todo!() + let classifier = classifier.ignore_punctuation(ignore_punctuation); + Ok(movement::is_subword_start(left, right, &classifier) || is_buffer_start(left)) } Self::AngleBrackets => Ok(left == '<'), Self::BackQuotes => Ok(left == '`'), @@ -106,10 +112,11 @@ impl Object { match self { Self::Word { ignore_punctuation } => { let classifier = classifier.ignore_punctuation(ignore_punctuation); - Ok(is_word_end(left, right, classifier)) + Ok(is_word_end(left, right, classifier) || is_buffer_end(right)) } Self::Subword { ignore_punctuation } => { - todo!() + let classifier = classifier.ignore_punctuation(ignore_punctuation); + Ok(movement::is_subword_end(left, right, &classifier) || is_buffer_end(right)) } Self::AngleBrackets => Ok(right == '>'), Self::BackQuotes => Ok(right == '`'), @@ -136,7 +143,7 @@ fn try_find_boundary( .next() .unwrap_or('\0'); - for ch in map.buffer_snapshot.chars_at(offset) { + for ch in map.buffer_snapshot.chars_at(offset).chain(['\0']) { if is_boundary(prev_ch, ch)? { return Ok(Some( map.clip_point(offset.to_display_point(map), Bias::Right), @@ -157,13 +164,13 @@ fn try_find_preceding_boundary( let mut offset = from.to_offset(map, Bias::Right); let mut prev_ch = map.buffer_snapshot.chars_at(offset).next().unwrap_or('\0'); - for ch in map.buffer_snapshot.reversed_chars_at(offset) { + for ch in map.buffer_snapshot.reversed_chars_at(offset).chain(['\0']) { if is_boundary(ch, prev_ch)? { return Ok(Some( map.clip_point(offset.to_display_point(map), Bias::Right), )); } - offset -= ch.len_utf8(); + offset = offset.saturating_sub(ch.len_utf8()); prev_ch = ch; } @@ -174,6 +181,10 @@ fn is_buffer_start(left: char) -> bool { left == '\0' } +fn is_buffer_end(right: char) -> bool { + right == '\0' +} + fn is_word_start(left: char, right: char, classifier: CharClassifier) -> bool { classifier.kind(left) != classifier.kind(right) && classifier.kind(right) != CharKind::Whitespace diff --git a/crates/vim/src/helix/object.rs b/crates/vim/src/helix/object.rs index 29df3d6640..3f222efd72 100644 --- a/crates/vim/src/helix/object.rs +++ b/crates/vim/src/helix/object.rs @@ -84,7 +84,8 @@ impl Object { } } - /// Returns the range of the object the cursor is over if it can be found with simple boundary checking. Potentially none. Follows helix convention. + /// Returns the range of the object the cursor is over if it can be found with simple boundary checking. + /// Potentially none. Follows helix convention. fn current_bounded_object( self, map: &DisplaySnapshot, @@ -116,7 +117,8 @@ impl Object { Ok(Some(prev_start..next_end)) } - /// Returns the range of the next object the cursor is not over if it can be found with simple boundary checking. Potentially none. Follows helix convention. + /// Returns the range of the next object the cursor is not over if it can be found with simple boundary checking. + /// Potentially none. Follows helix convention. fn next_bounded_object( self, map: &DisplaySnapshot, @@ -138,7 +140,8 @@ impl Object { Ok(Some(next_start..end)) } - /// Returns the previous range of the object the cursor not is over if it can be found with simple boundary checking. Potentially none. Follows helix convention. + /// Returns the previous range of the object the cursor not is over if it can be found with simple boundary checking. + /// Potentially none. Follows helix convention. fn previous_bounded_object( self, map: &DisplaySnapshot, diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index ef5e16c663..fc5234ff80 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -1044,6 +1044,8 @@ impl Operator { Operator::AutoIndent => "=".to_string(), Operator::ShellCommand => "=".to_string(), Operator::HelixMatch => "m".to_string(), + Operator::SelectNext => "]".to_string(), + Operator::SelectPrevious => "[".to_string(), _ => self.id().to_string(), } }