indent.rs

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