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}