Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:42:07.158Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

indent.rs

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