mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-29 21:49:33 +00:00
Merge pull request #244 from zed-industries/mouse-selections
Improve support for selecting text via the mouse
This commit is contained in:
commit
e0bf5337ca
7 changed files with 401 additions and 102 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -14,7 +14,11 @@ pub enum Event {
|
|||
},
|
||||
LeftMouseDown {
|
||||
position: Vector2F,
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
cmd: bool,
|
||||
click_count: usize,
|
||||
},
|
||||
LeftMouseUp {
|
||||
position: Vector2F,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue