on master help sign in
Implement view swapping

* add Tree::swap_split_in_direction() * add swap_view_{left,down,up,right} commands, bound to H,J,K,L respectively in the Window menu(s) * add test for view swapping

commit3f10473d30eec79e135ea74fa2bc4cc996426128 authorRoland Kovacs <zen3ger@tutanota.com> dateMon 9 May 2022, 21:12 (4 years ago) parente8e2526
4 files changed+191
helix-term/src/commands.rs+20 helix-term/src/keymap/default.rs+8 helix-view/src/editor.rs+16 helix-view/src/tree.rs+147
helix-term/src/commands.rs+20
@@ -360,6 +360,10 @@ impl MappableCommand {
360360 jump_view_left, "Jump to the split to the left",
361361 jump_view_up, "Jump to the split above",
362362 jump_view_down, "Jump to the split below",
363+ swap_view_right, "Swap with the split to the right",
364+ swap_view_left, "Swap with the split to the left",
365+ swap_view_up, "Swap with the split above",
366+ swap_view_down, "Swap with the split below",
363367 transpose_view, "Transpose splits",
364368 rotate_view, "Goto next window",
365369 hsplit, "Horizontal bottom split",
@@ -3864,6 +3868,22 @@ fn jump_view_down(cx: &mut Context) {
38643868 cx.editor.focus_down()
38653869}
38663870
3871+fn swap_view_right(cx: &mut Context) {
3872+ cx.editor.swap_right()
3873+}
3874+
3875+fn swap_view_left(cx: &mut Context) {
3876+ cx.editor.swap_left()
3877+}
3878+
3879+fn swap_view_up(cx: &mut Context) {
3880+ cx.editor.swap_up()
3881+}
3882+
3883+fn swap_view_down(cx: &mut Context) {
3884+ cx.editor.swap_down()
3885+}
3886+
38673887fn transpose_view(cx: &mut Context) {
38683888 cx.editor.transpose_view()
38693889}
helix-term/src/keymap/default.rs+8
@@ -180,6 +180,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
180180 "C-j" | "j" | "down" => jump_view_down,
181181 "C-k" | "k" | "up" => jump_view_up,
182182 "C-l" | "l" | "right" => jump_view_right,
183+ "L" => swap_view_right,
184+ "K" => swap_view_up,
185+ "H" => swap_view_left,
186+ "J" => swap_view_down,
183187 "n" => { "New split scratch buffer"
184188 "C-s" | "s" => hsplit_new,
185189 "C-v" | "v" => vsplit_new,
@@ -236,6 +240,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
236240 "C-j" | "j" | "down" => jump_view_down,
237241 "C-k" | "k" | "up" => jump_view_up,
238242 "C-l" | "l" | "right" => jump_view_right,
243+ "H" => swap_view_left,
244+ "J" => swap_view_down,
245+ "K" => swap_view_up,
246+ "L" => swap_view_right,
239247 "n" => { "New split scratch buffer"
240248 "C-s" | "s" => hsplit_new,
241249 "C-v" | "v" => vsplit_new,
helix-view/src/editor.rs+16
@@ -885,6 +885,22 @@ impl Editor {
885885 self.tree.focus_direction(tree::Direction::Down);
886886 }
887887
888+ pub fn swap_right(&mut self) {
889+ self.tree.swap_split_in_direction(tree::Direction::Right);
890+ }
891+
892+ pub fn swap_left(&mut self) {
893+ self.tree.swap_split_in_direction(tree::Direction::Left);
894+ }
895+
896+ pub fn swap_up(&mut self) {
897+ self.tree.swap_split_in_direction(tree::Direction::Up);
898+ }
899+
900+ pub fn swap_down(&mut self) {
901+ self.tree.swap_split_in_direction(tree::Direction::Down);
902+ }
903+
888904 pub fn transpose_view(&mut self) {
889905 self.tree.transpose();
890906 }
helix-view/src/tree.rs+147
@@ -538,6 +538,24 @@ impl Tree {
538538 }
539539 }
540540
541+ pub fn swap_split_in_direction(&mut self, direction: Direction) {
542+ if let Some(id) = self.find_split_in_direction(self.focus, direction) {
543+ if let Some([focused, target]) = self.nodes.get_disjoint_mut([self.focus, id]) {
544+ match (&mut focused.content, &mut target.content) {
545+ (Content::View(focused), Content::View(target)) => {
546+ std::mem::swap(&mut focused.doc, &mut target.doc);
547+ std::mem::swap(&mut focused.id, &mut target.id);
548+ self.focus = id;
549+ }
550+ // self.focus always points to a view which has a content of Content::View
551+ // and find_split_in_direction() only returns a view which has content of
552+ // Content::View.
553+ _ => unreachable!(),
554+ }
555+ }
556+ }
557+ }
558+
541559 pub fn area(&self) -> Rect {
542560 self.area
543561 }
@@ -649,4 +667,133 @@ mod test {
649667 assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
650668 assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
651669 }
670+
671+ #[test]
672+ fn swap_split_in_direction() {
673+ let mut tree = Tree::new(Rect {
674+ x: 0,
675+ y: 0,
676+ width: 180,
677+ height: 80,
678+ });
679+
680+ let doc_l0 = DocumentId::default();
681+ let mut view = View::new(
682+ doc_l0,
683+ vec![GutterType::Diagnostics, GutterType::LineNumbers],
684+ );
685+ view.area = Rect::new(0, 0, 180, 80);
686+ tree.insert(view);
687+
688+ let l0 = tree.focus;
689+
690+ let doc_r0 = DocumentId::default();
691+ let view = View::new(
692+ doc_r0,
693+ vec![GutterType::Diagnostics, GutterType::LineNumbers],
694+ );
695+ tree.split(view, Layout::Vertical);
696+ let r0 = tree.focus;
697+
698+ tree.focus = l0;
699+
700+ let doc_l1 = DocumentId::default();
701+ let view = View::new(
702+ doc_l1,
703+ vec![GutterType::Diagnostics, GutterType::LineNumbers],
704+ );
705+ tree.split(view, Layout::Horizontal);
706+ let l1 = tree.focus;
707+
708+ tree.focus = l0;
709+
710+ let doc_l2 = DocumentId::default();
711+ let view = View::new(
712+ doc_l2,
713+ vec![GutterType::Diagnostics, GutterType::LineNumbers],
714+ );
715+ tree.split(view, Layout::Vertical);
716+ let l2 = tree.focus;
717+
718+ // Views in test
719+ // | L0 | L2 | |
720+ // | L1 | R0 |
721+
722+ // Document IDs in test
723+ // | l0 | l2 | |
724+ // | l1 | r0 |
725+
726+ fn doc_id(tree: &Tree, view_id: ViewId) -> Option<DocumentId> {
727+ if let Content::View(view) = &tree.nodes[view_id].content {
728+ Some(view.doc)
729+ } else {
730+ None
731+ }
732+ }
733+
734+ tree.focus = l0;
735+ // `*` marks the view in focus from view table (here L0)
736+ // | l0* | l2 | |
737+ // | l1 | r0 |
738+ tree.swap_split_in_direction(Direction::Down);
739+ // | l1 | l2 | |
740+ // | l0* | r0 |
741+ assert_eq!(tree.focus, l1);
742+ assert_eq!(doc_id(&tree, l0), Some(doc_l1));
743+ assert_eq!(doc_id(&tree, l1), Some(doc_l0));
744+ assert_eq!(doc_id(&tree, l2), Some(doc_l2));
745+ assert_eq!(doc_id(&tree, r0), Some(doc_r0));
746+
747+ tree.swap_split_in_direction(Direction::Right);
748+
749+ // | l1 | l2 | |
750+ // | r0 | l0* |
751+ assert_eq!(tree.focus, r0);
752+ assert_eq!(doc_id(&tree, l0), Some(doc_l1));
753+ assert_eq!(doc_id(&tree, l1), Some(doc_r0));
754+ assert_eq!(doc_id(&tree, l2), Some(doc_l2));
755+ assert_eq!(doc_id(&tree, r0), Some(doc_l0));
756+
757+ // cannot swap, nothing changes
758+ tree.swap_split_in_direction(Direction::Up);
759+ // | l1 | l2 | |
760+ // | r0 | l0* |
761+ assert_eq!(tree.focus, r0);
762+ assert_eq!(doc_id(&tree, l0), Some(doc_l1));
763+ assert_eq!(doc_id(&tree, l1), Some(doc_r0));
764+ assert_eq!(doc_id(&tree, l2), Some(doc_l2));
765+ assert_eq!(doc_id(&tree, r0), Some(doc_l0));
766+
767+ // cannot swap, nothing changes
768+ tree.swap_split_in_direction(Direction::Down);
769+ // | l1 | l2 | |
770+ // | r0 | l0* |
771+ assert_eq!(tree.focus, r0);
772+ assert_eq!(doc_id(&tree, l0), Some(doc_l1));
773+ assert_eq!(doc_id(&tree, l1), Some(doc_r0));
774+ assert_eq!(doc_id(&tree, l2), Some(doc_l2));
775+ assert_eq!(doc_id(&tree, r0), Some(doc_l0));
776+
777+ tree.focus = l2;
778+ // | l1 | l2* | |
779+ // | r0 | l0 |
780+
781+ tree.swap_split_in_direction(Direction::Down);
782+ // | l1 | r0 | |
783+ // | l2* | l0 |
784+ assert_eq!(tree.focus, l1);
785+ assert_eq!(doc_id(&tree, l0), Some(doc_l1));
786+ assert_eq!(doc_id(&tree, l1), Some(doc_l2));
787+ assert_eq!(doc_id(&tree, l2), Some(doc_r0));
788+ assert_eq!(doc_id(&tree, r0), Some(doc_l0));
789+
790+ tree.swap_split_in_direction(Direction::Up);
791+ // | l2* | r0 | |
792+ // | l1 | l0 |
793+ assert_eq!(tree.focus, l0);
794+ assert_eq!(doc_id(&tree, l0), Some(doc_l2));
795+ assert_eq!(doc_id(&tree, l1), Some(doc_l1));
796+ assert_eq!(doc_id(&tree, l2), Some(doc_r0));
797+ assert_eq!(doc_id(&tree, r0), Some(doc_l0));
798+ }
652799}