From bcf38e6bb561a45d6ea2626f12092d76a843d23e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 18:50:17 +0100 Subject: [PATCH] Implement word-wise mouse selection Co-Authored-By: Nathan Sobo --- crates/editor/src/element.rs | 16 ++--- crates/editor/src/lib.rs | 99 +++++++++++++++++---------- crates/editor/src/movement.rs | 8 +++ crates/gpui/src/app.rs | 1 + crates/gpui/src/platform/event.rs | 2 +- crates/gpui/src/platform/mac/event.rs | 2 +- 6 files changed, 77 insertions(+), 51 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0c0bfb0d25..741519e5b0 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,6 +1,6 @@ use super::{ DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, - Select, SelectMode, SelectPhase, Snapshot, MAX_LINE_LEN, + Select, SelectPhase, Snapshot, MAX_LINE_LEN, }; use clock::ReplicaId; use gpui::{ @@ -56,7 +56,7 @@ impl EditorElement { &self, position: Vector2F, cmd: bool, - count: usize, + click_count: usize, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, @@ -64,16 +64,10 @@ impl EditorElement { if paint.text_bounds.contains_point(position) { let snapshot = self.snapshot(cx.app); let position = paint.point_for_position(&snapshot, layout, position); - let mode = match count { - 1 => SelectMode::Character, - 2 => SelectMode::Word, - 3 => SelectMode::Line, - _ => SelectMode::All, - }; cx.dispatch_action(Select(SelectPhase::Begin { position, add: cmd, - mode, + click_count, })); true } else { @@ -855,8 +849,8 @@ impl Element for EditorElement { Event::LeftMouseDown { position, cmd, - count, - } => self.mouse_down(*position, *cmd, *count, layout, paint, cx), + click_count, + } => self.mouse_down(*position, *cmd, *click_count, layout, paint, cx), Event::LeftMouseUp { position } => self.mouse_up(*position, cx), Event::LeftMouseDragged { position } => { self.mouse_dragged(*position, layout, paint, cx) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 55f46c5d4a..7570252831 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -274,7 +274,7 @@ pub enum SelectPhase { Begin { position: DisplayPoint, add: bool, - mode: SelectMode, + click_count: usize, }, Update { position: DisplayPoint, @@ -283,11 +283,11 @@ pub enum SelectPhase { End, } -#[derive(Copy, Clone, Debug)] -pub enum SelectMode { +#[derive(Clone, Debug)] +enum SelectMode { Character, - Word, - Line, + Word(Range), + Line(Range), All, } @@ -644,8 +644,8 @@ impl Editor { SelectPhase::Begin { position, add, - mode, - } => self.begin_selection(*position, *add, *mode, cx), + click_count, + } => self.begin_selection(*position, *add, *click_count, cx), SelectPhase::Update { position, scroll_position, @@ -658,7 +658,7 @@ impl Editor { &mut self, position: DisplayPoint, add: bool, - mode: SelectMode, + click_count: usize, cx: &mut ViewContext, ) { if !self.focused { @@ -670,20 +670,24 @@ impl Editor { let buffer = self.buffer.read(cx); let start; let end; - match mode { - SelectMode::Character => { + let mode; + match click_count { + 1 => { start = buffer.anchor_before(position.to_point(&display_map)); end = start.clone(); + mode = SelectMode::Character; } - SelectMode::Word => { + 2 => { let range = movement::surrounding_word(&display_map, position); start = buffer.anchor_before(range.start.to_point(&display_map)); end = buffer.anchor_before(range.end.to_point(&display_map)); + mode = SelectMode::Word(start.clone()..end.clone()); } - SelectMode::Line => todo!(), - SelectMode::All => { + 3 => todo!(), + _ => { start = buffer.anchor_before(0); end = buffer.anchor_before(buffer.len()); + mode = SelectMode::All; } } let selection = Selection { @@ -711,32 +715,51 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() { let buffer = self.buffer.read(cx); - let cursor = match mode { - SelectMode::Character => buffer.anchor_before(position.to_point(&display_map)), - SelectMode::Word => { - let word_range = movement::surrounding_word(&display_map, position); - if word_range.start < selection.start.to_display_point(&display_map) { - buffer.anchor_before(word_range.start.to_point(&display_map)) + let head; + let tail; + match mode { + SelectMode::Character => { + head = position.to_point(&display_map); + tail = selection.tail().to_point(buffer); + } + SelectMode::Word(original_range) => { + let original_display_range = original_range.start.to_display_point(&display_map) + ..original_range.end.to_display_point(&display_map); + let original_buffer_range = original_display_range.start.to_point(&display_map) + ..original_display_range.end.to_point(&display_map); + if movement::is_inside_word(&display_map, position) + || original_display_range.contains(&position) + { + let word_range = movement::surrounding_word(&display_map, position); + if word_range.start < original_display_range.start { + head = word_range.start.to_point(&display_map); + } else { + head = word_range.end.to_point(&display_map); + } } else { - buffer.anchor_before(word_range.end.to_point(&display_map)) + head = position.to_point(&display_map); + } + + if head <= original_buffer_range.start { + tail = original_buffer_range.end; + } else { + tail = original_buffer_range.start; } } - SelectMode::Line => todo!(), - SelectMode::All => selection.head(), + SelectMode::Line(_) => todo!(), + SelectMode::All => { + return; + } }; - if cursor.cmp(&selection.tail(), buffer).unwrap() < Ordering::Equal { - if !selection.reversed { - selection.end = selection.start.clone(); - selection.reversed = true; - } - selection.start = cursor; + if head < tail { + selection.start = buffer.anchor_before(head); + selection.end = buffer.anchor_before(tail); + selection.reversed = true; } else { - if selection.reversed { - selection.start = selection.end.clone(); - selection.reversed = false; - } - selection.end = cursor; + selection.start = buffer.anchor_before(tail); + selection.end = buffer.anchor_before(head); + selection.reversed = false; } } else { log::error!("update_selection dispatched with no pending selection"); @@ -3198,7 +3221,7 @@ mod tests { cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); editor.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(2, 2), false, SelectMode::Character, cx); + view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); }); assert_eq!( @@ -3235,7 +3258,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(3, 3), true, SelectMode::Character, cx); + view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); }); @@ -3264,7 +3287,7 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(2, 2), false, SelectMode::Character, cx); + view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] @@ -3296,11 +3319,11 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(3, 4), false, SelectMode::Character, cx); + view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); view.end_selection(cx); - view.begin_selection(DisplayPoint::new(0, 1), true, SelectMode::Character, cx); + view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx); view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx); view.end_selection(cx); assert_eq!( diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 083b828536..438846258c 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -181,6 +181,14 @@ pub fn next_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> point } +pub fn is_inside_word(map: &DisplayMapSnapshot, point: DisplayPoint) -> bool { + let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); + let text = map.buffer_snapshot.text(); + let next_char_kind = text.chars_at(ix).next().map(char_kind); + let prev_char_kind = text.reversed_chars_at(ix).next().map(char_kind); + prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word)) +} + pub fn surrounding_word(map: &DisplayMapSnapshot, point: DisplayPoint) -> Range { let mut start = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); let mut end = start; diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7692b2586a..b05934fdcb 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3651,6 +3651,7 @@ mod tests { Event::LeftMouseDown { position: Default::default(), cmd: false, + click_count: 1, }, cx, ); diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 2830bee5b2..1da8fc151f 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -15,7 +15,7 @@ pub enum Event { LeftMouseDown { position: Vector2F, cmd: bool, - count: usize, + click_count: usize, }, LeftMouseUp { position: Vector2F, diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 8a782c5ed6..fcac34111d 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -94,7 +94,7 @@ impl Event { cmd: native_event .modifierFlags() .contains(NSEventModifierFlags::NSCommandKeyMask), - count: native_event.clickCount() as usize, + click_count: native_event.clickCount() as usize, }) } NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {