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