mark.rs

  1use std::{ops::Range, path::Path, sync::Arc};
  2
  3use editor::{
  4    display_map::{DisplaySnapshot, ToDisplayPoint},
  5    movement,
  6    scroll::Autoscroll,
  7    Anchor, Bias, DisplayPoint, Editor, MultiBuffer,
  8};
  9use gpui::{Context, Entity, EntityId, UpdateGlobal, Window};
 10use language::SelectionGoal;
 11use text::Point;
 12use ui::App;
 13use workspace::OpenOptions;
 14
 15use crate::{
 16    motion::{self, Motion},
 17    state::{Mark, Mode, VimGlobals},
 18    Vim,
 19};
 20
 21impl Vim {
 22    pub fn create_mark(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
 23        self.update_editor(window, cx, |vim, editor, window, cx| {
 24            let anchors = editor
 25                .selections
 26                .disjoint_anchors()
 27                .iter()
 28                .map(|s| s.head())
 29                .collect::<Vec<_>>();
 30            vim.set_mark(text.to_string(), anchors, editor.buffer(), window, cx);
 31        });
 32        self.clear_operator(window, cx);
 33    }
 34
 35    // When handling an action, you must create visual marks if you will switch to normal
 36    // mode without the default selection behavior.
 37    pub(crate) fn store_visual_marks(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 38        if self.mode.is_visual() {
 39            self.create_visual_marks(self.mode, window, cx);
 40        }
 41    }
 42
 43    pub(crate) fn create_visual_marks(
 44        &mut self,
 45        mode: Mode,
 46        window: &mut Window,
 47        cx: &mut Context<Self>,
 48    ) {
 49        let mut starts = vec![];
 50        let mut ends = vec![];
 51        let mut reversed = vec![];
 52
 53        self.update_editor(window, cx, |vim, editor, window, cx| {
 54            let (map, selections) = editor.selections.all_display(cx);
 55            for selection in selections {
 56                let end = movement::saturating_left(&map, selection.end);
 57                ends.push(
 58                    map.buffer_snapshot
 59                        .anchor_before(end.to_offset(&map, Bias::Left)),
 60                );
 61                starts.push(
 62                    map.buffer_snapshot
 63                        .anchor_before(selection.start.to_offset(&map, Bias::Left)),
 64                );
 65                reversed.push(selection.reversed)
 66            }
 67            vim.set_mark("<".to_string(), starts, editor.buffer(), window, cx);
 68            vim.set_mark(">".to_string(), ends, editor.buffer(), window, cx);
 69        });
 70
 71        self.stored_visual_mode.replace((mode, reversed));
 72    }
 73
 74    fn open_buffer_mark(
 75        &mut self,
 76        line: bool,
 77        entity_id: EntityId,
 78        anchors: Vec<Anchor>,
 79        window: &mut Window,
 80        cx: &mut Context<Self>,
 81    ) {
 82        let Some(workspace) = self.workspace(window) else {
 83            return;
 84        };
 85        workspace.update(cx, |workspace, cx| {
 86            let item = workspace.items(cx).find(|item| {
 87                item.act_as::<Editor>(cx)
 88                    .is_some_and(|editor| editor.read(cx).buffer().entity_id() == entity_id)
 89            });
 90            let Some(item) = item.cloned() else {
 91                return;
 92            };
 93            if let Some(pane) = workspace.pane_for(item.as_ref()) {
 94                pane.update(cx, |pane, cx| {
 95                    if let Some(index) = pane.index_for_item(item.as_ref()) {
 96                        pane.activate_item(index, true, true, window, cx);
 97                    }
 98                });
 99            };
100
101            item.act_as::<Editor>(cx).unwrap().update(cx, |editor, cx| {
102                let map = editor.snapshot(window, cx);
103                let mut ranges: Vec<Range<Anchor>> = Vec::new();
104                for mut anchor in anchors {
105                    if line {
106                        let mut point = anchor.to_display_point(&map.display_snapshot);
107                        point = motion::first_non_whitespace(&map.display_snapshot, false, point);
108                        anchor = map
109                            .display_snapshot
110                            .buffer_snapshot
111                            .anchor_before(point.to_point(&map.display_snapshot));
112                    }
113
114                    if ranges.last() != Some(&(anchor..anchor)) {
115                        ranges.push(anchor..anchor);
116                    }
117                }
118
119                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
120                    s.select_anchor_ranges(ranges)
121                });
122            })
123        });
124        return;
125    }
126
127    fn open_path_mark(
128        &mut self,
129        line: bool,
130        path: Arc<Path>,
131        points: Vec<Point>,
132        window: &mut Window,
133        cx: &mut Context<Self>,
134    ) {
135        let Some(workspace) = self.workspace(window) else {
136            return;
137        };
138        let task = workspace.update(cx, |workspace, cx| {
139            workspace.open_abs_path(
140                path.to_path_buf(),
141                OpenOptions {
142                    visible: Some(workspace::OpenVisible::All),
143                    focus: Some(true),
144                    ..Default::default()
145                },
146                window,
147                cx,
148            )
149        });
150        cx.spawn_in(window, async move |this, cx| {
151            let editor = task.await?;
152            this.update_in(cx, |_, window, cx| {
153                if let Some(editor) = editor.act_as::<Editor>(cx) {
154                    editor.update(cx, |editor, cx| {
155                        let map = editor.snapshot(window, cx);
156                        let points: Vec<_> = points
157                            .into_iter()
158                            .map(|p| {
159                                if line {
160                                    let point = p.to_display_point(&map.display_snapshot);
161                                    motion::first_non_whitespace(
162                                        &map.display_snapshot,
163                                        false,
164                                        point,
165                                    )
166                                    .to_point(&map.display_snapshot)
167                                } else {
168                                    p
169                                }
170                            })
171                            .collect();
172                        editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
173                            s.select_ranges(points.into_iter().map(|p| p..p))
174                        })
175                    })
176                }
177            })
178        })
179        .detach_and_log_err(cx);
180    }
181
182    pub fn jump(
183        &mut self,
184        text: Arc<str>,
185        line: bool,
186        should_pop_operator: bool,
187        window: &mut Window,
188        cx: &mut Context<Self>,
189    ) {
190        if should_pop_operator {
191            self.pop_operator(window, cx);
192        }
193        let mark = self
194            .update_editor(window, cx, |vim, editor, window, cx| {
195                vim.get_mark(&text, editor, window, cx)
196            })
197            .flatten();
198        let anchors = match mark {
199            None => None,
200            Some(Mark::Local(anchors)) => Some(anchors),
201            Some(Mark::Buffer(entity_id, anchors)) => {
202                self.open_buffer_mark(line, entity_id, anchors, window, cx);
203                return;
204            }
205            Some(Mark::Path(path, points)) => {
206                self.open_path_mark(line, path, points, window, cx);
207                return;
208            }
209        };
210
211        let Some(mut anchors) = anchors else { return };
212
213        let is_active_operator = self.active_operator().is_some();
214        if is_active_operator {
215            if let Some(anchor) = anchors.last() {
216                self.motion(
217                    Motion::Jump {
218                        anchor: *anchor,
219                        line,
220                    },
221                    window,
222                    cx,
223                )
224            }
225        } else {
226            // Save the last anchor so as to jump to it later.
227            let anchor: Option<Anchor> = anchors.last_mut().map(|anchor| *anchor);
228            let should_jump = self.mode == Mode::Visual
229                || self.mode == Mode::VisualLine
230                || self.mode == Mode::VisualBlock;
231
232            self.update_editor(window, cx, |_, editor, window, cx| {
233                let map = editor.snapshot(window, cx);
234                let mut ranges: Vec<Range<Anchor>> = Vec::new();
235                for mut anchor in anchors {
236                    if line {
237                        let mut point = anchor.to_display_point(&map.display_snapshot);
238                        point = motion::first_non_whitespace(&map.display_snapshot, false, point);
239                        anchor = map
240                            .display_snapshot
241                            .buffer_snapshot
242                            .anchor_before(point.to_point(&map.display_snapshot));
243                    }
244
245                    if ranges.last() != Some(&(anchor..anchor)) {
246                        ranges.push(anchor..anchor);
247                    }
248                }
249
250                if !should_jump && !ranges.is_empty() {
251                    editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
252                        s.select_anchor_ranges(ranges)
253                    });
254                }
255            });
256
257            if should_jump {
258                if let Some(anchor) = anchor {
259                    self.motion(Motion::Jump { anchor, line }, window, cx)
260                }
261            }
262        }
263    }
264
265    pub fn set_mark(
266        &mut self,
267        name: String,
268        anchors: Vec<Anchor>,
269        buffer_entity: &Entity<MultiBuffer>,
270        window: &mut Window,
271        cx: &mut App,
272    ) {
273        let Some(workspace) = self.workspace(window) else {
274            return;
275        };
276        let entity_id = workspace.entity_id();
277        Vim::update_globals(cx, |vim_globals, cx| {
278            let Some(marks_state) = vim_globals.marks.get(&entity_id) else {
279                return;
280            };
281            marks_state.update(cx, |ms, cx| {
282                ms.set_mark(name.clone(), buffer_entity, anchors, cx);
283            });
284        });
285    }
286
287    pub fn get_mark(
288        &self,
289        name: &str,
290        editor: &mut Editor,
291        window: &mut Window,
292        cx: &mut App,
293    ) -> Option<Mark> {
294        if matches!(name, "{" | "}" | "(" | ")") {
295            let (map, selections) = editor.selections.all_display(cx);
296            let anchors = selections
297                .into_iter()
298                .map(|selection| {
299                    let point = match name {
300                        "{" => movement::start_of_paragraph(&map, selection.head(), 1),
301                        "}" => movement::end_of_paragraph(&map, selection.head(), 1),
302                        "(" => motion::sentence_backwards(&map, selection.head(), 1),
303                        ")" => motion::sentence_forwards(&map, selection.head(), 1),
304                        _ => unreachable!(),
305                    };
306                    map.buffer_snapshot
307                        .anchor_before(point.to_offset(&map, Bias::Left))
308                })
309                .collect::<Vec<Anchor>>();
310            return Some(Mark::Local(anchors));
311        }
312        VimGlobals::update_global(cx, |globals, cx| {
313            let workspace_id = self.workspace(window)?.entity_id();
314            globals
315                .marks
316                .get_mut(&workspace_id)?
317                .update(cx, |ms, cx| ms.get_mark(name, editor.buffer(), cx))
318        })
319    }
320}
321
322pub fn jump_motion(
323    map: &DisplaySnapshot,
324    anchor: Anchor,
325    line: bool,
326) -> (DisplayPoint, SelectionGoal) {
327    let mut point = anchor.to_display_point(map);
328    if line {
329        point = motion::first_non_whitespace(map, false, point)
330    }
331
332    (point, SelectionGoal::None)
333}