1use std::sync::{Arc, Mutex, OnceLock};
13
14use lattice_grammar::Effect;
15use lattice_mode::{
16 ActionContext, ActionHandlerContribution, BufferStoreHandle, CapabilitySet, Keymap,
17 KeymapEntry, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind, OptionOverrideSet,
18 keymap_entry,
19};
20
21use crate::{NotificationId, NotificationStoreHandle, notification_at, render_buffer};
22
23async fn replace_buffer_text(handle: &Arc<dyn lattice_runtime::Document>, text: String) {
27 use lattice_protocol::edit::Edit;
28 use lattice_protocol::position::{Position, Range};
29 let snap = handle.snapshot();
30 let last = snap.buffer.rope_line_count().saturating_sub(1);
36 let last_line = snap.buffer.line(last).unwrap_or_default();
37 let end = Position::new(last, last_line.len() as u32);
38 let _ = handle
39 .apply_edit_batch(vec![Edit::replace(
40 Range::new(Position::new(0, 0), end),
41 text,
42 )])
43 .await;
44}
45
46pub(crate) async fn rerender(
49 store: &NotificationStoreHandle,
50 rows: Option<&RowMapHandle>,
51 handle: &Arc<dyn lattice_runtime::Document>,
52) {
53 let (text, row_map) = render_buffer(store);
54 if let Some(rows) = rows {
55 rows.set(row_map);
56 }
57 replace_buffer_text(handle, text).await;
58}
59
60pub struct NotificationsMode;
61
62impl NotificationsMode {
63 pub fn mode_id() -> ModeId {
64 ModeId::new("notifications-mode")
65 }
66}
67
68fn keymap_entries() -> &'static [KeymapEntry] {
69 static ENTRIES: OnceLock<Vec<KeymapEntry>> = OnceLock::new();
70 ENTRIES.get_or_init(|| {
71 vec![
72 keymap_entry! { mode: Normal, chord: "<CR>", doc: "Run the action for the notification at cursor", cmd: "action:notification-run" },
73 keymap_entry! { mode: Normal, chord: "d", doc: "Dismiss the notification at cursor", cmd: "action:notification-dismiss" },
74 ]
75 })
76}
77
78#[derive(Default)]
84pub struct RowMap(Mutex<Vec<Option<NotificationId>>>);
85
86pub type RowMapHandle = Arc<RowMap>;
87
88impl RowMap {
89 fn set(&self, rows: Vec<Option<NotificationId>>) {
90 if let Ok(mut slot) = self.0.lock() {
91 *slot = rows;
92 }
93 }
94 fn at(&self, line: u32) -> Option<NotificationId> {
95 let rows = self.0.lock().ok()?;
96 notification_at(&rows, line)
97 }
98}
99
100fn store(ctx: &ActionContext<'_>) -> Option<NotificationStoreHandle> {
101 ctx.services
102 .get::<NotificationStoreHandle>()
103 .map(|outer| (*outer).clone())
104}
105
106fn rows(ctx: &ActionContext<'_>) -> Option<RowMapHandle> {
107 ctx.services.get::<RowMapHandle>().map(|o| (*o).clone())
108}
109
110impl Mode for NotificationsMode {
111 type Guard = ();
112
113 fn id(&self) -> ModeId {
114 Self::mode_id()
115 }
116 fn kind(&self) -> ModeKind {
117 ModeKind::Major
118 }
119 fn target_buffer_kind(&self) -> Option<lattice_core::BufferKind> {
120 None
121 }
122
123 fn options(&self) -> OptionOverrideSet {
124 lattice_config::overrides! {
125 lattice_config::ReadOnly = true,
126 lattice_config::NoFile = true,
127 }
128 }
129
130 fn required_capabilities(&self) -> CapabilitySet {
131 CapabilitySet::empty()
132 }
133 fn keymap(&self) -> Keymap {
134 Keymap::from_entries(keymap_entries())
135 }
136
137 fn refresh_action(&self) -> Option<&'static str> {
145 Some("action:notification-refresh")
146 }
147
148 fn action_handlers(&self) -> Vec<ActionHandlerContribution> {
149 vec![
150 ActionHandlerContribution {
156 action_name: "action:notification-run",
157 handler: Arc::new(|ctx: &ActionContext<'_>| {
158 let id = rows(ctx)?.at(ctx.cursor.line)?;
159 let store = store(ctx)?;
160 let action = store
161 .all()
162 .into_iter()
163 .find(|n| n.id == id)?
164 .actions
165 .into_iter()
166 .next()?;
167 Some(action.effect)
168 }),
169 },
170 ActionHandlerContribution {
171 action_name: "action:notification-dismiss",
172 handler: Arc::new(|ctx: &ActionContext<'_>| {
173 let id = rows(ctx)?.at(ctx.cursor.line)?;
174 store(ctx)?.dismiss(id);
175 Some(Effect::OpenSyntheticBuffer {
176 name: BUFFER_NAME.to_string(),
177 mode_id: NotificationsMode::mode_id().as_str().to_string(),
178 content: None,
179 cursor: None,
180 activate_minor: None,
181 })
182 }),
183 },
184 ActionHandlerContribution {
185 action_name: "action:notification-refresh",
186 handler: Arc::new(|_ctx: &ActionContext<'_>| {
187 Some(Effect::OpenSyntheticBuffer {
188 name: BUFFER_NAME.to_string(),
189 mode_id: NotificationsMode::mode_id().as_str().to_string(),
190 content: None,
191 cursor: None,
192 activate_minor: None,
193 })
194 }),
195 },
196 ]
197 }
198
199 fn on_activate(&self, ctx: ModeContext) -> LifecycleFuture<'_, Self::Guard> {
200 Box::pin(async move {
201 let buffer_id = lattice_core::BufferId(ctx.buffer_id().0 as u32);
202 let Some(bufs) = ctx.service::<BufferStoreHandle>() else {
203 return Ok(());
204 };
205 let Some(handle) = bufs.handle_for(buffer_id) else {
206 return Ok(());
207 };
208 let Some(store) = ctx.service::<NotificationStoreHandle>() else {
209 return Ok(());
210 };
211 let rows = ctx.service::<RowMapHandle>().map(|r| (*r).clone());
212 rerender(&store, rows.as_ref(), &handle).await;
213 Ok(())
214 })
215 }
216}
217
218pub const BUFFER_NAME: &str = "*notifications*";