Skip to repository content405 lines · 13.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:33:33.377Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
bookmarks.rs
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