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        times: Option<usize>,
126        window: &mut Window,
127        cx: &mut Context<Self>,
128    ) {
129        self.stop_recording(cx);
130        self.update_editor(window, cx, |_, editor, window, cx| {
131            editor.transact(window, cx, |editor, window, cx| {
132                let mut original_positions: HashMap<_, _> = Default::default();
133                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
134                    s.move_with(|map, selection| {
135                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
136                        original_positions.insert(selection.id, anchor);
137                        object.expand_selection(map, selection, around, times);
138                    });
139                });
140                match dir {
141                    IndentDirection::In => editor.indent(&Default::default(), window, cx),
142                    IndentDirection::Out => editor.outdent(&Default::default(), window, cx),
143                    IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx),
144                }
145                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
146                    s.move_with(|map, selection| {
147                        let anchor = original_positions.remove(&selection.id).unwrap();
148                        selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
149                    });
150                });
151            });
152        });
153    }
154}
155
156#[cfg(test)]
157mod test {
158    use crate::{
159        state::Mode,
160        test::{NeovimBackedTestContext, VimTestContext},
161    };
162    use indoc::indoc;
163
164    #[gpui::test]
165    async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
166        let mut cx = NeovimBackedTestContext::new(cx).await;
167        cx.set_neovim_option("shiftwidth=4").await;
168
169        cx.set_shared_state("ˇhello\nworld\n").await;
170        cx.simulate_shared_keystrokes("v j > g v").await;
171        cx.shared_state()
172            .await
173            .assert_eq("«    hello\n ˇ»   world\n");
174    }
175
176    #[gpui::test]
177    async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
178        let mut cx = VimTestContext::new(cx, true).await;
179
180        cx.set_state(
181            indoc!(
182                "
183            fn a() {
184                b();
185                c();
186
187                    d();
188                    ˇe();
189                    f();
190
191                g();
192            }
193        "
194            ),
195            Mode::Normal,
196        );
197
198        cx.simulate_keystrokes("= a p");
199        cx.assert_state(
200            indoc!(
201                "
202                fn a() {
203                    b();
204                    c();
205
206                    d();
207                    ˇe();
208                    f();
209
210                    g();
211                }
212            "
213            ),
214            Mode::Normal,
215        );
216    }
217}