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/element.rs b/crates/editor/src/element.rs index 8927f9efdd..87b8001b38 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -55,19 +55,36 @@ impl EditorElement { fn mouse_down( &self, position: Vector2F, - cmd: bool, + alt: bool, + shift: bool, + mut click_count: usize, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, ) -> bool { - 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 })); - true - } else { - false + 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; } + + let snapshot = self.snapshot(cx.app); + let position = paint.point_for_position(&snapshot, layout, position); + + if shift { + cx.dispatch_action(Select(SelectPhase::Extend { + position, + click_count, + })); + } else { + cx.dispatch_action(Select(SelectPhase::Begin { + position, + add: alt, + click_count, + })); + } + + true } fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool { @@ -824,6 +841,7 @@ impl Element for EditorElement { Some(PaintState { bounds, + gutter_bounds, text_bounds, }) } else { @@ -841,9 +859,13 @@ 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, + alt, + shift, + click_count, + .. + } => 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) @@ -948,6 +970,7 @@ impl LayoutState { pub struct PaintState { bounds: RectF, + gutter_bounds: RectF, text_bounds: RectF, } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 84e87172ad..0c84de1d96 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}, @@ -280,6 +280,11 @@ pub enum SelectPhase { Begin { position: DisplayPoint, add: bool, + click_count: usize, + }, + Extend { + position: DisplayPoint, + click_count: usize, }, Update { position: DisplayPoint, @@ -288,6 +293,14 @@ pub enum SelectPhase { End, } +#[derive(Clone, Debug)] +enum SelectMode { + Character, + Word(Range), + Line(Range), + All, +} + #[derive(Copy, Clone, PartialEq, Eq)] pub enum EditorMode { SingleLine, @@ -306,7 +319,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, @@ -333,6 +346,11 @@ pub struct Snapshot { scroll_top_anchor: Anchor, } +struct PendingSelection { + selection: Selection, + mode: SelectMode, +} + struct AddSelectionsState { above: bool, stack: Vec, @@ -633,7 +651,15 @@ 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, + click_count, + } => 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, @@ -642,7 +668,44 @@ impl Editor { } } - fn begin_selection(&mut self, position: DisplayPoint, add: bool, cx: &mut ViewContext) { + 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, + add: bool, + click_count: usize, + cx: &mut ViewContext, + ) { if !self.focused { cx.focus_self(); cx.emit(Event::Activate); @@ -650,19 +713,63 @@ 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; + let mode; + match click_count { + 1 => { + start = buffer.anchor_before(position.to_point(&display_map)); + end = start.clone(); + 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)); + mode = SelectMode::Word(start.clone()..end.clone()); + } + 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()); + mode = SelectMode::All; + } + } + let selection = Selection { id: post_inc(&mut self.next_selection_id), - start: cursor.clone(), - end: cursor, + start, + end, reversed: false, goal: SelectionGoal::None, }; 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(selection); + + self.pending_selection = Some(PendingSelection { selection, mode }); cx.notify(); } @@ -674,21 +781,75 @@ 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 head; + let tail; + match mode { + SelectMode::Character => { + head = position.to_point(&display_map); + tail = selection.tail().to_point(buffer); } - pending_selection.start = cursor; + 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 { + 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(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; + } + }; + + if head < tail { + selection.start = buffer.anchor_before(head); + selection.end = buffer.anchor_before(tail); + selection.reversed = true; } else { - if pending_selection.reversed { - pending_selection.start = pending_selection.end.clone(); - pending_selection.reversed = false; - } - pending_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"); @@ -713,17 +874,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); @@ -1814,8 +1975,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; @@ -1833,8 +1993,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; } @@ -1852,8 +2011,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; } @@ -1872,8 +2031,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; @@ -1891,8 +2049,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; } @@ -1910,8 +2067,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; } @@ -1930,7 +2087,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; @@ -1949,7 +2106,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; } @@ -1973,7 +2130,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; @@ -1989,7 +2146,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; } @@ -2632,9 +2789,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 { @@ -2704,12 +2861,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, }) } @@ -3317,7 +3474,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, 1, cx); }); assert_eq!( @@ -3354,7 +3511,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, 1, cx); view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); }); @@ -3383,7 +3540,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, 1, cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] @@ -3415,11 +3572,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, 1, 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, 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 c6190fec29..b3d9610e02 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 { @@ -99,24 +101,21 @@ 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, -) -> 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,54 @@ pub fn next_word_boundary( } prev_char_kind = Some(char_kind); } - Ok(point) + point } -#[derive(Copy, Clone, Eq, PartialEq)] +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; + + 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 +261,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) + ); + } } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7692b2586a..0e2e313db0 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3650,7 +3650,11 @@ mod tests { presenter.borrow_mut().dispatch_event( Event::LeftMouseDown { position: Default::default(), + ctrl: false, + alt: false, + shift: false, cmd: false, + click_count: 1, }, cx, ); diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index fba7b812e6..d780ff10a8 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -14,7 +14,11 @@ pub enum Event { }, LeftMouseDown { position: Vector2F, + ctrl: bool, + alt: bool, + shift: bool, cmd: bool, + 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 f0153de1bf..8c144e8340 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -88,14 +88,17 @@ 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), + 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, }) } NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {