@@ -54,6 +54,11 @@ pub fn init(app: &mut MutableAppContext) {
Binding::new("down", "buffer:move_down", Some("BufferView")),
Binding::new("left", "buffer:move_left", Some("BufferView")),
Binding::new("right", "buffer:move_right", Some("BufferView")),
+ Binding::new(
+ "alt-left",
+ "buffer:move_to_previous_word_boundary",
+ Some("BufferView"),
+ ),
Binding::new(
"alt-right",
"buffer:move_to_next_word_boundary",
@@ -146,6 +151,10 @@ pub fn init(app: &mut MutableAppContext) {
app.add_action("buffer:move_down", BufferView::move_down);
app.add_action("buffer:move_left", BufferView::move_left);
app.add_action("buffer:move_right", BufferView::move_right);
+ app.add_action(
+ "buffer:move_to_previous_word_boundary",
+ BufferView::move_to_previous_word_boundary,
+ );
app.add_action(
"buffer:move_to_next_word_boundary",
BufferView::move_to_next_word_boundary,
@@ -1094,6 +1103,24 @@ impl BufferView {
self.update_selections(selections, true, ctx);
}
+ pub fn move_to_previous_word_boundary(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+ let app = ctx.as_ref();
+ let mut selections = self.selections(app).to_vec();
+ {
+ let map = self.display_map.read(app);
+ for selection in &mut selections {
+ let head = selection.head().to_display_point(map, app).unwrap();
+ let new_head = movement::prev_word_boundary(map, head, app).unwrap();
+ let anchor = map.anchor_before(new_head, Bias::Left, app).unwrap();
+ selection.start = anchor.clone();
+ selection.end = anchor;
+ selection.reversed = false;
+ selection.goal_column = None;
+ }
+ }
+ self.update_selections(selections, true, ctx);
+ }
+
pub fn move_to_next_word_boundary(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
let app = ctx.as_ref();
let mut selections = self.selections(app).to_vec();
@@ -85,7 +85,31 @@ pub fn prev_word_boundary(
point: DisplayPoint,
app: &AppContext,
) -> Result<DisplayPoint> {
- todo!()
+ if point.column() == 0 {
+ if point.row() == 0 {
+ Ok(DisplayPoint::new(0, 0))
+ } else {
+ let row = point.row() - 1;
+ Ok(DisplayPoint::new(row, map.line_len(row, app)?))
+ }
+ } else {
+ let mut boundary = DisplayPoint::new(point.row(), 0);
+ let mut column = 0;
+ let mut prev_c = None;
+ for c in map.chars_at(boundary, app)? {
+ if column >= point.column() {
+ break;
+ }
+
+ if prev_c.is_none() || char_kind(prev_c.unwrap()) != char_kind(c) {
+ *boundary.column_mut() = column;
+ }
+
+ prev_c = Some(c);
+ column += 1;
+ }
+ Ok(boundary)
+ }
}
pub fn next_word_boundary(
@@ -95,7 +119,7 @@ pub fn next_word_boundary(
) -> Result<DisplayPoint> {
let mut prev_c = None;
for c in map.chars_at(point, app)? {
- if prev_c.is_some() && (c == '\n' || is_word_char(prev_c.unwrap()) != is_word_char(c)) {
+ if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) {
break;
}
@@ -110,11 +134,19 @@ pub fn next_word_boundary(
Ok(point)
}
-fn is_word_char(c: char) -> bool {
- match c {
- '/' | '\\' | '(' | ')' | '"' | '\'' | ':' | ',' | '.' | ';' | '<' | '>' | '~' | '!'
- | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '|' | '+' | '=' | '[' | ']' | '{' | '}'
- | '`' | '?' | '-' | '…' | ' ' | '\n' => false,
- _ => true,
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum CharKind {
+ Whitespace,
+ Punctuation,
+ Word,
+}
+
+fn char_kind(c: char) -> CharKind {
+ if c.is_whitespace() {
+ CharKind::Whitespace
+ } else if c.is_alphanumeric() || c == '_' {
+ CharKind::Word
+ } else {
+ CharKind::Punctuation
}
}