From 005a7076af5de3dea3c5f90de68e90044ab8c321 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 16:18:17 +0100 Subject: [PATCH 01/10] Expose a `count` field on `Event::LeftMouseDown` --- crates/gpui/src/platform/event.rs | 1 + crates/gpui/src/platform/mac/event.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index fba7b812e6..2830bee5b2 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -15,6 +15,7 @@ pub enum Event { LeftMouseDown { position: Vector2F, cmd: bool, + count: usize, }, LeftMouseUp { position: Vector2F, diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index f205420dde..8a782c5ed6 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -94,6 +94,7 @@ impl Event { cmd: native_event .modifierFlags() .contains(NSEventModifierFlags::NSCommandKeyMask), + count: native_event.clickCount() as usize, }) } NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp { From a0ea5b38a0f3a35345c4b6f4aa8a64d0c5516a51 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 16:19:11 +0100 Subject: [PATCH 02/10] Add a new `movement::surrounding_word` function --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/movement.rs | 148 ++++++++++++++++++++++++++----- 2 files changed, 125 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e0ead12674..d87c0e13a9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -169,7 +169,7 @@ impl DisplayMap { } pub struct DisplayMapSnapshot { - buffer_snapshot: language::Snapshot, + pub buffer_snapshot: language::Snapshot, folds_snapshot: fold_map::Snapshot, tabs_snapshot: tab_map::Snapshot, wraps_snapshot: wrap_map::Snapshot, diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index c6190fec29..083b828536 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -1,5 +1,7 @@ -use super::{Bias, DisplayMapSnapshot, DisplayPoint, SelectionGoal}; +use super::{Bias, DisplayMapSnapshot, DisplayPoint, SelectionGoal, ToDisplayPoint}; use anyhow::Result; +use buffer::ToPoint; +use std::{cmp, ops::Range}; pub fn left(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> Result { if point.column() > 0 { @@ -113,10 +115,7 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result Result { +pub fn prev_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> DisplayPoint { let mut line_start = 0; if point.row() > 0 { if let Some(indent) = map.soft_wrap_indent(point.row() - 1) { @@ -126,7 +125,7 @@ pub fn prev_word_boundary( if point.column() == line_start { if point.row() == 0 { - return Ok(DisplayPoint::new(0, 0)); + return DisplayPoint::new(0, 0); } else { let row = point.row() - 1; point = map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left); @@ -152,13 +151,10 @@ pub fn prev_word_boundary( prev_char_kind = char_kind; column += c.len_utf8() as u32; } - Ok(boundary) + boundary } -pub fn next_word_boundary( - map: &DisplayMapSnapshot, - mut point: DisplayPoint, -) -> Result { +pub fn next_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> DisplayPoint { let mut prev_char_kind = None; for c in map.chars_at(point) { let char_kind = char_kind(c); @@ -182,14 +178,46 @@ pub fn next_word_boundary( } prev_char_kind = Some(char_kind); } - Ok(point) + point } -#[derive(Copy, Clone, Eq, PartialEq)] +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; + + let text = map.buffer_snapshot.text(); + let mut next_chars = text.chars_at(start).peekable(); + let mut prev_chars = text.reversed_chars_at(start).peekable(); + let word_kind = cmp::max( + prev_chars.peek().copied().map(char_kind), + next_chars.peek().copied().map(char_kind), + ); + + for ch in prev_chars { + if Some(char_kind(ch)) == word_kind { + start -= ch.len_utf8(); + } else { + break; + } + } + + for ch in next_chars { + if Some(char_kind(ch)) == word_kind { + end += ch.len_utf8(); + } else { + break; + } + } + + start.to_point(&map.buffer_snapshot).to_display_point(map) + ..end.to_point(&map.buffer_snapshot).to_display_point(map) +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] enum CharKind { Newline, - Whitespace, Punctuation, + Whitespace, Word, } @@ -225,45 +253,117 @@ mod tests { cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)), DisplayPoint::new(0, 7) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)), DisplayPoint::new(0, 2) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)), DisplayPoint::new(0, 2) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)), DisplayPoint::new(0, 0) ); assert_eq!( - prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), + prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)), DisplayPoint::new(0, 0) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 0)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 0)), DisplayPoint::new(0, 1) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 1)), DisplayPoint::new(0, 6) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 2)), DisplayPoint::new(0, 6) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 6)), DisplayPoint::new(0, 12) ); assert_eq!( - next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(), + next_word_boundary(&snapshot, DisplayPoint::new(0, 7)), DisplayPoint::new(0, 12) ); } + + #[gpui::test] + fn test_surrounding_word(cx: &mut gpui::MutableAppContext) { + let tab_size = 4; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + let buffer = cx.add_model(|cx| Buffer::new(0, "lorem ipsum dolor\n sit", cx)); + let display_map = + cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); + let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); + + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 0)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 2)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 5)), + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 6)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 7)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 11)), + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 13)), + DisplayPoint::new(0, 11)..DisplayPoint::new(0, 14) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 14)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 17)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(0, 19)), + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 0)), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 1)), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 6)), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7) + ); + assert_eq!( + surrounding_word(&snapshot, DisplayPoint::new(1, 7)), + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7) + ); + } } From 3269b9925f892cf9000b935a8580bca48a744464 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 17:04:58 +0100 Subject: [PATCH 03/10] WIP: Start integrating `SelectMode` and `movement::surrounding_word` --- crates/editor/src/element.rs | 23 ++++-- crates/editor/src/lib.rs | 154 +++++++++++++++++++++++------------ 2 files changed, 120 insertions(+), 57 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8927f9efdd..0c0bfb0d25 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, SelectPhase, Snapshot, MAX_LINE_LEN, + Select, SelectMode, SelectPhase, Snapshot, MAX_LINE_LEN, }; use clock::ReplicaId; use gpui::{ @@ -56,6 +56,7 @@ impl EditorElement { &self, position: Vector2F, cmd: bool, + count: usize, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, @@ -63,7 +64,17 @@ impl EditorElement { if paint.text_bounds.contains_point(position) { let snapshot = self.snapshot(cx.app); let position = paint.point_for_position(&snapshot, layout, position); - cx.dispatch_action(Select(SelectPhase::Begin { position, add: cmd })); + 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, + })); true } else { false @@ -841,9 +852,11 @@ impl Element for EditorElement { ) -> bool { if let (Some(layout), Some(paint)) = (layout, paint) { match event { - Event::LeftMouseDown { position, cmd } => { - self.mouse_down(*position, *cmd, layout, paint, cx) - } + Event::LeftMouseDown { + position, + cmd, + count, + } => self.mouse_down(*position, *cmd, *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 b54ea6989d..55f46c5d4a 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -274,6 +274,7 @@ pub enum SelectPhase { Begin { position: DisplayPoint, add: bool, + mode: SelectMode, }, Update { position: DisplayPoint, @@ -282,6 +283,14 @@ pub enum SelectPhase { End, } +#[derive(Copy, Clone, Debug)] +pub enum SelectMode { + Character, + Word, + Line, + All, +} + #[derive(Copy, Clone, PartialEq, Eq)] pub enum EditorMode { SingleLine, @@ -300,7 +309,7 @@ pub struct Editor { buffer: ModelHandle, display_map: ModelHandle, selection_set_id: SelectionSetId, - pending_selection: Option>, + pending_selection: Option, next_selection_id: usize, add_selections_state: Option, autoclose_stack: Vec, @@ -327,6 +336,11 @@ pub struct Snapshot { scroll_top_anchor: Anchor, } +struct PendingSelection { + selection: Selection, + mode: SelectMode, +} + struct AddSelectionsState { above: bool, stack: Vec, @@ -627,7 +641,11 @@ impl Editor { fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext) { match phase { - SelectPhase::Begin { position, add } => self.begin_selection(*position, *add, cx), + SelectPhase::Begin { + position, + add, + mode, + } => self.begin_selection(*position, *add, *mode, cx), SelectPhase::Update { position, scroll_position, @@ -636,7 +654,13 @@ impl Editor { } } - fn begin_selection(&mut self, position: DisplayPoint, add: bool, cx: &mut ViewContext) { + fn begin_selection( + &mut self, + position: DisplayPoint, + add: bool, + mode: SelectMode, + cx: &mut ViewContext, + ) { if !self.focused { cx.focus_self(); cx.emit(Event::Activate); @@ -644,11 +668,28 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); - let cursor = buffer.anchor_before(position.to_point(&display_map)); + let start; + let end; + match mode { + SelectMode::Character => { + start = buffer.anchor_before(position.to_point(&display_map)); + end = start.clone(); + } + SelectMode::Word => { + 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)); + } + SelectMode::Line => todo!(), + SelectMode::All => { + start = buffer.anchor_before(0); + end = buffer.anchor_before(buffer.len()); + } + } let selection = Selection { id: post_inc(&mut self.next_selection_id), - start: cursor.clone(), - end: cursor, + start, + end, reversed: false, goal: SelectionGoal::None, }; @@ -656,7 +697,7 @@ impl Editor { if !add { self.update_selections::(Vec::new(), false, cx); } - self.pending_selection = Some(selection); + self.pending_selection = Some(PendingSelection { selection, mode }); cx.notify(); } @@ -668,21 +709,34 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - if let Some(pending_selection) = self.pending_selection.as_mut() { + if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() { let buffer = self.buffer.read(cx); - let cursor = buffer.anchor_before(position.to_point(&display_map)); - if cursor.cmp(&pending_selection.tail(), buffer).unwrap() < Ordering::Equal { - if !pending_selection.reversed { - pending_selection.end = pending_selection.start.clone(); - pending_selection.reversed = true; + 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)) + } else { + buffer.anchor_before(word_range.end.to_point(&display_map)) + } } - pending_selection.start = cursor; + SelectMode::Line => todo!(), + SelectMode::All => selection.head(), + }; + + if cursor.cmp(&selection.tail(), buffer).unwrap() < Ordering::Equal { + if !selection.reversed { + selection.end = selection.start.clone(); + selection.reversed = true; + } + selection.start = cursor; } else { - if pending_selection.reversed { - pending_selection.start = pending_selection.end.clone(); - pending_selection.reversed = false; + if selection.reversed { + selection.start = selection.end.clone(); + selection.reversed = false; } - pending_selection.end = cursor; + selection.end = cursor; } } else { log::error!("update_selection dispatched with no pending selection"); @@ -707,17 +761,17 @@ impl Editor { pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { if self.active_diagnostics.is_some() { self.dismiss_diagnostics(cx); - } else if let Some(pending_selection) = self.pending_selection.take() { + } else if let Some(PendingSelection { selection, .. }) = self.pending_selection.take() { let buffer = self.buffer.read(cx); - let pending_selection = Selection { - id: pending_selection.id, - start: pending_selection.start.to_point(buffer), - end: pending_selection.end.to_point(buffer), - reversed: pending_selection.reversed, - goal: pending_selection.goal, + let selection = Selection { + id: selection.id, + start: selection.start.to_point(buffer), + end: selection.end.to_point(buffer), + reversed: selection.reversed, + goal: selection.goal, }; if self.selections::(cx).next().is_none() { - self.update_selections(vec![pending_selection], true, cx); + self.update_selections(vec![selection], true, cx); } } else { let mut oldest_selection = self.oldest_selection::(cx); @@ -1735,8 +1789,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = movement::prev_word_boundary(&display_map, head).to_point(&display_map); selection.start = cursor.clone(); selection.end = cursor; selection.reversed = false; @@ -1754,8 +1807,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = movement::prev_word_boundary(&display_map, head).to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1773,8 +1825,8 @@ impl Editor { for selection in &mut selections { if selection.is_empty() { let head = selection.head().to_display_point(&display_map); - let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = + movement::prev_word_boundary(&display_map, head).to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1793,8 +1845,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = movement::next_word_boundary(&display_map, head).to_point(&display_map); selection.start = cursor; selection.end = cursor; selection.reversed = false; @@ -1812,8 +1863,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = movement::next_word_boundary(&display_map, head).to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1831,8 +1881,8 @@ impl Editor { for selection in &mut selections { if selection.is_empty() { let head = selection.head().to_display_point(&display_map); - let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_point(&display_map); + let cursor = + movement::next_word_boundary(&display_map, head).to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -2463,9 +2513,9 @@ impl Editor { let start_index = self.selection_insertion_index(&selections, start); let pending_selection = if set_id == self.selection_set_id { self.pending_selection.as_ref().and_then(|pending| { - let mut selection_start = pending.start.to_display_point(&display_map); - let mut selection_end = pending.end.to_display_point(&display_map); - if pending.reversed { + let mut selection_start = pending.selection.start.to_display_point(&display_map); + let mut selection_end = pending.selection.end.to_display_point(&display_map); + if pending.selection.reversed { mem::swap(&mut selection_start, &mut selection_end); } if selection_start <= range.end || selection_end <= range.end { @@ -2535,12 +2585,12 @@ impl Editor { D: 'a + TextDimension<'a>, { let buffer = self.buffer.read(cx); - self.pending_selection.as_ref().map(|selection| Selection { - id: selection.id, - start: selection.start.summary::(buffer), - end: selection.end.summary::(buffer), - reversed: selection.reversed, - goal: selection.goal, + self.pending_selection.as_ref().map(|pending| Selection { + id: pending.selection.id, + start: pending.selection.start.summary::(buffer), + end: pending.selection.end.summary::(buffer), + reversed: pending.selection.reversed, + goal: pending.selection.goal, }) } @@ -3148,7 +3198,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, cx); + view.begin_selection(DisplayPoint::new(2, 2), false, SelectMode::Character, cx); }); assert_eq!( @@ -3185,7 +3235,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(3, 3), true, cx); + view.begin_selection(DisplayPoint::new(3, 3), true, SelectMode::Character, cx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); }); @@ -3214,7 +3264,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, cx); + view.begin_selection(DisplayPoint::new(2, 2), false, SelectMode::Character, cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] @@ -3246,11 +3296,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, cx); + view.begin_selection(DisplayPoint::new(3, 4), false, SelectMode::Character, cx); view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); view.end_selection(cx); - view.begin_selection(DisplayPoint::new(0, 1), true, cx); + view.begin_selection(DisplayPoint::new(0, 1), true, SelectMode::Character, cx); view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx); view.end_selection(cx); assert_eq!( From bcf38e6bb561a45d6ea2626f12092d76a843d23e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 18:50:17 +0100 Subject: [PATCH 04/10] 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 { From 7a79df7a24520bd59f3daf9c5793b360a7e1a540 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 19:10:15 +0100 Subject: [PATCH 05/10] Implement line-wise selection --- crates/editor/src/lib.rs | 47 +++++++++++++++++++++++++++++------ crates/editor/src/movement.rs | 10 ++++---- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 7570252831..159ac76be2 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; use smol::Timer; use std::{ cell::RefCell, - cmp::{self, Ordering}, + cmp, collections::HashMap, iter, mem, ops::{Range, RangeInclusive}, @@ -683,7 +683,18 @@ impl Editor { end = buffer.anchor_before(range.end.to_point(&display_map)); mode = SelectMode::Word(start.clone()..end.clone()); } - 3 => todo!(), + 3 => { + let position = display_map.clip_point(position, Bias::Left); + let line_start = movement::line_beginning(&display_map, position, false); + let mut next_line_start = line_start.clone(); + *next_line_start.row_mut() += 1; + *next_line_start.column_mut() = 0; + next_line_start = display_map.clip_point(next_line_start, Bias::Right); + + start = buffer.anchor_before(line_start.to_point(&display_map)); + end = buffer.anchor_before(next_line_start.to_point(&display_map)); + mode = SelectMode::Line(start.clone()..end.clone()); + } _ => { start = buffer.anchor_before(0); end = buffer.anchor_before(buffer.len()); @@ -746,7 +757,29 @@ impl Editor { tail = original_buffer_range.start; } } - SelectMode::Line(_) => todo!(), + SelectMode::Line(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); + let line_start = movement::line_beginning(&display_map, position, false); + let mut next_line_start = line_start.clone(); + *next_line_start.row_mut() += 1; + *next_line_start.column_mut() = 0; + next_line_start = display_map.clip_point(next_line_start, Bias::Right); + + if line_start < original_display_range.start { + head = line_start.to_point(&display_map); + } else { + head = next_line_start.to_point(&display_map); + } + + if head <= original_buffer_range.start { + tail = original_buffer_range.end; + } else { + tail = original_buffer_range.start; + } + } SelectMode::All => { return; } @@ -1924,7 +1957,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::line_beginning(&display_map, head, true).unwrap(); + let new_head = movement::line_beginning(&display_map, head, true); let cursor = new_head.to_point(&display_map); selection.start = cursor; selection.end = cursor; @@ -1943,7 +1976,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap(); + let new_head = movement::line_beginning(&display_map, head, *toggle_indent); selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } @@ -1967,7 +2000,7 @@ impl Editor { { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::line_end(&display_map, head).unwrap(); + let new_head = movement::line_end(&display_map, head); let anchor = new_head.to_point(&display_map); selection.start = anchor.clone(); selection.end = anchor; @@ -1983,7 +2016,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map); - let new_head = movement::line_end(&display_map, head).unwrap(); + let new_head = movement::line_end(&display_map, head); selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 438846258c..b3d9610e02 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -101,18 +101,18 @@ pub fn line_beginning( map: &DisplayMapSnapshot, point: DisplayPoint, toggle_indent: bool, -) -> Result { +) -> DisplayPoint { let (indent, is_blank) = map.line_indent(point.row()); if toggle_indent && !is_blank && point.column() != indent { - Ok(DisplayPoint::new(point.row(), indent)) + DisplayPoint::new(point.row(), indent) } else { - Ok(DisplayPoint::new(point.row(), 0)) + DisplayPoint::new(point.row(), 0) } } -pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result { +pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> DisplayPoint { let line_end = DisplayPoint::new(point.row(), map.line_len(point.row())); - Ok(map.clip_point(line_end, Bias::Left)) + map.clip_point(line_end, Bias::Left) } pub fn prev_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) -> DisplayPoint { From 63089badf1464ef5433e7061a6eba371889dfd6d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 23 Nov 2021 19:14:39 +0100 Subject: [PATCH 06/10] Simulate line-wise selection when clicking on the gutter --- crates/editor/src/element.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 741519e5b0..0eef242bd7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -70,6 +70,15 @@ impl EditorElement { click_count, })); true + } else if paint.gutter_bounds.contains_point(position) { + let snapshot = self.snapshot(cx.app); + let position = paint.point_for_position(&snapshot, layout, position); + cx.dispatch_action(Select(SelectPhase::Begin { + position, + add: cmd, + click_count: 3, + })); + true } else { false } @@ -829,6 +838,7 @@ impl Element for EditorElement { Some(PaintState { bounds, + gutter_bounds, text_bounds, }) } else { @@ -955,6 +965,7 @@ impl LayoutState { pub struct PaintState { bounds: RectF, + gutter_bounds: RectF, text_bounds: RectF, } From d969f38850ba623612349fe1d289b558969b9eaa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Nov 2021 15:42:21 -0700 Subject: [PATCH 07/10] Implement shift-click to extend the newest selection --- crates/editor/src/element.rs | 18 +++++- crates/editor/src/lib.rs | 81 +++++++++++++++++++-------- crates/gpui/src/app.rs | 1 + crates/gpui/src/platform/event.rs | 1 + crates/gpui/src/platform/mac/event.rs | 6 +- 5 files changed, 77 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0eef242bd7..77def82c51 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,3 +1,5 @@ +use crate::BeginSelectionMode; + use super::{ DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, SelectPhase, Snapshot, MAX_LINE_LEN, @@ -55,18 +57,27 @@ impl EditorElement { fn mouse_down( &self, position: Vector2F, + shift: bool, cmd: bool, click_count: usize, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, ) -> bool { + let mode = if cmd { + BeginSelectionMode::Add + } else if shift { + BeginSelectionMode::Extend + } else { + BeginSelectionMode::Update + }; + if paint.text_bounds.contains_point(position) { let snapshot = self.snapshot(cx.app); let position = paint.point_for_position(&snapshot, layout, position); cx.dispatch_action(Select(SelectPhase::Begin { position, - add: cmd, + mode, click_count, })); true @@ -75,7 +86,7 @@ impl EditorElement { let position = paint.point_for_position(&snapshot, layout, position); cx.dispatch_action(Select(SelectPhase::Begin { position, - add: cmd, + mode, click_count: 3, })); true @@ -858,9 +869,10 @@ impl Element for EditorElement { match event { Event::LeftMouseDown { position, + shift, cmd, click_count, - } => self.mouse_down(*position, *cmd, *click_count, layout, paint, cx), + } => self.mouse_down(*position, *shift, *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 159ac76be2..1252234b05 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -273,7 +273,7 @@ struct SpannedRows { pub enum SelectPhase { Begin { position: DisplayPoint, - add: bool, + mode: BeginSelectionMode, click_count: usize, }, Update { @@ -283,8 +283,15 @@ pub enum SelectPhase { End, } +#[derive(Clone, Copy, Debug)] +pub enum BeginSelectionMode { + Update, + Extend, + Add, +} + #[derive(Clone, Debug)] -enum SelectMode { +enum ExtendSelectionMode { Character, Word(Range), Line(Range), @@ -338,7 +345,7 @@ pub struct Snapshot { struct PendingSelection { selection: Selection, - mode: SelectMode, + mode: ExtendSelectionMode, } struct AddSelectionsState { @@ -643,9 +650,9 @@ impl Editor { match phase { SelectPhase::Begin { position, - add, + mode, click_count, - } => self.begin_selection(*position, *add, *click_count, cx), + } => self.begin_selection(*position, *mode, *click_count, cx), SelectPhase::Update { position, scroll_position, @@ -657,7 +664,7 @@ impl Editor { fn begin_selection( &mut self, position: DisplayPoint, - add: bool, + begin_mode: BeginSelectionMode, click_count: usize, cx: &mut ViewContext, ) { @@ -670,18 +677,18 @@ impl Editor { let buffer = self.buffer.read(cx); let start; let end; - let mode; + let mut extend_mode; match click_count { 1 => { start = buffer.anchor_before(position.to_point(&display_map)); end = start.clone(); - mode = SelectMode::Character; + extend_mode = ExtendSelectionMode::Character; } 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()); + extend_mode = ExtendSelectionMode::Word(start.clone()..end.clone()); } 3 => { let position = display_map.clip_point(position, Bias::Left); @@ -693,15 +700,16 @@ impl Editor { start = buffer.anchor_before(line_start.to_point(&display_map)); end = buffer.anchor_before(next_line_start.to_point(&display_map)); - mode = SelectMode::Line(start.clone()..end.clone()); + extend_mode = ExtendSelectionMode::Line(start.clone()..end.clone()); } _ => { start = buffer.anchor_before(0); end = buffer.anchor_before(buffer.len()); - mode = SelectMode::All; + extend_mode = ExtendSelectionMode::All; } } - let selection = Selection { + + let mut selection = Selection { id: post_inc(&mut self.next_selection_id), start, end, @@ -709,10 +717,35 @@ impl Editor { goal: SelectionGoal::None, }; - if !add { - self.update_selections::(Vec::new(), false, cx); + match begin_mode { + BeginSelectionMode::Update => { + self.update_selections::(Vec::new(), false, cx); + } + BeginSelectionMode::Extend => { + let position = position.to_offset(&display_map, Bias::Left); + let tail = self.newest_selection::(cx).tail(); + let tail_anchor = buffer.anchor_before(tail); + if position >= tail { + selection.start = tail_anchor.clone(); + } else { + selection.end = tail_anchor.clone(); + selection.reversed = true; + } + match &mut extend_mode { + ExtendSelectionMode::Word(range) | ExtendSelectionMode::Line(range) => { + *range = tail_anchor.clone()..tail_anchor + } + _ => {} + } + self.update_selections::(Vec::new(), false, cx); + } + BeginSelectionMode::Add => {} } - self.pending_selection = Some(PendingSelection { selection, mode }); + + self.pending_selection = Some(PendingSelection { + selection, + mode: extend_mode, + }); cx.notify(); } @@ -729,11 +762,11 @@ impl Editor { let head; let tail; match mode { - SelectMode::Character => { + ExtendSelectionMode::Character => { head = position.to_point(&display_map); tail = selection.tail().to_point(buffer); } - SelectMode::Word(original_range) => { + ExtendSelectionMode::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) @@ -757,7 +790,7 @@ impl Editor { tail = original_buffer_range.start; } } - SelectMode::Line(original_range) => { + ExtendSelectionMode::Line(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) @@ -780,7 +813,7 @@ impl Editor { tail = original_buffer_range.start; } } - SelectMode::All => { + ExtendSelectionMode::All => { return; } }; @@ -3254,7 +3287,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, 1, cx); + view.begin_selection(DisplayPoint::new(2, 2), BeginSelectionMode::Update, 1, cx); }); assert_eq!( @@ -3291,7 +3324,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx); + view.begin_selection(DisplayPoint::new(3, 3), BeginSelectionMode::Add, 1, cx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); }); @@ -3320,7 +3353,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, 1, cx); + view.begin_selection(DisplayPoint::new(2, 2), BeginSelectionMode::Update, 1, cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] @@ -3352,11 +3385,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, 1, cx); + view.begin_selection(DisplayPoint::new(3, 4), BeginSelectionMode::Update, 1, cx); view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); view.end_selection(cx); - view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx); + view.begin_selection(DisplayPoint::new(0, 1), BeginSelectionMode::Add, 1, cx); view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx); view.end_selection(cx); assert_eq!( diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b05934fdcb..e34ed6877d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3650,6 +3650,7 @@ mod tests { presenter.borrow_mut().dispatch_event( Event::LeftMouseDown { position: Default::default(), + shift: false, cmd: false, click_count: 1, }, diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 1da8fc151f..08f84765bc 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -15,6 +15,7 @@ pub enum Event { LeftMouseDown { position: Vector2F, cmd: bool, + shift: bool, click_count: usize, }, LeftMouseUp { diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index fcac34111d..4e25dd7de7 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -86,14 +86,14 @@ impl Event { }) } NSEventType::NSLeftMouseDown => { + let modifiers = native_event.modifierFlags(); window_height.map(|window_height| Self::LeftMouseDown { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - cmd: native_event - .modifierFlags() - .contains(NSEventModifierFlags::NSCommandKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, }) } From 9e651ee1271f1015bcb73e67ea9defc5fc8d54ae Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Nov 2021 16:03:21 -0700 Subject: [PATCH 08/10] Simplify handling of shift-click to extend selections --- crates/editor/src/element.rs | 40 +++++------- crates/editor/src/lib.rs | 118 +++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 77def82c51..c5e1dd797f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,5 +1,3 @@ -use crate::BeginSelectionMode; - use super::{ DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, SelectPhase, Snapshot, MAX_LINE_LEN, @@ -59,40 +57,34 @@ impl EditorElement { position: Vector2F, shift: bool, cmd: bool, - click_count: usize, + mut click_count: usize, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, ) -> bool { - let mode = if cmd { - BeginSelectionMode::Add - } else if shift { - BeginSelectionMode::Extend - } else { - BeginSelectionMode::Update - }; + if paint.gutter_bounds.contains_point(position) { + click_count = 3; // Simulate triple-click when clicking the gutter to select lines + } else if !paint.text_bounds.contains_point(position) { + return false; + } - if paint.text_bounds.contains_point(position) { - let snapshot = self.snapshot(cx.app); - let position = paint.point_for_position(&snapshot, layout, position); - cx.dispatch_action(Select(SelectPhase::Begin { + let snapshot = self.snapshot(cx.app); + let position = paint.point_for_position(&snapshot, layout, position); + + if shift { + cx.dispatch_action(Select(SelectPhase::Extend { position, - mode, click_count, })); - true - } else if paint.gutter_bounds.contains_point(position) { - let snapshot = self.snapshot(cx.app); - let position = paint.point_for_position(&snapshot, layout, position); + } else { cx.dispatch_action(Select(SelectPhase::Begin { position, - mode, - click_count: 3, + add: cmd, + click_count, })); - true - } else { - false } + + true } fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool { diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 1252234b05..020d7593cf 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -273,7 +273,11 @@ struct SpannedRows { pub enum SelectPhase { Begin { position: DisplayPoint, - mode: BeginSelectionMode, + add: bool, + click_count: usize, + }, + Extend { + position: DisplayPoint, click_count: usize, }, Update { @@ -283,15 +287,8 @@ pub enum SelectPhase { End, } -#[derive(Clone, Copy, Debug)] -pub enum BeginSelectionMode { - Update, - Extend, - Add, -} - #[derive(Clone, Debug)] -enum ExtendSelectionMode { +enum SelectMode { Character, Word(Range), Line(Range), @@ -345,7 +342,7 @@ pub struct Snapshot { struct PendingSelection { selection: Selection, - mode: ExtendSelectionMode, + mode: SelectMode, } struct AddSelectionsState { @@ -650,9 +647,13 @@ impl Editor { match phase { SelectPhase::Begin { position, - mode, + add, click_count, - } => self.begin_selection(*position, *mode, *click_count, cx), + } => self.begin_selection(*position, *add, *click_count, cx), + SelectPhase::Extend { + position, + click_count, + } => self.extend_selection(*position, *click_count, cx), SelectPhase::Update { position, scroll_position, @@ -661,10 +662,41 @@ impl Editor { } } + fn extend_selection( + &mut self, + position: DisplayPoint, + click_count: usize, + cx: &mut ViewContext, + ) { + let tail = self.newest_selection::(cx).tail(); + + self.begin_selection(position, false, click_count, cx); + + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx); + let position = position.to_offset(&display_map, Bias::Left); + let tail_anchor = buffer.anchor_before(tail); + let pending = self.pending_selection.as_mut().unwrap(); + + if position >= tail { + pending.selection.start = tail_anchor.clone(); + } else { + pending.selection.end = tail_anchor.clone(); + pending.selection.reversed = true; + } + + match &mut pending.mode { + SelectMode::Word(range) | SelectMode::Line(range) => { + *range = tail_anchor.clone()..tail_anchor + } + _ => {} + } + } + fn begin_selection( &mut self, position: DisplayPoint, - begin_mode: BeginSelectionMode, + add: bool, click_count: usize, cx: &mut ViewContext, ) { @@ -677,18 +709,18 @@ impl Editor { let buffer = self.buffer.read(cx); let start; let end; - let mut extend_mode; + let mode; match click_count { 1 => { start = buffer.anchor_before(position.to_point(&display_map)); end = start.clone(); - extend_mode = ExtendSelectionMode::Character; + mode = SelectMode::Character; } 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)); - extend_mode = ExtendSelectionMode::Word(start.clone()..end.clone()); + mode = SelectMode::Word(start.clone()..end.clone()); } 3 => { let position = display_map.clip_point(position, Bias::Left); @@ -700,16 +732,16 @@ impl Editor { start = buffer.anchor_before(line_start.to_point(&display_map)); end = buffer.anchor_before(next_line_start.to_point(&display_map)); - extend_mode = ExtendSelectionMode::Line(start.clone()..end.clone()); + mode = SelectMode::Line(start.clone()..end.clone()); } _ => { start = buffer.anchor_before(0); end = buffer.anchor_before(buffer.len()); - extend_mode = ExtendSelectionMode::All; + mode = SelectMode::All; } } - let mut selection = Selection { + let selection = Selection { id: post_inc(&mut self.next_selection_id), start, end, @@ -717,35 +749,11 @@ impl Editor { goal: SelectionGoal::None, }; - match begin_mode { - BeginSelectionMode::Update => { - self.update_selections::(Vec::new(), false, cx); - } - BeginSelectionMode::Extend => { - let position = position.to_offset(&display_map, Bias::Left); - let tail = self.newest_selection::(cx).tail(); - let tail_anchor = buffer.anchor_before(tail); - if position >= tail { - selection.start = tail_anchor.clone(); - } else { - selection.end = tail_anchor.clone(); - selection.reversed = true; - } - match &mut extend_mode { - ExtendSelectionMode::Word(range) | ExtendSelectionMode::Line(range) => { - *range = tail_anchor.clone()..tail_anchor - } - _ => {} - } - self.update_selections::(Vec::new(), false, cx); - } - BeginSelectionMode::Add => {} + if !add { + self.update_selections::(Vec::new(), false, cx); } - self.pending_selection = Some(PendingSelection { - selection, - mode: extend_mode, - }); + self.pending_selection = Some(PendingSelection { selection, mode }); cx.notify(); } @@ -762,11 +770,11 @@ impl Editor { let head; let tail; match mode { - ExtendSelectionMode::Character => { + SelectMode::Character => { head = position.to_point(&display_map); tail = selection.tail().to_point(buffer); } - ExtendSelectionMode::Word(original_range) => { + 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) @@ -790,7 +798,7 @@ impl Editor { tail = original_buffer_range.start; } } - ExtendSelectionMode::Line(original_range) => { + SelectMode::Line(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) @@ -813,7 +821,7 @@ impl Editor { tail = original_buffer_range.start; } } - ExtendSelectionMode::All => { + SelectMode::All => { return; } }; @@ -3287,7 +3295,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), BeginSelectionMode::Update, 1, cx); + view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); }); assert_eq!( @@ -3324,7 +3332,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.begin_selection(DisplayPoint::new(3, 3), BeginSelectionMode::Add, 1, cx); + view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); }); @@ -3353,7 +3361,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), BeginSelectionMode::Update, 1, 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)] @@ -3385,11 +3393,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), BeginSelectionMode::Update, 1, 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), BeginSelectionMode::Add, 1, 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!( From 73afb29b0491b71cec4254f127192cc8ecb30024 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Nov 2021 16:23:30 -0700 Subject: [PATCH 09/10] Use alt modifier instead of cmd to add selections --- crates/editor/src/element.rs | 9 +++++---- crates/gpui/src/app.rs | 2 ++ crates/gpui/src/platform/event.rs | 4 +++- crates/gpui/src/platform/mac/event.rs | 2 ++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c5e1dd797f..87b8001b38 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -55,8 +55,8 @@ impl EditorElement { fn mouse_down( &self, position: Vector2F, + alt: bool, shift: bool, - cmd: bool, mut click_count: usize, layout: &mut LayoutState, paint: &mut PaintState, @@ -79,7 +79,7 @@ impl EditorElement { } else { cx.dispatch_action(Select(SelectPhase::Begin { position, - add: cmd, + add: alt, click_count, })); } @@ -861,10 +861,11 @@ impl Element for EditorElement { match event { Event::LeftMouseDown { position, + alt, shift, - cmd, click_count, - } => self.mouse_down(*position, *shift, *cmd, *click_count, layout, paint, cx), + .. + } => self.mouse_down(*position, *alt, *shift, *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/gpui/src/app.rs b/crates/gpui/src/app.rs index e34ed6877d..0e2e313db0 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3650,6 +3650,8 @@ mod tests { presenter.borrow_mut().dispatch_event( Event::LeftMouseDown { position: Default::default(), + ctrl: false, + alt: false, shift: false, cmd: false, click_count: 1, diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 08f84765bc..d780ff10a8 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -14,8 +14,10 @@ pub enum Event { }, LeftMouseDown { position: Vector2F, - cmd: bool, + ctrl: bool, + alt: bool, shift: bool, + cmd: bool, click_count: usize, }, LeftMouseUp { diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 4e25dd7de7..6adb85f191 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -92,6 +92,8 @@ impl Event { native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, From bfecdb7bc0e51ed1e37c8d0f61ceb27c5c6b5de9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Nov 2021 16:30:33 -0700 Subject: [PATCH 10/10] Remove newest selection when adding a selection with a click count > 1 This prevents selections added in earlier clicks from being rendered under the pending selection. --- crates/editor/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 020d7593cf..4443d23ad7 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -751,6 +751,16 @@ impl Editor { if !add { self.update_selections::(Vec::new(), false, cx); + } else if click_count > 1 { + // Remove the newest selection since it was only added as part of this multi-click. + let newest_selection = self.newest_selection::(cx); + self.update_selections::( + self.selections(cx) + .filter(|selection| selection.id != newest_selection.id) + .collect(), + false, + cx, + ) } self.pending_selection = Some(PendingSelection { selection, mode });