Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:33:33.377Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

bookmarks.rs

405 lines · 13.2 KB · rust
1use std::ops::Range;
2
3use gpui::Entity;
4use language::Buffer;
5use multi_buffer::{Anchor, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _};
6use project::{Project, bookmark_store::BookmarkStore};
7use rope::Point;
8use text::Bias;
9use ui::{Context, Window};
10use util::ResultExt as _;
11use workspace::{Workspace, searchable::Direction};
12
13use crate::display_map::DisplayRow;
14use crate::{
15    EditBookmark, Editor, GoToNextBookmark, GoToPreviousBookmark, MultibufferSelectionMode,
16    SelectionEffects, ToggleBookmark, ToggleBookmarkWithLabel, ViewBookmarks, scroll::Autoscroll,
17};
18
19#[derive(Clone, Debug)]
20struct BookmarkTarget {
21    buffer: Entity<Buffer>,
22    anchor: Anchor,
23    buffer_anchor: text::Anchor,
24}
25
26impl Editor {
27    fn bookmark_exists_for_target(
28        bookmark_store: &Entity<BookmarkStore>,
29        target: &BookmarkTarget,
30        cx: &mut Context<Self>,
31    ) -> bool {
32        bookmark_store.update(cx, |bookmark_store, cx| {
33            bookmark_store
34                .find_bookmark(&target.buffer, target.buffer_anchor, cx)
35                .is_some()
36        })
37    }
38
39    pub fn set_show_bookmarks(&mut self, show_bookmarks: bool, cx: &mut Context<Self>) {
40        self.show_bookmarks = Some(show_bookmarks);
41        cx.notify();
42    }
43
44    pub fn toggle_bookmark(
45        &mut self,
46        _: &ToggleBookmark,
47        window: &mut Window,
48        cx: &mut Context<Self>,
49    ) {
50        self.toggle_bookmark_impl(false, window, cx);
51    }
52
53    pub fn toggle_bookmark_with_label(
54        &mut self,
55        _: &ToggleBookmarkWithLabel,
56        window: &mut Window,
57        cx: &mut Context<Self>,
58    ) {
59        self.toggle_bookmark_impl(true, window, cx);
60    }
61
62    fn toggle_bookmark_impl(
63        &mut self,
64        with_label: bool,
65        window: &mut Window,
66        cx: &mut Context<Self>,
67    ) {
68        let Some(bookmark_store) = self.bookmark_store.clone() else {
69            return;
70        };
71        let Some(project) = self.project() else {
72            return;
73        };
74
75        let snapshot = self.snapshot(window, cx);
76        let multi_buffer_snapshot = snapshot.buffer_snapshot();
77
78        let mut selections = self.selections.all::<Point>(&snapshot.display_snapshot);
79        selections.sort_by_key(|s| s.head());
80        selections.dedup_by_key(|s| s.head().row);
81
82        let mut exist_targets: Vec<BookmarkTarget> = vec![];
83        let mut absent_targets: Vec<BookmarkTarget> = vec![];
84
85        for selection in &selections {
86            let head = selection.head();
87            let multibuffer_anchor = multi_buffer_snapshot.anchor_before(Point::new(head.row, 0));
88
89            if let Some((buffer_anchor, _)) =
90                multi_buffer_snapshot.anchor_to_buffer_anchor(multibuffer_anchor)
91            {
92                let buffer_id = buffer_anchor.buffer_id;
93                if let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) {
94                    let target = BookmarkTarget {
95                        buffer,
96                        anchor: multibuffer_anchor,
97                        buffer_anchor,
98                    };
99
100                    if Self::bookmark_exists_for_target(&bookmark_store, &target, cx) {
101                        exist_targets.push(target);
102                    } else {
103                        absent_targets.push(target);
104                    }
105                }
106            }
107        }
108
109        if absent_targets.is_empty() {
110            // All cursors are on existing bookmarks, remove all bookmarks.
111            self.toggle_bookmarks(exist_targets, String::new(), cx);
112        } else if with_label {
113            // Only add new ones (prompting for a label) and leave existing ones unchanged.
114            self.add_toggle_bookmark_blocks(absent_targets, bookmark_store, window, cx);
115        } else {
116            // Only add new (unnamed) bookmarks and leave existing ones unchanged.
117            self.toggle_bookmarks(absent_targets, String::new(), cx);
118        }
119
120        cx.notify();
121    }
122
123    pub fn toggle_bookmark_at_row(&mut self, row: DisplayRow, cx: &mut Context<Self>) {
124        let display_snapshot = self.display_snapshot(cx);
125        let point = display_snapshot.display_point_to_point(row.as_display_point(), Bias::Left);
126        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
127        let anchor = buffer_snapshot.anchor_before(point);
128
129        self.toggle_bookmark_at_anchor(anchor, cx);
130    }
131
132    pub fn toggle_bookmark_at_anchor(&mut self, anchor: Anchor, cx: &mut Context<Self>) {
133        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
134        let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(anchor) else {
135            return;
136        };
137        let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
138            return;
139        };
140
141        let Some(bookmark_store) = self.bookmark_store.clone() else {
142            return;
143        };
144
145        bookmark_store.update(cx, |bookmark_store, cx| {
146            bookmark_store.toggle_bookmark(buffer, position, String::new(), cx);
147        });
148
149        cx.notify();
150    }
151
152    pub fn edit_bookmark(&mut self, _: &EditBookmark, window: &mut Window, cx: &mut Context<Self>) {
153        let snapshot = self.snapshot(window, cx);
154        let multi_buffer_snapshot = snapshot.buffer_snapshot();
155        let selection = self
156            .selections
157            .newest::<Point>(&snapshot.display_snapshot)
158            .head();
159        let anchor = multi_buffer_snapshot.anchor_before(Point::new(selection.row, 0));
160        self.edit_bookmark_at_anchor(anchor, window, cx);
161    }
162
163    pub fn edit_bookmark_at_anchor(
164        &mut self,
165        anchor: Anchor,
166        window: &mut Window,
167        cx: &mut Context<Self>,
168    ) {
169        let Some(bookmark_store) = self.bookmark_store.clone() else {
170            return;
171        };
172        let Some(project) = self.project() else {
173            return;
174        };
175
176        let editor_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
177        let Some((buffer_anchor, _)) = editor_buffer_snapshot.anchor_to_buffer_anchor(anchor)
178        else {
179            return;
180        };
181        let Some(buffer) = project.read(cx).buffer_for_id(buffer_anchor.buffer_id, cx) else {
182            return;
183        };
184        let Some(label) = bookmark_store.update(cx, |store, cx| {
185            store
186                .find_bookmark(&buffer, buffer_anchor, cx)
187                .map(|bookmark| bookmark.label.clone())
188        }) else {
189            return;
190        };
191
192        self.add_edit_bookmark_block(
193            BookmarkTarget {
194                anchor,
195                buffer,
196                buffer_anchor,
197            },
198            &label,
199            bookmark_store,
200            window,
201            cx,
202        );
203    }
204
205    fn add_edit_bookmark_block(
206        &mut self,
207        target: BookmarkTarget,
208        label: &str,
209        bookmark_store: Entity<BookmarkStore>,
210        window: &mut Window,
211        cx: &mut Context<Self>,
212    ) {
213        self.add_edit_block(
214            target.anchor,
215            label,
216            "Enter bookmark label (Optional)",
217            Some(Box::new(move |label, _, cx| {
218                bookmark_store.update(cx, |store, cx| {
219                    store.edit_bookmark(&target.buffer, target.buffer_anchor, label, cx)
220                });
221            })),
222            None,
223            window,
224            cx,
225        );
226    }
227
228    fn add_toggle_bookmark_blocks(
229        &mut self,
230        targets: Vec<BookmarkTarget>,
231        bookmark_store: Entity<BookmarkStore>,
232        window: &mut Window,
233        cx: &mut Context<Self>,
234    ) {
235        for target in targets {
236            let bookmark_store = bookmark_store.clone();
237            self.add_edit_block(
238                target.anchor,
239                "",
240                "Enter bookmark label (Optional)",
241                Some(Box::new(move |label: String, _, cx| {
242                    bookmark_store.update(cx, |store, cx| {
243                        store.toggle_bookmark(target.buffer, target.buffer_anchor, label, cx);
244                    });
245                })),
246                None,
247                window,
248                cx,
249            );
250        }
251    }
252
253    fn toggle_bookmarks(
254        &mut self,
255        targets: Vec<BookmarkTarget>,
256        label: String,
257        cx: &mut Context<Self>,
258    ) {
259        if let Some(bookmark_store) = self.bookmark_store.clone() {
260            bookmark_store.update(cx, |store, cx| {
261                for target in targets {
262                    store.toggle_bookmark(target.buffer, target.buffer_anchor, label.clone(), cx);
263                }
264            });
265        }
266    }
267
268    pub fn go_to_next_bookmark(
269        &mut self,
270        _: &GoToNextBookmark,
271        window: &mut Window,
272        cx: &mut Context<Self>,
273    ) {
274        self.go_to_bookmark_impl(Direction::Next, window, cx);
275    }
276
277    pub fn go_to_previous_bookmark(
278        &mut self,
279        _: &GoToPreviousBookmark,
280        window: &mut Window,
281        cx: &mut Context<Self>,
282    ) {
283        self.go_to_bookmark_impl(Direction::Prev, window, cx);
284    }
285
286    fn go_to_bookmark_impl(
287        &mut self,
288        direction: Direction,
289        window: &mut Window,
290        cx: &mut Context<Self>,
291    ) {
292        let Some(project) = &self.project else {
293            return;
294        };
295        let Some(bookmark_store) = &self.bookmark_store else {
296            return;
297        };
298
299        let selection = self
300            .selections
301            .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
302        let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
303
304        let mut all_bookmarks = Self::bookmarks_in_range(
305            MultiBufferOffset(0)..multi_buffer_snapshot.len(),
306            &multi_buffer_snapshot,
307            project,
308            bookmark_store,
309            cx,
310        );
311        all_bookmarks.sort_by_key(|a| a.to_offset(&multi_buffer_snapshot));
312
313        let anchor = match direction {
314            Direction::Next => all_bookmarks
315                .iter()
316                .find(|anchor| anchor.to_offset(&multi_buffer_snapshot) > selection.head())
317                .or_else(|| all_bookmarks.first()),
318            Direction::Prev => all_bookmarks
319                .iter()
320                .rfind(|anchor| anchor.to_offset(&multi_buffer_snapshot) < selection.head())
321                .or_else(|| all_bookmarks.last()),
322        }
323        .cloned();
324
325        if let Some(anchor) = anchor {
326            self.unfold_ranges(&[anchor..anchor], true, false, cx);
327            self.change_selections(
328                SelectionEffects::scroll(Autoscroll::center()),
329                window,
330                cx,
331                |s| {
332                    s.select_anchor_ranges([anchor..anchor]);
333                },
334            );
335        }
336    }
337
338    pub fn view_bookmarks(
339        workspace: &mut Workspace,
340        _: &ViewBookmarks,
341        window: &mut Window,
342        cx: &mut Context<Workspace>,
343    ) {
344        let bookmark_store = workspace.project().read(cx).bookmark_store();
345        cx.spawn_in(window, async move |workspace, cx| {
346            let Some(locations) = BookmarkStore::all_bookmark_locations(bookmark_store, cx)
347                .await
348                .log_err()
349            else {
350                return;
351            };
352
353            workspace
354                .update_in(cx, |workspace, window, cx| {
355                    Editor::open_locations_in_multibuffer(
356                        workspace,
357                        locations,
358                        "Bookmarks".into(),
359                        false,
360                        false,
361                        MultibufferSelectionMode::First,
362                        window,
363                        cx,
364                    );
365                })
366                .log_err();
367        })
368        .detach();
369    }
370
371    fn bookmarks_in_range(
372        range: Range<MultiBufferOffset>,
373        multi_buffer_snapshot: &MultiBufferSnapshot,
374        project: &Entity<Project>,
375        bookmark_store: &Entity<BookmarkStore>,
376        cx: &mut Context<Self>,
377    ) -> Vec<Anchor> {
378        multi_buffer_snapshot
379            .range_to_buffer_ranges(range)
380            .into_iter()
381            .flat_map(|(buffer_snapshot, buffer_range, _excerpt_range)| {
382                let Some(buffer) = project
383                    .read(cx)
384                    .buffer_for_id(buffer_snapshot.remote_id(), cx)
385                else {
386                    return Vec::new();
387                };
388                bookmark_store
389                    .update(cx, |store, cx| {
390                        store.bookmarks_for_buffer(
391                            buffer,
392                            buffer_snapshot.anchor_before(buffer_range.start)
393                                ..buffer_snapshot.anchor_after(buffer_range.end),
394                            &buffer_snapshot,
395                            cx,
396                        )
397                    })
398                    .into_iter()
399                    .filter_map(|bookmark| multi_buffer_snapshot.anchor_in_buffer(bookmark.anchor))
400                    .collect::<Vec<_>>()
401            })
402            .collect()
403    }
404}
405
Served at tenant.openagents/omega Member data and write actions are omitted.