Add unique lines command (#7526)
Changes `Editor::manipulate_lines` to allow line adding and removal through callback function. - Added `editor::UniqueLinesCaseSensitive` and `editor::UniqueLinesCaseInsensitive` commands ([#4831](https://github.com/zed-industries/zed/issues/4831))
This commit is contained in:
parent
89b1e76003
commit
a0b2614d57
4 changed files with 219 additions and 15 deletions
|
@ -238,5 +238,7 @@ gpui::actions!(
|
|||
Undo,
|
||||
UndoSelection,
|
||||
UnfoldLines,
|
||||
UniqueLinesCaseSensitive,
|
||||
UniqueLinesCaseInsensitive
|
||||
]
|
||||
);
|
||||
|
|
|
@ -4655,6 +4655,28 @@ impl Editor {
|
|||
self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn unique_lines_case_insensitive(
|
||||
&mut self,
|
||||
_: &UniqueLinesCaseInsensitive,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.manipulate_lines(cx, |lines| {
|
||||
let mut seen = HashSet::default();
|
||||
lines.retain(|line| seen.insert(line.to_lowercase()));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unique_lines_case_sensitive(
|
||||
&mut self,
|
||||
_: &UniqueLinesCaseSensitive,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.manipulate_lines(cx, |lines| {
|
||||
let mut seen = HashSet::default();
|
||||
lines.retain(|line| seen.insert(*line));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
|
||||
self.manipulate_lines(cx, |lines| lines.reverse())
|
||||
}
|
||||
|
@ -4665,7 +4687,7 @@ impl Editor {
|
|||
|
||||
fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
|
||||
where
|
||||
Fn: FnMut(&mut [&str]),
|
||||
Fn: FnMut(&mut Vec<&str>),
|
||||
{
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
|
@ -4676,6 +4698,8 @@ impl Editor {
|
|||
let mut selections = selections.iter().peekable();
|
||||
let mut contiguous_row_selections = Vec::new();
|
||||
let mut new_selections = Vec::new();
|
||||
let mut added_lines: usize = 0;
|
||||
let mut removed_lines: usize = 0;
|
||||
|
||||
while let Some(selection) = selections.next() {
|
||||
let (start_row, end_row) = consume_contiguous_rows(
|
||||
|
@ -4690,37 +4714,55 @@ impl Editor {
|
|||
let text = buffer
|
||||
.text_for_range(start_point..end_point)
|
||||
.collect::<String>();
|
||||
|
||||
let mut lines = text.split("\n").collect_vec();
|
||||
|
||||
let lines_len = lines.len();
|
||||
let lines_before = lines.len();
|
||||
callback(&mut lines);
|
||||
|
||||
// This is a current limitation with selections.
|
||||
// If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
|
||||
debug_assert!(
|
||||
lines.len() == lines_len,
|
||||
"callback should not change the number of lines"
|
||||
);
|
||||
let lines_after = lines.len();
|
||||
|
||||
edits.push((start_point..end_point, lines.join("\n")));
|
||||
let start_anchor = buffer.anchor_after(start_point);
|
||||
let end_anchor = buffer.anchor_before(end_point);
|
||||
|
||||
// Make selection and push
|
||||
// Selections must change based on added and removed line count
|
||||
let start_row = start_point.row + added_lines as u32 - removed_lines as u32;
|
||||
let end_row = start_row + lines_after.saturating_sub(1) as u32;
|
||||
new_selections.push(Selection {
|
||||
id: selection.id,
|
||||
start: start_anchor.to_offset(&buffer),
|
||||
end: end_anchor.to_offset(&buffer),
|
||||
start: start_row,
|
||||
end: end_row,
|
||||
goal: SelectionGoal::None,
|
||||
reversed: selection.reversed,
|
||||
});
|
||||
|
||||
if lines_after > lines_before {
|
||||
added_lines += lines_after - lines_before;
|
||||
} else if lines_before > lines_after {
|
||||
removed_lines += lines_before - lines_after;
|
||||
}
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let buffer = this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
// Recalculate offsets on newly edited buffer
|
||||
let new_selections = new_selections
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let start_point = Point::new(s.start, 0);
|
||||
let end_point = Point::new(s.end, buffer.line_len(s.end));
|
||||
Selection {
|
||||
id: s.id,
|
||||
start: buffer.point_to_offset(start_point),
|
||||
end: buffer.point_to_offset(end_point),
|
||||
goal: s.goal,
|
||||
reversed: s.reversed,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select(new_selections);
|
||||
});
|
||||
|
|
|
@ -2786,6 +2786,126 @@ async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
|
|||
dddˇ»
|
||||
|
||||
"});
|
||||
|
||||
// Adding new line
|
||||
cx.set_state(indoc! {"
|
||||
aa«a
|
||||
bbˇ»b
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«aaa
|
||||
bbb
|
||||
added_lineˇ»
|
||||
"});
|
||||
|
||||
// Removing line
|
||||
cx.set_state(indoc! {"
|
||||
aa«a
|
||||
bbbˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| {
|
||||
e.manipulate_lines(cx, |lines| {
|
||||
lines.pop();
|
||||
})
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«aaaˇ»
|
||||
"});
|
||||
|
||||
// Removing all lines
|
||||
cx.set_state(indoc! {"
|
||||
aa«a
|
||||
bbbˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| {
|
||||
e.manipulate_lines(cx, |lines| {
|
||||
lines.drain(..);
|
||||
})
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
ˇ
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// Consider continuous selection as single selection
|
||||
cx.set_state(indoc! {"
|
||||
Aaa«aa
|
||||
cˇ»c«c
|
||||
bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«Aaaaa
|
||||
ccc
|
||||
bb
|
||||
aaaaaˇ»
|
||||
"});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
Aaa«aa
|
||||
cˇ»c«c
|
||||
bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«Aaaaa
|
||||
ccc
|
||||
bbˇ»
|
||||
"});
|
||||
|
||||
// Consider non continuous selection as distinct dedup operations
|
||||
cx.set_state(indoc! {"
|
||||
«aaaaa
|
||||
bb
|
||||
aaaaa
|
||||
aaaaaˇ»
|
||||
|
||||
aaa«aaˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«aaaaa
|
||||
bbˇ»
|
||||
|
||||
«aaaaaˇ»
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
«Aaa
|
||||
aAa
|
||||
Aaaˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«Aaa
|
||||
aAaˇ»
|
||||
"});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
«Aaa
|
||||
aAa
|
||||
aaAˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«Aaaˇ»
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -2835,6 +2955,44 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
|
|||
ccc
|
||||
ddddˇ»
|
||||
"});
|
||||
|
||||
// Adding lines on each selection
|
||||
cx.set_state(indoc! {"
|
||||
2«
|
||||
1ˇ»
|
||||
|
||||
bb«bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«2
|
||||
1
|
||||
added lineˇ»
|
||||
|
||||
«bbbb
|
||||
aaaaa
|
||||
added lineˇ»
|
||||
"});
|
||||
|
||||
// Removing lines on each selection
|
||||
cx.set_state(indoc! {"
|
||||
2«
|
||||
1ˇ»
|
||||
|
||||
bb«bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| {
|
||||
e.manipulate_lines(cx, |lines| {
|
||||
lines.pop();
|
||||
})
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«2ˇ»
|
||||
|
||||
«bbbbˇ»
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
@ -330,6 +330,8 @@ impl EditorElement {
|
|||
register_action(view, cx, Editor::context_menu_next);
|
||||
register_action(view, cx, Editor::context_menu_last);
|
||||
register_action(view, cx, Editor::display_cursor_names);
|
||||
register_action(view, cx, Editor::unique_lines_case_insensitive);
|
||||
register_action(view, cx, Editor::unique_lines_case_sensitive);
|
||||
}
|
||||
|
||||
fn register_key_listeners(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue