1use crate::{Vim, motion::Motion, object::Object, state::Mode};
  2use collections::HashMap;
  3use editor::{Bias, Editor, RewrapOptions, SelectionEffects, display_map::ToDisplayPoint};
  4use gpui::{Context, Window, actions};
  5use language::SelectionGoal;
  6
  7actions!(
  8    vim,
  9    [
 10        /// Rewraps the selected text to fit within the line width.
 11        Rewrap
 12    ]
 13);
 14
 15pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
 16    Vim::action(editor, cx, |vim, _: &Rewrap, window, cx| {
 17        vim.record_current_action(cx);
 18        Vim::take_count(cx);
 19        Vim::take_forced_motion(cx);
 20        vim.store_visual_marks(window, cx);
 21        vim.update_editor(cx, |vim, editor, cx| {
 22            editor.transact(window, cx, |editor, window, cx| {
 23                let mut positions = vim.save_selection_starts(editor, cx);
 24                editor.rewrap_impl(
 25                    RewrapOptions {
 26                        override_language_settings: true,
 27                        ..Default::default()
 28                    },
 29                    cx,
 30                );
 31                editor.change_selections(Default::default(), window, cx, |s| {
 32                    s.move_with(|map, selection| {
 33                        if let Some(anchor) = positions.remove(&selection.id) {
 34                            let mut point = anchor.to_display_point(map);
 35                            *point.column_mut() = 0;
 36                            selection.collapse_to(point, SelectionGoal::None);
 37                        }
 38                    });
 39                });
 40            });
 41        });
 42        if vim.mode.is_visual() {
 43            vim.switch_mode(Mode::Normal, true, window, cx)
 44        }
 45    });
 46}
 47
 48impl Vim {
 49    pub(crate) fn rewrap_motion(
 50        &mut self,
 51        motion: Motion,
 52        times: Option<usize>,
 53        forced_motion: bool,
 54        window: &mut Window,
 55        cx: &mut Context<Self>,
 56    ) {
 57        self.stop_recording(cx);
 58        self.update_editor(cx, |_, editor, cx| {
 59            let text_layout_details = editor.text_layout_details(window);
 60            editor.transact(window, cx, |editor, window, cx| {
 61                let mut selection_starts: HashMap<_, _> = Default::default();
 62                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 63                    s.move_with(|map, selection| {
 64                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
 65                        selection_starts.insert(selection.id, anchor);
 66                        motion.expand_selection(
 67                            map,
 68                            selection,
 69                            times,
 70                            &text_layout_details,
 71                            forced_motion,
 72                        );
 73                    });
 74                });
 75                editor.rewrap_impl(
 76                    RewrapOptions {
 77                        override_language_settings: true,
 78                        ..Default::default()
 79                    },
 80                    cx,
 81                );
 82                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 83                    s.move_with(|map, selection| {
 84                        let anchor = selection_starts.remove(&selection.id).unwrap();
 85                        let mut point = anchor.to_display_point(map);
 86                        *point.column_mut() = 0;
 87                        selection.collapse_to(point, SelectionGoal::None);
 88                    });
 89                });
 90            });
 91        });
 92    }
 93
 94    pub(crate) fn rewrap_object(
 95        &mut self,
 96        object: Object,
 97        around: bool,
 98        times: Option<usize>,
 99        window: &mut Window,
100        cx: &mut Context<Self>,
101    ) {
102        self.stop_recording(cx);
103        self.update_editor(cx, |_, editor, cx| {
104            editor.transact(window, cx, |editor, window, cx| {
105                let mut original_positions: HashMap<_, _> = Default::default();
106                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
107                    s.move_with(|map, selection| {
108                        let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
109                        original_positions.insert(selection.id, anchor);
110                        object.expand_selection(map, selection, around, times);
111                    });
112                });
113                editor.rewrap_impl(
114                    RewrapOptions {
115                        override_language_settings: true,
116                        ..Default::default()
117                    },
118                    cx,
119                );
120                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
121                    s.move_with(|map, selection| {
122                        let anchor = original_positions.remove(&selection.id).unwrap();
123                        let mut point = anchor.to_display_point(map);
124                        *point.column_mut() = 0;
125                        selection.collapse_to(point, SelectionGoal::None);
126                    });
127                });
128            });
129        });
130    }
131}
132
133#[cfg(test)]
134mod test {
135    use crate::test::NeovimBackedTestContext;
136
137    #[gpui::test]
138    async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
139        let mut cx = NeovimBackedTestContext::new(cx).await;
140        cx.set_neovim_option("shiftwidth=4").await;
141
142        cx.set_shared_state("ˇhello\nworld\n").await;
143        cx.simulate_shared_keystrokes("v j > g v").await;
144        cx.shared_state()
145            .await
146            .assert_eq("«    hello\n ˇ»   world\n");
147    }
148}