change_list.rs

  1use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll};
  2use gpui::{Context, Window, actions};
  3
  4use crate::{Vim, state::Mode};
  5
  6actions!(vim, [ChangeListOlder, ChangeListNewer]);
  7
  8pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
  9    Vim::action(editor, cx, |vim, _: &ChangeListOlder, window, cx| {
 10        vim.move_to_change(Direction::Prev, window, cx);
 11    });
 12    Vim::action(editor, cx, |vim, _: &ChangeListNewer, window, cx| {
 13        vim.move_to_change(Direction::Next, window, cx);
 14    });
 15}
 16
 17impl Vim {
 18    fn move_to_change(
 19        &mut self,
 20        direction: Direction,
 21        window: &mut Window,
 22        cx: &mut Context<Self>,
 23    ) {
 24        let count = Vim::take_count(cx).unwrap_or(1);
 25        Vim::take_forced_motion(cx);
 26        self.update_editor(window, cx, |_, editor, window, cx| {
 27            if let Some(selections) = editor
 28                .change_list
 29                .next_change(count, direction)
 30                .map(|s| s.to_vec())
 31            {
 32                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 33                    let map = s.display_map();
 34                    s.select_display_ranges(selections.iter().map(|a| {
 35                        let point = a.to_display_point(&map);
 36                        point..point
 37                    }))
 38                })
 39            };
 40        });
 41    }
 42
 43    pub(crate) fn push_to_change_list(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 44        let Some((new_positions, buffer)) = self.update_editor(window, cx, |vim, editor, _, cx| {
 45            let (map, selections) = editor.selections.all_adjusted_display(cx);
 46            let buffer = editor.buffer().clone();
 47
 48            let pop_state = editor
 49                .change_list
 50                .last()
 51                .map(|previous| {
 52                    previous.len() == selections.len()
 53                        && previous.iter().enumerate().all(|(ix, p)| {
 54                            p.to_display_point(&map).row() == selections[ix].head().row()
 55                        })
 56                })
 57                .unwrap_or(false);
 58
 59            let new_positions = selections
 60                .into_iter()
 61                .map(|s| {
 62                    let point = if vim.mode == Mode::Insert {
 63                        movement::saturating_left(&map, s.head())
 64                    } else {
 65                        s.head()
 66                    };
 67                    map.display_point_to_anchor(point, Bias::Left)
 68                })
 69                .collect::<Vec<_>>();
 70
 71            editor
 72                .change_list
 73                .push_to_change_list(pop_state, new_positions.clone());
 74
 75            (new_positions, buffer)
 76        }) else {
 77            return;
 78        };
 79
 80        self.set_mark(".".to_string(), new_positions, &buffer, window, cx)
 81    }
 82}
 83
 84#[cfg(test)]
 85mod test {
 86    use indoc::indoc;
 87
 88    use crate::{state::Mode, test::NeovimBackedTestContext};
 89
 90    #[gpui::test]
 91    async fn test_change_list_insert(cx: &mut gpui::TestAppContext) {
 92        let mut cx = NeovimBackedTestContext::new(cx).await;
 93
 94        cx.set_shared_state("ˇ").await;
 95
 96        cx.simulate_shared_keystrokes("i 1 1 escape shift-o 2 2 escape shift-g o 3 3 escape")
 97            .await;
 98
 99        cx.shared_state().await.assert_eq(indoc! {
100            "22
101             11
102             3ˇ3"
103        });
104
105        cx.simulate_shared_keystrokes("g ;").await;
106        // NOTE: this matches nvim when I type it into it
107        // but in tests, nvim always reports the column as 0...
108        cx.assert_state(
109            indoc! {
110            "22
111             11
112             3ˇ3"
113            },
114            Mode::Normal,
115        );
116        cx.simulate_shared_keystrokes("g ;").await;
117        cx.assert_state(
118            indoc! {
119            "2ˇ2
120             11
121             33"
122            },
123            Mode::Normal,
124        );
125        cx.simulate_shared_keystrokes("g ;").await;
126        cx.assert_state(
127            indoc! {
128            "22
129             1ˇ1
130             33"
131            },
132            Mode::Normal,
133        );
134        cx.simulate_shared_keystrokes("g ,").await;
135        cx.assert_state(
136            indoc! {
137            "2ˇ2
138             11
139             33"
140            },
141            Mode::Normal,
142        );
143        cx.simulate_shared_keystrokes("shift-g i 4 4 escape").await;
144        cx.simulate_shared_keystrokes("g ;").await;
145        cx.assert_state(
146            indoc! {
147            "22
148             11
149             34ˇ43"
150            },
151            Mode::Normal,
152        );
153        cx.simulate_shared_keystrokes("g ;").await;
154        cx.assert_state(
155            indoc! {
156            "2ˇ2
157             11
158             3443"
159            },
160            Mode::Normal,
161        );
162    }
163
164    #[gpui::test]
165    async fn test_change_list_delete(cx: &mut gpui::TestAppContext) {
166        let mut cx = NeovimBackedTestContext::new(cx).await;
167        cx.set_shared_state(indoc! {
168        "one two
169        three fˇour"})
170            .await;
171        cx.simulate_shared_keystrokes("x k d i w ^ x").await;
172        cx.shared_state().await.assert_eq(indoc! {
173        "ˇne•
174        three fur"});
175        cx.simulate_shared_keystrokes("2 g ;").await;
176        cx.shared_state().await.assert_eq(indoc! {
177        "ne•
178        three fˇur"});
179        cx.simulate_shared_keystrokes("g ,").await;
180        cx.shared_state().await.assert_eq(indoc! {
181        "ˇne•
182        three fur"});
183    }
184
185    #[gpui::test]
186    async fn test_gi(cx: &mut gpui::TestAppContext) {
187        let mut cx = NeovimBackedTestContext::new(cx).await;
188        cx.set_shared_state(indoc! {
189        "one two
190        three fˇr"})
191            .await;
192        cx.simulate_shared_keystrokes("i o escape k g i").await;
193        cx.simulate_shared_keystrokes("u escape").await;
194        cx.shared_state().await.assert_eq(indoc! {
195        "one two
196        three foˇur"});
197    }
198
199    #[gpui::test]
200    async fn test_dot_mark(cx: &mut gpui::TestAppContext) {
201        let mut cx = NeovimBackedTestContext::new(cx).await;
202        cx.set_shared_state(indoc! {
203        "one two
204        three fˇr"})
205            .await;
206        cx.simulate_shared_keystrokes("i o escape k ` .").await;
207        cx.shared_state().await.assert_eq(indoc! {
208        "one two
209        three fˇor"});
210    }
211}