indent.rs

  1use crate::{motion::Motion, object::Object, state::Mode, Vim};
  2use collections::HashMap;
  3use editor::{display_map::ToDisplayPoint, Bias, Editor};
  4use gpui::actions;
  5use language::SelectionGoal;
  6use ui::ViewContext;
  7
  8#[derive(PartialEq, Eq)]
  9pub(crate) enum IndentDirection {
 10    In,
 11    Out,
 12}
 13
 14actions!(vim, [Indent, Outdent,]);
 15
 16pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
 17    Vim::action(editor, cx, |vim, _: &Indent, cx| {
 18        vim.record_current_action(cx);
 19        let count = Vim::take_count(cx).unwrap_or(1);
 20        vim.store_visual_marks(cx);
 21        vim.update_editor(cx, |vim, editor, cx| {
 22            editor.transact(cx, |editor, cx| {
 23                let original_positions = vim.save_selection_starts(editor, cx);
 24                for _ in 0..count {
 25                    editor.indent(&Default::default(), cx);
 26                }
 27                vim.restore_selection_cursors(editor, cx, original_positions);
 28            });
 29        });
 30        if vim.mode.is_visual() {
 31            vim.switch_mode(Mode::Normal, true, cx)
 32        }
 33    });
 34
 35    Vim::action(editor, cx, |vim, _: &Outdent, cx| {
 36        vim.record_current_action(cx);
 37        let count = Vim::take_count(cx).unwrap_or(1);
 38        vim.store_visual_marks(cx);
 39        vim.update_editor(cx, |vim, editor, cx| {
 40            editor.transact(cx, |editor, cx| {
 41                let original_positions = vim.save_selection_starts(editor, cx);
 42                for _ in 0..count {
 43                    editor.outdent(&Default::default(), cx);
 44                }
 45                vim.restore_selection_cursors(editor, cx, original_positions);
 46            });
 47        });
 48        if vim.mode.is_visual() {
 49            vim.switch_mode(Mode::Normal, true, cx)
 50        }
 51    });
 52}
 53
 54impl Vim {
 55    pub(crate) fn indent_motion(
 56        &mut self,
 57        motion: Motion,
 58        times: Option<usize>,
 59        dir: IndentDirection,
 60        cx: &mut ViewContext<Self>,
 61    ) {
 62        self.stop_recording(cx);
 63        self.update_editor(cx, |_, editor, cx| {
 64            let text_layout_details = editor.text_layout_details(cx);
 65            editor.transact(cx, |editor, cx| {
 66                let mut selection_starts: HashMap<_, _> = Default::default();
 67                editor.change_selections(None, cx, |s| {
 68                    s.move_with(|map, selection| {
 69                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
 70                        selection_starts.insert(selection.id, anchor);
 71                        motion.expand_selection(map, selection, times, false, &text_layout_details);
 72                    });
 73                });
 74                if dir == IndentDirection::In {
 75                    editor.indent(&Default::default(), cx);
 76                } else {
 77                    editor.outdent(&Default::default(), cx);
 78                }
 79                editor.change_selections(None, cx, |s| {
 80                    s.move_with(|map, selection| {
 81                        let anchor = selection_starts.remove(&selection.id).unwrap();
 82                        selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
 83                    });
 84                });
 85            });
 86        });
 87    }
 88
 89    pub(crate) fn indent_object(
 90        &mut self,
 91        object: Object,
 92        around: bool,
 93        dir: IndentDirection,
 94        cx: &mut ViewContext<Self>,
 95    ) {
 96        self.stop_recording(cx);
 97        self.update_editor(cx, |_, editor, cx| {
 98            editor.transact(cx, |editor, cx| {
 99                let mut original_positions: HashMap<_, _> = Default::default();
100                editor.change_selections(None, cx, |s| {
101                    s.move_with(|map, selection| {
102                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
103                        original_positions.insert(selection.id, anchor);
104                        object.expand_selection(map, selection, around);
105                    });
106                });
107                if dir == IndentDirection::In {
108                    editor.indent(&Default::default(), cx);
109                } else {
110                    editor.outdent(&Default::default(), cx);
111                }
112                editor.change_selections(None, cx, |s| {
113                    s.move_with(|map, selection| {
114                        let anchor = original_positions.remove(&selection.id).unwrap();
115                        selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
116                    });
117                });
118            });
119        });
120    }
121}
122
123#[cfg(test)]
124mod test {
125    use crate::test::NeovimBackedTestContext;
126
127    #[gpui::test]
128    async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
129        let mut cx = NeovimBackedTestContext::new(cx).await;
130        cx.set_neovim_option("shiftwidth=4").await;
131
132        cx.set_shared_state("ˇhello\nworld\n").await;
133        cx.simulate_shared_keystrokes("v j > g v").await;
134        cx.shared_state()
135            .await
136            .assert_eq("«    hello\n ˇ»   world\n");
137    }
138}