Skip to content

Commit

Permalink
Improve Editor::DuplicateSelection (#21976)
Browse files Browse the repository at this point in the history
Improves the new `Editor::DuplicateSelection` @CharlesChen0823 added in
#21154.

- Merge `duplicate_selection` and `duplicate_line` into single function.
- Add keyboard shortcuts to JetBrains and SublimeText keymaps.
- If the selection is empty (e.g. just a cursor) make
`Editor::DuplicateSelection` fallback to being the same as
`Editor::DuplicateLineDown`.
- Tested with multiple cursors and for multiple selections.

| Editor      | Action              | macOS       | Linux        |
| ----------- | ------------------- | ----------- | ------------ |
| VSCode      | Duplicate Selection |             |              |
| JetBrains   | Duplicate Selection | cmd-d       | ctrl-d       |
| XCode       | Duplicate           | cmd-d       | N/A          |
| SublimeText | duplicate_line      | cmd-shift-d | ctrl-shift-d |

This matches behavior of the `duplicate` functionality in all other
major editors, with one exception: other editors change the selection so
that the newly duplicated object, current Zed behavior leaves the
original selection unchanged (TODO?)
  • Loading branch information
notpeter authored Dec 13, 2024
1 parent 06edcd1 commit 735849e
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 55 deletions.
2 changes: 1 addition & 1 deletion assets/keymaps/linux/jetbrains.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-d": "editor::DuplicateLineDown",
"ctrl-d": "editor::DuplicateSelection",
"ctrl-y": "editor::DeleteLine",
"ctrl-m": "editor::ScrollCursorCenter",
"ctrl-pagedown": "editor::MovePageDown",
Expand Down
2 changes: 1 addition & 1 deletion assets/keymaps/linux/sublime_text.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-shift-d": "editor::DuplicateSelection",
"alt-f3": "editor::SelectAllMatches", // find_all_under
"f12": "editor::GoToDefinition",
"ctrl-f12": "editor::GoToDefinitionSplit",
Expand Down
2 changes: 1 addition & 1 deletion assets/keymaps/macos/jetbrains.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"cmd-d": "editor::DuplicateLineDown",
"cmd-d": "editor::DuplicateSelection",
"cmd-backspace": "editor::DeleteLine",
"cmd-pagedown": "editor::MovePageDown",
"cmd-pageup": "editor::MovePageUp",
Expand Down
2 changes: 1 addition & 1 deletion assets/keymaps/macos/sublime_text.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"cmd-shift-l": "editor::SplitSelectionIntoLines",
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
"cmd-shift-d": "editor::DuplicateLineDown",
"cmd-shift-d": "editor::DuplicateSelection",
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
"shift-f12": "editor::FindAllReferences",
"alt-cmd-down": "editor::GoToDefinition",
Expand Down
92 changes: 41 additions & 51 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6135,66 +6135,52 @@ impl Editor {
});
}

pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let selections = self.selections.all::<Point>(cx);

let mut edits = Vec::new();
for selection in selections.iter() {
let start = selection.start;
let end = selection.end;
let text = buffer.text_for_range(start..end).collect::<String>();
edits.push((selection.end..selection.end, text));
}

self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});

this.request_autoscroll(Autoscroll::fit(), cx);
});
}

pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
pub fn duplicate(&mut self, upwards: bool, whole_lines: bool, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let selections = self.selections.all::<Point>(cx);

let mut edits = Vec::new();
let mut selections_iter = selections.iter().peekable();
while let Some(selection) = selections_iter.next() {
// Avoid duplicating the same lines twice.
let mut rows = selection.spanned_rows(false, &display_map);

while let Some(next_selection) = selections_iter.peek() {
let next_rows = next_selection.spanned_rows(false, &display_map);
if next_rows.start < rows.end {
rows.end = next_rows.end;
selections_iter.next().unwrap();
} else {
break;
// duplicate line-wise
if whole_lines || selection.start == selection.end {
// Avoid duplicating the same lines twice.
while let Some(next_selection) = selections_iter.peek() {
let next_rows = next_selection.spanned_rows(false, &display_map);
if next_rows.start < rows.end {
rows.end = next_rows.end;
selections_iter.next().unwrap();
} else {
break;
}
}
}

// Copy the text from the selected row region and splice it either at the start
// or end of the region.
let start = Point::new(rows.start.0, 0);
let end = Point::new(
rows.end.previous_row().0,
buffer.line_len(rows.end.previous_row()),
);
let text = buffer
.text_for_range(start..end)
.chain(Some("\n"))
.collect::<String>();
let insert_location = if upwards {
Point::new(rows.end.0, 0)
// Copy the text from the selected row region and splice it either at the start
// or end of the region.
let start = Point::new(rows.start.0, 0);
let end = Point::new(
rows.end.previous_row().0,
buffer.line_len(rows.end.previous_row()),
);
let text = buffer
.text_for_range(start..end)
.chain(Some("\n"))
.collect::<String>();
let insert_location = if upwards {
Point::new(rows.end.0, 0)
} else {
start
};
edits.push((insert_location..insert_location, text));
} else {
start
};
edits.push((insert_location..insert_location, text));
// duplicate character-wise
let start = selection.start;
let end = selection.end;
let text = buffer.text_for_range(start..end).collect::<String>();
edits.push((selection.end..selection.end, text));
}
}

self.transact(cx, |this, cx| {
Expand All @@ -6207,11 +6193,15 @@ impl Editor {
}

pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
self.duplicate_line(true, cx);
self.duplicate(true, true, cx);
}

pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
self.duplicate_line(false, cx);
self.duplicate(false, true, cx);
}

pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
self.duplicate(false, false, cx);
}

pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
Expand Down

0 comments on commit 735849e

Please sign in to comment.