mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Implement word-wise mouse selection
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
3269b9925f
commit
bcf38e6bb5
@ -1,6 +1,6 @@
|
|||||||
use super::{
|
use super::{
|
||||||
DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
|
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 clock::ReplicaId;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@ -56,7 +56,7 @@ impl EditorElement {
|
|||||||
&self,
|
&self,
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
cmd: bool,
|
cmd: bool,
|
||||||
count: usize,
|
click_count: usize,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
paint: &mut PaintState,
|
paint: &mut PaintState,
|
||||||
cx: &mut EventContext,
|
cx: &mut EventContext,
|
||||||
@ -64,16 +64,10 @@ impl EditorElement {
|
|||||||
if paint.text_bounds.contains_point(position) {
|
if paint.text_bounds.contains_point(position) {
|
||||||
let snapshot = self.snapshot(cx.app);
|
let snapshot = self.snapshot(cx.app);
|
||||||
let position = paint.point_for_position(&snapshot, layout, position);
|
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 {
|
cx.dispatch_action(Select(SelectPhase::Begin {
|
||||||
position,
|
position,
|
||||||
add: cmd,
|
add: cmd,
|
||||||
mode,
|
click_count,
|
||||||
}));
|
}));
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@ -855,8 +849,8 @@ impl Element for EditorElement {
|
|||||||
Event::LeftMouseDown {
|
Event::LeftMouseDown {
|
||||||
position,
|
position,
|
||||||
cmd,
|
cmd,
|
||||||
count,
|
click_count,
|
||||||
} => self.mouse_down(*position, *cmd, *count, layout, paint, cx),
|
} => self.mouse_down(*position, *cmd, *click_count, layout, paint, cx),
|
||||||
Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
|
Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
|
||||||
Event::LeftMouseDragged { position } => {
|
Event::LeftMouseDragged { position } => {
|
||||||
self.mouse_dragged(*position, layout, paint, cx)
|
self.mouse_dragged(*position, layout, paint, cx)
|
||||||
|
@ -274,7 +274,7 @@ pub enum SelectPhase {
|
|||||||
Begin {
|
Begin {
|
||||||
position: DisplayPoint,
|
position: DisplayPoint,
|
||||||
add: bool,
|
add: bool,
|
||||||
mode: SelectMode,
|
click_count: usize,
|
||||||
},
|
},
|
||||||
Update {
|
Update {
|
||||||
position: DisplayPoint,
|
position: DisplayPoint,
|
||||||
@ -283,11 +283,11 @@ pub enum SelectPhase {
|
|||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SelectMode {
|
enum SelectMode {
|
||||||
Character,
|
Character,
|
||||||
Word,
|
Word(Range<Anchor>),
|
||||||
Line,
|
Line(Range<Anchor>),
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,8 +644,8 @@ impl Editor {
|
|||||||
SelectPhase::Begin {
|
SelectPhase::Begin {
|
||||||
position,
|
position,
|
||||||
add,
|
add,
|
||||||
mode,
|
click_count,
|
||||||
} => self.begin_selection(*position, *add, *mode, cx),
|
} => self.begin_selection(*position, *add, *click_count, cx),
|
||||||
SelectPhase::Update {
|
SelectPhase::Update {
|
||||||
position,
|
position,
|
||||||
scroll_position,
|
scroll_position,
|
||||||
@ -658,7 +658,7 @@ impl Editor {
|
|||||||
&mut self,
|
&mut self,
|
||||||
position: DisplayPoint,
|
position: DisplayPoint,
|
||||||
add: bool,
|
add: bool,
|
||||||
mode: SelectMode,
|
click_count: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !self.focused {
|
if !self.focused {
|
||||||
@ -670,20 +670,24 @@ impl Editor {
|
|||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let start;
|
let start;
|
||||||
let end;
|
let end;
|
||||||
match mode {
|
let mode;
|
||||||
SelectMode::Character => {
|
match click_count {
|
||||||
|
1 => {
|
||||||
start = buffer.anchor_before(position.to_point(&display_map));
|
start = buffer.anchor_before(position.to_point(&display_map));
|
||||||
end = start.clone();
|
end = start.clone();
|
||||||
|
mode = SelectMode::Character;
|
||||||
}
|
}
|
||||||
SelectMode::Word => {
|
2 => {
|
||||||
let range = movement::surrounding_word(&display_map, position);
|
let range = movement::surrounding_word(&display_map, position);
|
||||||
start = buffer.anchor_before(range.start.to_point(&display_map));
|
start = buffer.anchor_before(range.start.to_point(&display_map));
|
||||||
end = buffer.anchor_before(range.end.to_point(&display_map));
|
end = buffer.anchor_before(range.end.to_point(&display_map));
|
||||||
|
mode = SelectMode::Word(start.clone()..end.clone());
|
||||||
}
|
}
|
||||||
SelectMode::Line => todo!(),
|
3 => todo!(),
|
||||||
SelectMode::All => {
|
_ => {
|
||||||
start = buffer.anchor_before(0);
|
start = buffer.anchor_before(0);
|
||||||
end = buffer.anchor_before(buffer.len());
|
end = buffer.anchor_before(buffer.len());
|
||||||
|
mode = SelectMode::All;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let selection = Selection {
|
let selection = Selection {
|
||||||
@ -711,32 +715,51 @@ impl Editor {
|
|||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() {
|
if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let cursor = match mode {
|
let head;
|
||||||
SelectMode::Character => buffer.anchor_before(position.to_point(&display_map)),
|
let tail;
|
||||||
SelectMode::Word => {
|
match mode {
|
||||||
let word_range = movement::surrounding_word(&display_map, position);
|
SelectMode::Character => {
|
||||||
if word_range.start < selection.start.to_display_point(&display_map) {
|
head = position.to_point(&display_map);
|
||||||
buffer.anchor_before(word_range.start.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 {
|
} 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::Line(_) => todo!(),
|
||||||
SelectMode::All => selection.head(),
|
SelectMode::All => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if cursor.cmp(&selection.tail(), buffer).unwrap() < Ordering::Equal {
|
if head < tail {
|
||||||
if !selection.reversed {
|
selection.start = buffer.anchor_before(head);
|
||||||
selection.end = selection.start.clone();
|
selection.end = buffer.anchor_before(tail);
|
||||||
selection.reversed = true;
|
selection.reversed = true;
|
||||||
}
|
|
||||||
selection.start = cursor;
|
|
||||||
} else {
|
} else {
|
||||||
if selection.reversed {
|
selection.start = buffer.anchor_before(tail);
|
||||||
selection.start = selection.end.clone();
|
selection.end = buffer.anchor_before(head);
|
||||||
selection.reversed = false;
|
selection.reversed = false;
|
||||||
}
|
|
||||||
selection.end = cursor;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!("update_selection dispatched with no pending selection");
|
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));
|
cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
editor.update(cx, |view, 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!(
|
assert_eq!(
|
||||||
@ -3235,7 +3258,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
editor.update(cx, |view, cx| {
|
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);
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
view.update(cx, |view, 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!(
|
assert_eq!(
|
||||||
view.selection_ranges(cx),
|
view.selection_ranges(cx),
|
||||||
[DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
|
[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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
view.update(cx, |view, 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.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx);
|
||||||
view.end_selection(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.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx);
|
||||||
view.end_selection(cx);
|
view.end_selection(cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -181,6 +181,14 @@ pub fn next_word_boundary(map: &DisplayMapSnapshot, mut point: DisplayPoint) ->
|
|||||||
point
|
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<DisplayPoint> {
|
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 start = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
|
||||||
let mut end = start;
|
let mut end = start;
|
||||||
|
@ -3651,6 +3651,7 @@ mod tests {
|
|||||||
Event::LeftMouseDown {
|
Event::LeftMouseDown {
|
||||||
position: Default::default(),
|
position: Default::default(),
|
||||||
cmd: false,
|
cmd: false,
|
||||||
|
click_count: 1,
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ pub enum Event {
|
|||||||
LeftMouseDown {
|
LeftMouseDown {
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
cmd: bool,
|
cmd: bool,
|
||||||
count: usize,
|
click_count: usize,
|
||||||
},
|
},
|
||||||
LeftMouseUp {
|
LeftMouseUp {
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
|
@ -94,7 +94,7 @@ impl Event {
|
|||||||
cmd: native_event
|
cmd: native_event
|
||||||
.modifierFlags()
|
.modifierFlags()
|
||||||
.contains(NSEventModifierFlags::NSCommandKeyMask),
|
.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 {
|
NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {
|
||||||
|
Loading…
Reference in New Issue
Block a user