Merge branch 'main' into project-panel-with-new-mouse-events

This commit is contained in:
Antonio Scandurra 2022-05-30 18:29:46 +02:00
commit 20e1044d49
46 changed files with 7318 additions and 7059 deletions

View file

@ -3,10 +3,10 @@ mod element;
pub mod items;
pub mod movement;
mod multi_buffer;
mod selections_collection;
pub mod selections_collection;
#[cfg(test)]
mod test;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use aho_corasick::AhoCorasick;
use anyhow::Result;
@ -850,9 +850,9 @@ struct ActiveDiagnosticGroup {
}
#[derive(Serialize, Deserialize)]
struct ClipboardSelection {
len: usize,
is_entire_line: bool,
pub struct ClipboardSelection {
pub len: usize,
pub is_entire_line: bool,
}
#[derive(Debug)]
@ -1038,6 +1038,10 @@ impl Editor {
self.buffer.read(cx).replica_id()
}
pub fn leader_replica_id(&self) -> Option<ReplicaId> {
self.leader_replica_id
}
pub fn buffer(&self) -> &ModelHandle<MultiBuffer> {
&self.buffer
}
@ -1332,7 +1336,11 @@ impl Editor {
) {
if self.focused && self.leader_replica_id.is_none() {
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(&self.selections.disjoint_anchors(), cx)
buffer.set_active_selections(
&self.selections.disjoint_anchors(),
self.selections.line_mode,
cx,
)
});
}
@ -1406,12 +1414,14 @@ impl Editor {
let old_cursor_position = self.selections.newest_anchor().head();
self.push_to_selection_history();
let result = self.selections.change_with(cx, change);
let (changed, result) = self.selections.change_with(cx, change);
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
if changed {
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
}
self.selections_did_change(true, &old_cursor_position, cx);
}
self.selections_did_change(true, &old_cursor_position, cx);
result
}
@ -1551,12 +1561,10 @@ impl Editor {
}
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
if add {
if click_count > 1 {
s.delete(newest_selection.id);
}
} else {
if !add {
s.clear_disjoint();
} else if click_count > 1 {
s.delete(newest_selection.id)
}
s.set_pending_range(start..end, mode);
@ -1869,13 +1877,16 @@ impl Editor {
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
let text: Arc<str> = text.into();
self.transact(cx, |this, cx| {
let old_selections = this.selections.all::<usize>(cx);
let old_selections = this.selections.all_adjusted(cx);
let selection_anchors = this.buffer.update(cx, |buffer, cx| {
let anchors = {
let snapshot = buffer.read(cx);
old_selections
.iter()
.map(|s| (s.id, s.goal, snapshot.anchor_after(s.end)))
.map(|s| {
let anchor = snapshot.anchor_after(s.end);
s.map(|_| anchor.clone())
})
.collect::<Vec<_>>()
};
buffer.edit_with_autoindent(
@ -1887,25 +1898,8 @@ impl Editor {
anchors
});
let selections = {
let snapshot = this.buffer.read(cx).read(cx);
selection_anchors
.into_iter()
.map(|(id, goal, position)| {
let position = position.to_offset(&snapshot);
Selection {
id,
start: position,
end: position,
goal,
reversed: false,
}
})
.collect()
};
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.select(selections);
s.select_anchors(selection_anchors);
})
});
}
@ -2758,28 +2752,31 @@ impl Editor {
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections.all::<Point>(cx);
for selection in &mut selections {
if selection.is_empty() {
let old_head = selection.head();
let mut new_head =
movement::left(&display_map, old_head.to_display_point(&display_map))
.to_point(&display_map);
if let Some((buffer, line_buffer_range)) = display_map
.buffer_snapshot
.buffer_line_for_row(old_head.row)
{
let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row);
let language_name = buffer.language().map(|language| language.name());
let indent = cx.global::<Settings>().tab_size(language_name.as_deref());
if old_head.column <= indent_column && old_head.column > 0 {
new_head = cmp::min(
new_head,
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
);
if !self.selections.line_mode {
for selection in &mut selections {
if selection.is_empty() {
let old_head = selection.head();
let mut new_head =
movement::left(&display_map, old_head.to_display_point(&display_map))
.to_point(&display_map);
if let Some((buffer, line_buffer_range)) = display_map
.buffer_snapshot
.buffer_line_for_row(old_head.row)
{
let indent_column =
buffer.indent_column_for_line(line_buffer_range.start.row);
let language_name = buffer.language().map(|language| language.name());
let indent = cx.global::<Settings>().tab_size(language_name.as_deref());
if old_head.column <= indent_column && old_head.column > 0 {
new_head = cmp::min(
new_head,
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
);
}
}
}
selection.set_head(new_head, SelectionGoal::None);
selection.set_head(new_head, SelectionGoal::None);
}
}
}
@ -2792,8 +2789,9 @@ impl Editor {
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if selection.is_empty() {
if selection.is_empty() && !line_mode {
let cursor = movement::right(map, selection.head());
selection.set_head(cursor, SelectionGoal::None);
}
@ -2816,7 +2814,7 @@ impl Editor {
return;
}
let mut selections = self.selections.all::<Point>(cx);
let mut selections = self.selections.all_adjusted(cx);
if selections.iter().all(|s| s.is_empty()) {
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
@ -3302,8 +3300,9 @@ impl Editor {
self.transact(cx, |this, cx| {
let edits = this.change_selections(Some(Autoscroll::Fit), cx, |s| {
let mut edits: Vec<(Range<usize>, String)> = Default::default();
let line_mode = s.line_mode;
s.move_with(|display_map, selection| {
if !selection.is_empty() {
if !selection.is_empty() || line_mode {
return;
}
@ -3356,7 +3355,7 @@ impl Editor {
{
let max_point = buffer.max_point();
for selection in &mut selections {
let is_entire_line = selection.is_empty();
let is_entire_line = selection.is_empty() || self.selections.line_mode;
if is_entire_line {
selection.start = Point::new(selection.start.row, 0);
selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
@ -3387,16 +3386,17 @@ impl Editor {
let selections = self.selections.all::<Point>(cx);
let buffer = self.buffer.read(cx).read(cx);
let mut text = String::new();
let mut clipboard_selections = Vec::with_capacity(selections.len());
{
let max_point = buffer.max_point();
for selection in selections.iter() {
let mut start = selection.start;
let mut end = selection.end;
let is_entire_line = selection.is_empty();
let is_entire_line = selection.is_empty() || self.selections.line_mode;
if is_entire_line {
start = Point::new(start.row, 0);
end = cmp::min(max_point, Point::new(start.row + 1, 0));
end = cmp::min(max_point, Point::new(end.row + 1, 0));
}
let mut len = 0;
for chunk in buffer.text_for_range(start..end) {
@ -3440,6 +3440,7 @@ impl Editor {
let snapshot = buffer.read(cx);
let mut start_offset = 0;
let mut edits = Vec::new();
let line_mode = this.selections.line_mode;
for (ix, selection) in old_selections.iter().enumerate() {
let to_insert;
let entire_line;
@ -3457,12 +3458,12 @@ impl Editor {
// clipboard text was written, then the entire line containing the
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let range = if selection.is_empty() && entire_line {
let range = if selection.is_empty() && !line_mode && entire_line {
let column = selection.start.to_point(&snapshot).column as usize;
let line_start = selection.start - column;
line_start..line_start
} else {
selection.start..selection.end
selection.range()
};
edits.push((range, to_insert));
@ -3512,8 +3513,9 @@ impl Editor {
pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
let cursor = if selection.is_empty() {
let cursor = if selection.is_empty() && !line_mode {
movement::left(map, selection.start)
} else {
selection.start
@ -3531,8 +3533,9 @@ impl Editor {
pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
let cursor = if selection.is_empty() {
let cursor = if selection.is_empty() && !line_mode {
movement::right(map, selection.end)
} else {
selection.end
@ -3565,8 +3568,9 @@ impl Editor {
}
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if !selection.is_empty() {
if !selection.is_empty() && !line_mode {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false);
@ -3596,8 +3600,9 @@ impl Editor {
}
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if !selection.is_empty() {
if !selection.is_empty() && !line_mode {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false);
@ -3679,8 +3684,9 @@ impl Editor {
) {
self.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if selection.is_empty() {
if selection.is_empty() && !line_mode {
let cursor = movement::previous_word_start(map, selection.head());
selection.set_head(cursor, SelectionGoal::None);
}
@ -3697,8 +3703,9 @@ impl Editor {
) {
self.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if selection.is_empty() {
if selection.is_empty() && !line_mode {
let cursor = movement::previous_subword_start(map, selection.head());
selection.set_head(cursor, SelectionGoal::None);
}
@ -3751,8 +3758,9 @@ impl Editor {
pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if selection.is_empty() {
if selection.is_empty() && !line_mode {
let cursor = movement::next_word_end(map, selection.head());
selection.set_head(cursor, SelectionGoal::None);
}
@ -4698,6 +4706,7 @@ impl Editor {
// Position the selection in the rename editor so that it matches the current selection.
this.show_local_selections = false;
let rename_editor = cx.add_view(|cx| {
println!("Rename editor created.");
let mut editor = Editor::single_line(None, cx);
if let Some(old_highlight_id) = old_highlight_id {
editor.override_text_style =
@ -5612,7 +5621,11 @@ impl View for Editor {
self.buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction(cx);
if self.leader_replica_id.is_none() {
buffer.set_active_selections(&self.selections.disjoint_anchors(), cx);
buffer.set_active_selections(
&self.selections.disjoint_anchors(),
self.selections.line_mode,
cx,
);
}
});
}
@ -6033,7 +6046,9 @@ pub fn styled_runs_for_code_label<'a>(
#[cfg(test)]
mod tests {
use crate::test::{assert_text_with_selections, select_ranges};
use crate::test::{
assert_text_with_selections, build_editor, select_ranges, EditorTestContext,
};
use super::*;
use gpui::{
@ -7305,117 +7320,62 @@ mod tests {
}
#[gpui::test]
fn test_indent_outdent(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
let buffer = MultiBuffer::build_simple(
indoc! {"
one two
three
four"},
cx,
);
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
view.update(cx, |view, cx| {
// two selections on the same line
select_ranges(
view,
indoc! {"
[one] [two]
three
four"},
cx,
);
cx.set_state(indoc! {"
[one} [two}
three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
[one} [two}
three
four"});
// indent from mid-tabstop to full tabstop
view.tab(&Tab, cx);
assert_text_with_selections(
view,
indoc! {"
[one] [two]
three
four"},
cx,
);
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
[one} [two}
three
four"});
// outdent from 1 tabstop to 0 tabstops
view.tab_prev(&TabPrev, cx);
assert_text_with_selections(
view,
indoc! {"
[one] [two]
three
four"},
cx,
);
// select across line ending
cx.set_state(indoc! {"
one two
t[hree
} four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
t[hree
} four"});
// select across line ending
select_ranges(
view,
indoc! {"
one two
t[hree
] four"},
cx,
);
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
t[hree
} four"});
// indent and outdent affect only the preceding line
view.tab(&Tab, cx);
assert_text_with_selections(
view,
indoc! {"
one two
t[hree
] four"},
cx,
);
view.tab_prev(&TabPrev, cx);
assert_text_with_selections(
view,
indoc! {"
one two
t[hree
] four"},
cx,
);
// Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {"
one two
|three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
|three
four"});
// Ensure that indenting/outdenting works when the cursor is at column 0.
select_ranges(
view,
indoc! {"
one two
[]three
four"},
cx,
);
view.tab(&Tab, cx);
assert_text_with_selections(
view,
indoc! {"
one two
[]three
four"},
cx,
);
select_ranges(
view,
indoc! {"
one two
[] three
four"},
cx,
);
view.tab_prev(&TabPrev, cx);
assert_text_with_selections(
view,
indoc! {"
one two
[]three
four"},
cx,
);
});
cx.set_state(indoc! {"
one two
| three
four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
|three
four"});
}
#[gpui::test]
@ -7524,73 +7484,71 @@ mod tests {
}
#[gpui::test]
fn test_backspace(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
let (_, view) = cx.add_window(Default::default(), |cx| {
build_editor(MultiBuffer::build_simple("", cx), cx)
});
async fn test_backspace(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
// Basic backspace
cx.set_state(indoc! {"
on|e two three
fou[r} five six
seven {eight nine
]ten"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
o|e two three
fou| five six
seven |ten"});
view.update(cx, |view, cx| {
view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx);
view.change_selections(None, cx, |s| {
s.select_display_ranges([
// an empty selection - the preceding character is deleted
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
// one character selected - it is deleted
DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
// a line suffix selected - it is deleted
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
])
});
view.backspace(&Backspace, cx);
assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n");
// Test backspace inside and around indents
cx.set_state(indoc! {"
zero
|one
|two
| | | three
| | four"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
zero
|one
|two
| three| four"});
view.set_text(" one\n two\n three\n four", cx);
view.change_selections(None, cx, |s| {
s.select_display_ranges([
// cursors at the the end of leading indent - last indent is deleted
DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8),
// cursors inside leading indent - overlapping indent deletions are coalesced
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
DisplayPoint::new(2, 6)..DisplayPoint::new(2, 6),
// cursor at the beginning of a line - preceding newline is deleted
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
// selection inside leading indent - only the selected character is deleted
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3),
])
});
view.backspace(&Backspace, cx);
assert_eq!(view.text(cx), "one\n two\n three four");
});
// Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {"
The |quick |brown
fox jumps over
the lazy dog
|The qu[ick b}rown"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state(indoc! {"
|fox jumps over
the lazy dog|"});
}
#[gpui::test]
fn test_delete(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
let buffer =
MultiBuffer::build_simple("one two three\nfour five six\nseven eight nine\nten\n", cx);
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
async fn test_delete(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
// an empty selection - the following character is deleted
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
// one character selected - it is deleted
DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
// a line suffix selected - it is deleted
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
])
});
view.delete(&Delete, cx);
});
cx.set_state(indoc! {"
on|e two three
fou[r} five six
seven {eight nine
]ten"});
cx.update_editor(|e, cx| e.delete(&Delete, cx));
cx.assert_editor_state(indoc! {"
on| two three
fou| five six
seven |ten"});
assert_eq!(
buffer.read(cx).read(cx).text(),
"on two three\nfou five six\nseven ten\n"
);
// Test backspace with line_mode set to true
cx.update_editor(|e, _| e.selections.line_mode = true);
cx.set_state(indoc! {"
The |quick |brown
fox {jum]ps over
the lazy dog
|The qu[ick b}rown"});
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
cx.assert_editor_state("|the lazy dog|");
}
#[gpui::test]
@ -7898,131 +7856,79 @@ mod tests {
}
#[gpui::test]
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
let buffer = MultiBuffer::build_simple("one✅ two three four five six ", cx);
let view = cx
.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx))
.1;
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
// Cut with three selections. Clipboard text is divided into three slices.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_ranges(vec![0..7, 11..17, 22..27]));
view.cut(&Cut, cx);
assert_eq!(view.display_text(cx), "two four six ");
});
cx.set_state("[one✅ }two [three }four [five }six ");
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state("|two |four |six ");
// Paste with three cursors. Each cursor pastes one slice of the clipboard text.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_ranges(vec![4..4, 9..9, 13..13]));
view.paste(&Paste, cx);
assert_eq!(view.display_text(cx), "two one✅ four three six five ");
assert_eq!(
view.selections.display_ranges(cx),
&[
DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
DisplayPoint::new(0, 22)..DisplayPoint::new(0, 22),
DisplayPoint::new(0, 31)..DisplayPoint::new(0, 31)
]
);
});
cx.set_state("two |four |six |");
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state("two one✅ |four three |six five |");
// Paste again but with only two cursors. Since the number of cursors doesn't
// match the number of slices in the clipboard, the entire clipboard text
// is pasted at each cursor.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_ranges(vec![0..0, 31..31]));
view.handle_input(&Input("( ".into()), cx);
view.paste(&Paste, cx);
view.handle_input(&Input(") ".into()), cx);
assert_eq!(
view.display_text(cx),
"( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
);
});
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_ranges(vec![0..0]));
view.handle_input(&Input("123\n4567\n89\n".into()), cx);
assert_eq!(
view.display_text(cx),
"123\n4567\n89\n( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
);
cx.set_state("|two one✅ four three six five |");
cx.update_editor(|e, cx| {
e.handle_input(&Input("( ".into()), cx);
e.paste(&Paste, cx);
e.handle_input(&Input(") ".into()), cx);
});
cx.assert_editor_state(indoc! {"
( one
three
five ) |two one four three six five ( one
three
five ) |"});
// Cut with three selections, one of which is full-line.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_display_ranges(
[
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2),
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
],
));
view.cut(&Cut, cx);
assert_eq!(
view.display_text(cx),
"13\n9\n( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
);
});
cx.set_state(indoc! {"
1[2}3
4|567
[8}9"});
cx.update_editor(|e, cx| e.cut(&Cut, cx));
cx.assert_editor_state(indoc! {"
1|3
|9"});
// Paste with three selections, noticing how the copied selection that was full-line
// gets inserted before the second cursor.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_display_ranges(
[
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
],
));
view.paste(&Paste, cx);
assert_eq!(
view.display_text(cx),
"123\n4567\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
);
assert_eq!(
view.selections.display_ranges(cx),
&[
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
DisplayPoint::new(3, 3)..DisplayPoint::new(3, 3),
]
);
});
cx.set_state(indoc! {"
1|3
9|
[o}ne"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
12|3
4567
9|
8|ne"});
// Copy with a single cursor only, which writes the whole line into the clipboard.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
});
view.copy(&Copy, cx);
});
cx.set_state(indoc! {"
The quick brown
fox ju|mps over
the lazy dog"});
cx.update_editor(|e, cx| e.copy(&Copy, cx));
cx.assert_clipboard_content(Some("fox jumps over\n"));
// Paste with three selections, noticing how the copied full-line selection is inserted
// before the empty selections but replaces the selection that is non-empty.
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| s.select_display_ranges(
[
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2),
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
],
));
view.paste(&Paste, cx);
assert_eq!(
view.display_text(cx),
"123\n123\n123\n67\n123\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
);
assert_eq!(
view.selections.display_ranges(cx),
&[
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
]
);
});
cx.set_state(indoc! {"
T|he quick brown
[fo}x jumps over
t|he lazy dog"});
cx.update_editor(|e, cx| e.paste(&Paste, cx));
cx.assert_editor_state(indoc! {"
fox jumps over
T|he quick brown
fox jumps over
|x jumps over
fox jumps over
t|he lazy dog"});
}
#[gpui::test]
@ -8761,8 +8667,10 @@ mod tests {
fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text_ranges: &str) {
let range_markers = ('<', '>');
let (expected_text, mut selection_ranges_lookup) =
marked_text_ranges_by(marked_text_ranges, vec![range_markers.clone()]);
let selection_ranges = selection_ranges_lookup.remove(&range_markers).unwrap();
marked_text_ranges_by(marked_text_ranges, vec![range_markers.clone().into()]);
let selection_ranges = selection_ranges_lookup
.remove(&range_markers.into())
.unwrap();
assert_eq!(editor.text(cx), expected_text);
assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
}
@ -9811,10 +9719,6 @@ mod tests {
point..point
}
fn build_editor(buffer: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
Editor::new(EditorMode::Full, buffer, None, None, None, cx)
}
fn assert_selection_ranges(
marked_text: &str,
selection_marker_pairs: Vec<(char, char)>,