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        window: &mut Window,
187        cx: &mut Context<Self>,
188    ) {
189        self.pop_operator(window, cx);
190        let mark = self
191            .update_editor(window, cx, |vim, editor, window, cx| {
192                vim.get_mark(&text, editor, window, cx)
193            })
194            .flatten();
195        let anchors = match mark {
196            None => None,
197            Some(Mark::Local(anchors)) => Some(anchors),
198            Some(Mark::Buffer(entity_id, anchors)) => {
199                self.open_buffer_mark(line, entity_id, anchors, window, cx);
200                return;
201            }
202            Some(Mark::Path(path, points)) => {
203                self.open_path_mark(line, path, points, window, cx);
204                return;
205            }
206        };
207
208        let Some(mut anchors) = anchors else { return };
209
210        let is_active_operator = self.active_operator().is_some();
211        if is_active_operator {
212            if let Some(anchor) = anchors.last() {
213                self.motion(
214                    Motion::Jump {
215                        anchor: *anchor,
216                        line,
217                    },
218                    window,
219                    cx,
220                )
221            }
222        } else {
223            // Save the last anchor so as to jump to it later.
224            let anchor: Option<Anchor> = anchors.last_mut().map(|anchor| *anchor);
225            let should_jump = self.mode == Mode::Visual
226                || self.mode == Mode::VisualLine
227                || self.mode == Mode::VisualBlock;
228
229            self.update_editor(window, cx, |_, editor, window, cx| {
230                let map = editor.snapshot(window, cx);
231                let mut ranges: Vec<Range<Anchor>> = Vec::new();
232                for mut anchor in anchors {
233                    if line {
234                        let mut point = anchor.to_display_point(&map.display_snapshot);
235                        point = motion::first_non_whitespace(&map.display_snapshot, false, point);
236                        anchor = map
237                            .display_snapshot
238                            .buffer_snapshot
239                            .anchor_before(point.to_point(&map.display_snapshot));
240                    }
241
242                    if ranges.last() != Some(&(anchor..anchor)) {
243                        ranges.push(anchor..anchor);
244                    }
245                }
246
247                if !should_jump && !ranges.is_empty() {
248                    editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
249                        s.select_anchor_ranges(ranges)
250                    });
251                }
252            });
253
254            if should_jump {
255                if let Some(anchor) = anchor {
256                    self.motion(Motion::Jump { anchor, line }, window, cx)
257                }
258            }
259        }
260    }
261
262    pub fn set_mark(
263        &mut self,
264        name: String,
265        anchors: Vec<Anchor>,
266        buffer_entity: &Entity<MultiBuffer>,
267        window: &mut Window,
268        cx: &mut App,
269    ) {
270        let Some(workspace) = self.workspace(window) else {
271            return;
272        };
273        let entity_id = workspace.entity_id();
274        Vim::update_globals(cx, |vim_globals, cx| {
275            let Some(marks_state) = vim_globals.marks.get(&entity_id) else {
276                return;
277            };
278            marks_state.update(cx, |ms, cx| {
279                ms.set_mark(name.clone(), buffer_entity, anchors, cx);
280            });
281        });
282    }
283
284    pub fn get_mark(
285        &self,
286        name: &str,
287        editor: &mut Editor,
288        window: &mut Window,
289        cx: &mut App,
290    ) -> Option<Mark> {
291        if matches!(name, "{" | "}" | "(" | ")") {
292            let (map, selections) = editor.selections.all_display(cx);
293            let anchors = selections
294                .into_iter()
295                .map(|selection| {
296                    let point = match name {
297                        "{" => movement::start_of_paragraph(&map, selection.head(), 1),
298                        "}" => movement::end_of_paragraph(&map, selection.head(), 1),
299                        "(" => motion::sentence_backwards(&map, selection.head(), 1),
300                        ")" => motion::sentence_forwards(&map, selection.head(), 1),
301                        _ => unreachable!(),
302                    };
303                    map.buffer_snapshot
304                        .anchor_before(point.to_offset(&map, Bias::Left))
305                })
306                .collect::<Vec<Anchor>>();
307            return Some(Mark::Local(anchors));
308        }
309        VimGlobals::update_global(cx, |globals, cx| {
310            let workspace_id = self.workspace(window)?.entity_id();
311            globals
312                .marks
313                .get_mut(&workspace_id)?
314                .update(cx, |ms, cx| ms.get_mark(name, editor.buffer(), cx))
315        })
316    }
317}
318
319pub fn jump_motion(
320    map: &DisplaySnapshot,
321    anchor: Anchor,
322    line: bool,
323) -> (DisplayPoint, SelectionGoal) {
324    let mut point = anchor.to_display_point(map);
325    if line {
326        point = motion::first_non_whitespace(map, false, point)
327    }
328
329    (point, SelectionGoal::None)
330}