Merge pull request #244 from zed-industries/mouse-selections

Improve support for selecting text via the mouse
This commit is contained in:
Nathan Sobo 2021-11-23 18:11:04 -07:00 committed by GitHub
commit e0bf5337ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 401 additions and 102 deletions

View file

@ -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,

View file

@ -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,
}

View file

@ -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<Anchor>),
Line(Range<Anchor>),
All,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum EditorMode {
SingleLine,
@ -306,7 +319,7 @@ pub struct Editor {
buffer: ModelHandle<Buffer>,
display_map: ModelHandle<DisplayMap>,
selection_set_id: SelectionSetId,
pending_selection: Option<Selection<Anchor>>,
pending_selection: Option<PendingSelection>,
next_selection_id: usize,
add_selections_state: Option<AddSelectionsState>,
autoclose_stack: Vec<BracketPairState>,
@ -333,6 +346,11 @@ pub struct Snapshot {
scroll_top_anchor: Anchor,
}
struct PendingSelection {
selection: Selection<Anchor>,
mode: SelectMode,
}
struct AddSelectionsState {
above: bool,
stack: Vec<usize>,
@ -633,7 +651,15 @@ impl Editor {
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
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<Self>) {
fn extend_selection(
&mut self,
position: DisplayPoint,
click_count: usize,
cx: &mut ViewContext<Self>,
) {
let tail = self.newest_selection::<usize>(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<Self>,
) {
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::<usize>(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::<usize>(cx);
self.update_selections::<usize>(
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<Self>,
) {
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<Self>) {
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::<Point>(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::<usize>(cx);
@ -1814,8 +1975,7 @@ impl Editor {
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<Point>(cx).collect::<Vec<_>>();
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::<D, _>(buffer),
end: selection.end.summary::<D, _>(buffer),
reversed: selection.reversed,
goal: selection.goal,
self.pending_selection.as_ref().map(|pending| Selection {
id: pending.selection.id,
start: pending.selection.start.summary::<D, _>(buffer),
end: pending.selection.end.summary::<D, _>(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!(

View file

@ -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<DisplayPoint> {
if point.column() > 0 {
@ -99,24 +101,21 @@ pub fn line_beginning(
map: &DisplayMapSnapshot,
point: DisplayPoint,
toggle_indent: bool,
) -> Result<DisplayPoint> {
) -> 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<DisplayPoint> {
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<DisplayPoint> {
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<DisplayPoint> {
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<DisplayPoint> {
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)
);
}
}

View file

@ -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,
);

View file

@ -14,7 +14,11 @@ pub enum Event {
},
LeftMouseDown {
position: Vector2F,
ctrl: bool,
alt: bool,
shift: bool,
cmd: bool,
click_count: usize,
},
LeftMouseUp {
position: Vector2F,

View file

@ -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 {