indent.rs

  1use crate::{Vim, motion::Motion, object::Object, state::Mode};
  2use collections::HashMap;
  3use editor::SelectionEffects;
  4use editor::{Bias, Editor, display_map::ToDisplayPoint};
  5use gpui::actions;
  6use gpui::{Context, Window};
  7use language::SelectionGoal;
  8
  9#[derive(PartialEq, Eq)]
 10pub(crate) enum IndentDirection {
 11    In,
 12    Out,
 13    Auto,
 14}
 15
 16actions!(vim, [Indent, Outdent, AutoIndent]);
 17
 18pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
 19    Vim::action(editor, cx, |vim, _: &Indent, window, cx| {
 20        vim.record_current_action(cx);
 21        let count = Vim::take_count(cx).unwrap_or(1);
 22        Vim::take_forced_motion(cx);
 23        vim.store_visual_marks(window, cx);
 24        vim.update_editor(window, cx, |vim, editor, window, cx| {
 25            editor.transact(window, cx, |editor, window, cx| {
 26                let original_positions = vim.save_selection_starts(editor, cx);
 27                for _ in 0..count {
 28                    editor.indent(&Default::default(), window, cx);
 29                }
 30                vim.restore_selection_cursors(editor, window, cx, original_positions);
 31            });
 32        });
 33        if vim.mode.is_visual() {
 34            vim.switch_mode(Mode::Normal, true, window, cx)
 35        }
 36    });
 37
 38    Vim::action(editor, cx, |vim, _: &Outdent, window, cx| {
 39        vim.record_current_action(cx);
 40        let count = Vim::take_count(cx).unwrap_or(1);
 41        Vim::take_forced_motion(cx);
 42        vim.store_visual_marks(window, cx);
 43        vim.update_editor(window, cx, |vim, editor, window, cx| {
 44            editor.transact(window, cx, |editor, window, cx| {
 45                let original_positions = vim.save_selection_starts(editor, cx);
 46                for _ in 0..count {
 47                    editor.outdent(&Default::default(), window, cx);
 48                }
 49                vim.restore_selection_cursors(editor, window, cx, original_positions);
 50            });
 51        });
 52        if vim.mode.is_visual() {
 53            vim.switch_mode(Mode::Normal, true, window, cx)
 54        }
 55    });
 56
 57    Vim::action(editor, cx, |vim, _: &AutoIndent, window, cx| {
 58        vim.record_current_action(cx);
 59        let count = Vim::take_count(cx).unwrap_or(1);
 60        Vim::take_forced_motion(cx);
 61        vim.store_visual_marks(window, cx);
 62        vim.update_editor(window, cx, |vim, editor, window, cx| {
 63            editor.transact(window, cx, |editor, window, cx| {
 64                let original_positions = vim.save_selection_starts(editor, cx);
 65                for _ in 0..count {
 66                    editor.autoindent(&Default::default(), window, cx);
 67                }
 68                vim.restore_selection_cursors(editor, window, cx, original_positions);
 69            });
 70        });
 71        if vim.mode.is_visual() {
 72            vim.switch_mode(Mode::Normal, true, window, cx)
 73        }
 74    });
 75}
 76
 77impl Vim {
 78    pub(crate) fn indent_motion(
 79        &mut self,
 80        motion: Motion,
 81        times: Option<usize>,
 82        forced_motion: bool,
 83        dir: IndentDirection,
 84        window: &mut Window,
 85        cx: &mut Context<Self>,
 86    ) {
 87        self.stop_recording(cx);
 88        self.update_editor(window, cx, |_, editor, window, cx| {
 89            let text_layout_details = editor.text_layout_details(window);
 90            editor.transact(window, cx, |editor, window, cx| {
 91                let mut selection_starts: HashMap<_, _> = Default::default();
 92                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 93                    s.move_with(|map, selection| {
 94                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
 95                        selection_starts.insert(selection.id, anchor);
 96                        motion.expand_selection(
 97                            map,
 98                            selection,
 99                            times,
100                            &text_layout_details,
101                            forced_motion,
102                        );
103                    });
104                });
105                match dir {
106                    IndentDirection::In => editor.indent(&Default::default(), window, cx),
107                    IndentDirection::Out => editor.outdent(&Default::default(), window, cx),
108                    IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx),
109                }
110                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
111                    s.move_with(|map, selection| {
112                        let anchor = selection_starts.remove(&selection.id).unwrap();
113                        selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
114                    });
115                });
116            });
117        });
118    }
119
120    pub(crate) fn indent_object(
121        &mut self,
122        object: Object,
123        around: bool,
124        dir: IndentDirection,
125        window: &mut Window,
126        cx: &mut Context<Self>,
127    ) {
128        self.stop_recording(cx);
129        self.update_editor(window, cx, |_, editor, window, cx| {
130            editor.transact(window, cx, |editor, window, cx| {
131                let mut original_positions: HashMap<_, _> = Default::default();
132                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
133                    s.move_with(|map, selection| {
134                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
135                        original_positions.insert(selection.id, anchor);
136                        object.expand_selection(map, selection, around);
137                    });
138                });
139                match dir {
140                    IndentDirection::In => editor.indent(&Default::default(), window, cx),
141                    IndentDirection::Out => editor.outdent(&Default::default(), window, cx),
142                    IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx),
143                }
144                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
145                    s.move_with(|map, selection| {
146                        let anchor = original_positions.remove(&selection.id).unwrap();
147                        selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
148                    });
149                });
150            });
151        });
152    }
153}
154
155#[cfg(test)]
156mod test {
157    use crate::{
158        state::Mode,
159        test::{NeovimBackedTestContext, VimTestContext},
160    };
161    use indoc::indoc;
162
163    #[gpui::test]
164    async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
165        let mut cx = NeovimBackedTestContext::new(cx).await;
166        cx.set_neovim_option("shiftwidth=4").await;
167
168        cx.set_shared_state("ˇhello\nworld\n").await;
169        cx.simulate_shared_keystrokes("v j > g v").await;
170        cx.shared_state()
171            .await
172            .assert_eq("«    hello\n ˇ»   world\n");
173    }
174
175    #[gpui::test]
176    async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
177        let mut cx = VimTestContext::new(cx, true).await;
178
179        cx.set_state(
180            indoc!(
181                "
182            fn a() {
183                b();
184                c();
185
186                    d();
187                    ˇe();
188                    f();
189
190                g();
191            }
192        "
193            ),
194            Mode::Normal,
195        );
196
197        cx.simulate_keystrokes("= a p");
198        cx.assert_state(
199            indoc!(
200                "
201                fn a() {
202                    b();
203                    c();
204
205                    d();
206                    ˇe();
207                    f();
208
209                    g();
210                }
211            "
212            ),
213            Mode::Normal,
214        );
215    }
216}