Skip to main content

fresh/app/
bookmark_actions.rs

1//! Bookmark orchestrators.
2//!
3//! Pure window-state mutations (`set_bookmark`, `clear_bookmark`,
4//! `list_bookmarks`) live on `impl Window`. The cross-cutting
5//! `jump_to_bookmark` stays on `impl Editor` because its body needs
6//! orchestration helpers (`set_active_buffer`,
7//! `apply_event_to_active_buffer`, `ensure_active_cursor_visible_for_navigation`)
8//! that have not yet moved to `impl Window` — those orchestrators
9//! still fire plugin hooks via `Editor.plugin_manager`. Once
10//! plugin-hook firing is available on `Window`, `jump_to_bookmark`
11//! becomes a Window method too.
12
13use rust_i18n::t;
14
15use crate::model::event::Event;
16
17use super::Editor;
18
19impl crate::app::window::Window {
20    /// Set bookmark at the active buffer's primary cursor position.
21    pub fn set_bookmark(&mut self, key: char) {
22        let buffer_id = self.active_buffer();
23        let position = self.active_cursors().primary().position;
24        self.bookmarks.set(
25            key,
26            crate::app::bookmarks::Bookmark {
27                buffer_id,
28                position,
29            },
30        );
31        self.set_status_message(t!("bookmark.set", key = key).to_string());
32    }
33
34    /// Clear a bookmark by key.
35    pub fn clear_bookmark(&mut self, key: char) {
36        if self.bookmarks.remove(key) {
37            self.set_status_message(t!("bookmark.cleared", key = key).to_string());
38        } else {
39            self.set_status_message(t!("bookmark.not_set", key = key).to_string());
40        }
41    }
42
43    /// Show the list of bookmarks in the status bar. Bookmarks pointing
44    /// at buffers that no longer exist render as "unknown".
45    pub fn list_bookmarks(&mut self) {
46        if self.bookmarks.is_empty() {
47            self.set_status_message(t!("bookmark.none_set").to_string());
48            return;
49        }
50
51        let mut bookmark_list: Vec<(char, crate::app::bookmarks::Bookmark)> =
52            self.bookmarks.iter().collect();
53        bookmark_list.sort_by_key(|(k, _)| *k);
54
55        let list_str: String = bookmark_list
56            .iter()
57            .map(|(k, bm)| {
58                let buffer_name = self
59                    .buffer_metadata
60                    .get(&bm.buffer_id)
61                    .map(|m| m.display_name.as_str())
62                    .unwrap_or("unknown");
63                format!("'{}': {} @ {}", k, buffer_name, bm.position)
64            })
65            .collect::<Vec<_>>()
66            .join(", ");
67
68        self.set_status_message(t!("bookmark.list", list = list_str).to_string());
69    }
70}
71
72impl Editor {
73    /// Jump to a bookmark.
74    ///
75    /// Stays on `impl Editor` because the body fires plugin hooks
76    /// (`apply_event_to_active_buffer`) and orchestrates cross-cutting
77    /// state (active-buffer switch, viewport recentering). Moving it
78    /// to `impl Window` waits for plugin-hook firing to be available
79    /// from `Window`.
80    pub(super) fn jump_to_bookmark(&mut self, key: char) {
81        let Some(bookmark) = self.active_window_mut().bookmarks.get(key) else {
82            self.set_status_message(t!("bookmark.not_set", key = key).to_string());
83            return;
84        };
85
86        // Switch to the buffer if needed, or forget the bookmark if it's gone.
87        if bookmark.buffer_id != self.active_buffer() {
88            if self
89                .windows
90                .get(&self.active_window)
91                .map(|w| &w.buffers)
92                .expect("active window present")
93                .contains_key(&bookmark.buffer_id)
94            {
95                self.set_active_buffer(bookmark.buffer_id);
96            } else {
97                self.set_status_message(t!("bookmark.buffer_gone", key = key).to_string());
98                self.active_window_mut().bookmarks.remove(key);
99                return;
100            }
101        }
102
103        // Move cursor to bookmark position
104        let cursor = *self.active_cursors().primary();
105        let cursor_id = self.active_cursors().primary_id();
106        let state = self.active_state_mut();
107        let new_pos = bookmark.position.min(state.buffer.len());
108
109        let event = Event::MoveCursor {
110            cursor_id,
111            old_position: cursor.position,
112            new_position: new_pos,
113            old_anchor: cursor.anchor,
114            new_anchor: None,
115            old_sticky_column: cursor.sticky_column,
116            new_sticky_column: 0,
117        };
118
119        self.active_event_log_mut().append(event.clone());
120        self.apply_event_to_active_buffer(&event);
121        // Bookmarks can point anywhere in the file; the viewport must scroll
122        // to follow the jump even when the bookmark target is in the same
123        // buffer that's already visible (#1689).
124        self.active_window_mut()
125            .ensure_active_cursor_visible_for_navigation(true);
126        self.set_status_message(t!("bookmark.jumped", key = key).to_string());
127    }
128}