From 070607c821242babaf660cda2cf34e14c255be25 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 3 May 2022 14:21:06 +0200 Subject: [PATCH] Implement `Editor::transpose` without accounting for multi-byte chars --- assets/keymaps/default.json | 1 + crates/editor/src/editor.rs | 115 ++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index af47a165f9..1378022bcf 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -33,6 +33,7 @@ "tab": "editor::Tab", "shift-tab": "editor::TabPrev", "ctrl-k": "editor::CutToEndOfLine", + "ctrl-t": "editor::Transpose", "cmd-backspace": "editor::DeleteToBeginningOfLine", "cmd-delete": "editor::DeleteToEndOfLine", "alt-backspace": "editor::DeleteToPreviousWordStart", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d099cbef11..3d5a666038 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -136,6 +136,7 @@ actions!( DuplicateLine, MoveLineUp, MoveLineDown, + Transpose, Cut, Copy, Paste, @@ -239,6 +240,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::duplicate_line); cx.add_action(Editor::move_line_up); cx.add_action(Editor::move_line_down); + cx.add_action(Editor::transpose); cx.add_action(Editor::cut); cx.add_action(Editor::copy); cx.add_action(Editor::paste); @@ -3382,6 +3384,41 @@ impl Editor { }); } + pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext) { + self.transact(cx, |this, cx| { + let mut edits: Vec<(Range, String)> = Default::default(); + this.move_selections(cx, |display_map, selection| { + if !selection.is_empty() { + return; + } + + let mut head = selection.head(); + let mut transpose_offset = head.to_offset(display_map, Bias::Right); + if head.column() == display_map.line_len(head.row()) { + transpose_offset = transpose_offset.saturating_sub(1); + } + + if transpose_offset == 0 { + return; + } + + *head.column_mut() += 1; + head = display_map.clip_point(head, Bias::Right); + selection.collapse_to(head, SelectionGoal::Column(head.column())); + + let transpose_start = transpose_offset.saturating_sub(1); + if edits.last().map_or(true, |e| e.0.end < transpose_start) { + let transpose_end = transpose_offset + 1; + if let Some(ch) = display_map.buffer_snapshot.chars_at(transpose_start).next() { + edits.push((transpose_start..transpose_offset, String::new())); + edits.push((transpose_end..transpose_end, ch.to_string())); + } + } + }); + this.buffer.update(cx, |buffer, cx| buffer.edit(edits, cx)); + }); + } + pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { let mut text = String::new(); let mut selections = self.local_selections::(cx); @@ -8211,6 +8248,84 @@ mod tests { }); } + #[gpui::test] + fn test_transpose(cx: &mut gpui::MutableAppContext) { + cx.set_global(Settings::test(cx)); + + cx.add_window(Default::default(), |cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); + + editor.select_ranges([1..1], None, cx); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bac"); + assert_eq!(editor.selected_ranges(cx), [2..2]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bca"); + assert_eq!(editor.selected_ranges(cx), [3..3]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bac"); + assert_eq!(editor.selected_ranges(cx), [3..3]); + + editor + }) + .1; + + cx.add_window(Default::default(), |cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + + editor.select_ranges([3..3], None, cx); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acb\nde"); + assert_eq!(editor.selected_ranges(cx), [3..3]); + + editor.select_ranges([4..4], None, cx); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbd\ne"); + assert_eq!(editor.selected_ranges(cx), [5..5]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbde\n"); + assert_eq!(editor.selected_ranges(cx), [6..6]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "acbd\ne"); + assert_eq!(editor.selected_ranges(cx), [6..6]); + + editor + }) + .1; + + cx.add_window(Default::default(), |cx| { + let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + + editor.select_ranges([1..1, 2..2, 4..4], None, cx); + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bacd\ne"); + assert_eq!(editor.selected_ranges(cx), [2..2, 3..3, 5..5]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcade\n"); + assert_eq!(editor.selected_ranges(cx), [3..3, 4..4, 6..6]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcdae\n"); + assert_eq!(editor.selected_ranges(cx), [4..4, 5..5, 6..6]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcdea\n"); + assert_eq!(editor.selected_ranges(cx), [5..5, 6..6]); + + editor.transpose(&Default::default(), cx); + assert_eq!(editor.text(cx), "bcdae\n"); + assert_eq!(editor.selected_ranges(cx), [5..5, 6..6]); + + editor + }) + .1; + } + #[gpui::test] fn test_clipboard(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx));