change_list.rs

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