reovim_plugin_notification/
lib.rs

1//! Toast notification and progress bar plugin for reovim
2//!
3//! This plugin provides non-blocking notifications and progress indicators.
4//!
5//! # Usage
6//!
7//! Other plugins can emit notification events:
8//!
9//! ```ignore
10//! use reovim_plugin_notification::{NotificationShow, ProgressUpdate};
11//!
12//! // Show a notification
13//! bus.emit(NotificationShow::success("File saved"));
14//!
15//! // Show progress
16//! bus.emit(ProgressUpdate::new("build", "Building", "cargo").with_progress(45));
17//! ```
18
19pub mod command;
20pub mod notification;
21pub mod progress;
22pub mod state;
23pub mod style;
24pub mod window;
25
26use std::{any::TypeId, sync::Arc};
27
28use reovim_core::{
29    event_bus::{EventBus, EventResult},
30    plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
31    rpc::{RpcHandler, RpcHandlerContext, RpcResult},
32};
33
34use {
35    command::{NotificationDismiss, NotificationDismissAll, NotificationShow},
36    state::SharedNotificationManager,
37    window::NotificationPluginWindow,
38};
39
40// Re-export for external use
41pub use {
42    command::{
43        NotificationDismissAll as DismissAll, NotificationShow as Show, ProgressComplete,
44        ProgressUpdate,
45    },
46    notification::{Notification, NotificationLevel},
47    progress::{ProgressBarConfig, ProgressNotification},
48    state::{NotificationConfig, NotificationManagerHandle, NotificationPosition},
49    style::NotificationStyles,
50};
51
52/// RPC handler for state/notification queries
53struct NotificationStateHandler;
54
55impl RpcHandler for NotificationStateHandler {
56    fn method(&self) -> &'static str {
57        "state/notification"
58    }
59
60    fn handle(&self, _params: &serde_json::Value, ctx: &RpcHandlerContext) -> RpcResult {
61        let state = ctx
62            .with_state::<Arc<SharedNotificationManager>, _, _>(|manager| {
63                let notifications = manager.notifications();
64                let progress_items = manager.progress_items();
65
66                serde_json::json!({
67                    "notification_count": notifications.len(),
68                    "progress_count": progress_items.len(),
69                    "has_visible": manager.has_visible(),
70                    "notifications": notifications.iter().map(|n| {
71                        serde_json::json!({
72                            "id": n.id,
73                            "message": n.message,
74                            "level": format!("{:?}", n.level),
75                            "source": n.source,
76                        })
77                    }).collect::<Vec<_>>(),
78                    "progress": progress_items.iter().map(|p| {
79                        serde_json::json!({
80                            "id": p.id,
81                            "title": p.title,
82                            "source": p.source,
83                            "progress": p.progress,
84                            "detail": p.detail,
85                        })
86                    }).collect::<Vec<_>>(),
87                })
88            })
89            .unwrap_or_else(|| {
90                serde_json::json!({
91                    "notification_count": 0,
92                    "progress_count": 0,
93                    "has_visible": false,
94                    "notifications": [],
95                    "progress": [],
96                })
97            });
98        RpcResult::Success(state)
99    }
100
101    fn description(&self) -> &'static str {
102        "Get notification plugin state"
103    }
104}
105
106/// RPC handler for triggering test notifications
107struct NotificationShowHandler;
108
109impl RpcHandler for NotificationShowHandler {
110    fn method(&self) -> &'static str {
111        "notification/show"
112    }
113
114    fn handle(&self, params: &serde_json::Value, ctx: &RpcHandlerContext) -> RpcResult {
115        let message = params
116            .get("message")
117            .and_then(serde_json::Value::as_str)
118            .unwrap_or("Test notification");
119
120        let level = match params.get("level").and_then(serde_json::Value::as_str) {
121            Some("success") => notification::NotificationLevel::Success,
122            Some("warning") => notification::NotificationLevel::Warning,
123            Some("error") => notification::NotificationLevel::Error,
124            _ => notification::NotificationLevel::Info,
125        };
126
127        let duration_ms = params
128            .get("duration_ms")
129            .and_then(serde_json::Value::as_u64);
130
131        let source = params
132            .get("source")
133            .and_then(serde_json::Value::as_str)
134            .map(String::from);
135
136        let result = ctx.with_state::<Arc<SharedNotificationManager>, _, _>(|manager| {
137            manager.show(level, message.to_string(), duration_ms, source);
138            true
139        });
140
141        if result.unwrap_or(false) {
142            RpcResult::ok()
143        } else {
144            RpcResult::internal_error("Notification manager not available")
145        }
146    }
147
148    fn description(&self) -> &'static str {
149        "Show a test notification"
150    }
151}
152
153/// RPC handler for triggering test progress updates
154struct ProgressUpdateHandler;
155
156impl RpcHandler for ProgressUpdateHandler {
157    fn method(&self) -> &'static str {
158        "notification/progress"
159    }
160
161    fn handle(&self, params: &serde_json::Value, ctx: &RpcHandlerContext) -> RpcResult {
162        let id = params
163            .get("id")
164            .and_then(serde_json::Value::as_str)
165            .unwrap_or("test")
166            .to_string();
167
168        let title = params
169            .get("title")
170            .and_then(serde_json::Value::as_str)
171            .unwrap_or("Test Progress")
172            .to_string();
173
174        let source = params
175            .get("source")
176            .and_then(serde_json::Value::as_str)
177            .unwrap_or("test")
178            .to_string();
179
180        #[allow(clippy::cast_possible_truncation)]
181        let progress = params
182            .get("progress")
183            .and_then(serde_json::Value::as_u64)
184            .map(|p| p as u8);
185
186        let detail = params
187            .get("detail")
188            .and_then(serde_json::Value::as_str)
189            .map(String::from);
190
191        let result = ctx.with_state::<Arc<SharedNotificationManager>, _, _>(|manager| {
192            manager.update_progress(id, title, source, progress, detail);
193            true
194        });
195
196        if result.unwrap_or(false) {
197            RpcResult::ok()
198        } else {
199            RpcResult::internal_error("Notification manager not available")
200        }
201    }
202
203    fn description(&self) -> &'static str {
204        "Update a progress notification"
205    }
206}
207
208/// Notification plugin
209///
210/// Provides toast notifications and progress bars.
211pub struct NotificationPlugin {
212    manager: Arc<SharedNotificationManager>,
213}
214
215impl Default for NotificationPlugin {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl NotificationPlugin {
222    /// Create a new notification plugin
223    #[must_use]
224    pub fn new() -> Self {
225        Self {
226            manager: Arc::new(SharedNotificationManager::new()),
227        }
228    }
229}
230
231impl Plugin for NotificationPlugin {
232    fn id(&self) -> PluginId {
233        PluginId::new("reovim:notification")
234    }
235
236    fn name(&self) -> &'static str {
237        "Notification"
238    }
239
240    fn description(&self) -> &'static str {
241        "Toast notifications and progress bars"
242    }
243
244    fn dependencies(&self) -> Vec<TypeId> {
245        vec![]
246    }
247
248    fn build(&self, ctx: &mut PluginContext) {
249        // Register dismiss all command
250        let _ = ctx.register_command(NotificationDismissAll);
251
252        // Register RPC handlers for testing
253        ctx.register_rpc_handler(Arc::new(NotificationStateHandler));
254        ctx.register_rpc_handler(Arc::new(NotificationShowHandler));
255        ctx.register_rpc_handler(Arc::new(ProgressUpdateHandler));
256
257        tracing::debug!("NotificationPlugin: registered commands and RPC handlers");
258    }
259
260    fn init_state(&self, registry: &PluginStateRegistry) {
261        // Register the manager in plugin state
262        registry.register(Arc::clone(&self.manager));
263
264        // Register the plugin window (it accesses manager from the registry)
265        registry.register_plugin_window(Arc::new(NotificationPluginWindow::new()));
266
267        tracing::debug!("NotificationPlugin: registered window and state");
268    }
269
270    fn subscribe(&self, bus: &EventBus, _state: Arc<PluginStateRegistry>) {
271        // Subscribe to show notification events
272        let manager = Arc::clone(&self.manager);
273        bus.subscribe::<NotificationShow, _>(100, move |event, ctx| {
274            manager.show(
275                event.level,
276                event.message.clone(),
277                event.duration_ms,
278                event.source.clone(),
279            );
280            ctx.request_render();
281            EventResult::Handled
282        });
283
284        // Subscribe to dismiss events
285        let manager = Arc::clone(&self.manager);
286        bus.subscribe::<NotificationDismiss, _>(100, move |event, ctx| {
287            manager.dismiss(&event.id);
288            ctx.request_render();
289            EventResult::Handled
290        });
291
292        // Subscribe to dismiss all command
293        let manager = Arc::clone(&self.manager);
294        bus.subscribe::<NotificationDismissAll, _>(100, move |_event, ctx| {
295            manager.dismiss_all();
296            ctx.request_render();
297            EventResult::Handled
298        });
299
300        // Subscribe to progress update events
301        let manager = Arc::clone(&self.manager);
302        bus.subscribe::<ProgressUpdate, _>(100, move |event, ctx| {
303            manager.update_progress(
304                event.id.clone(),
305                event.title.clone(),
306                event.source.clone(),
307                event.progress,
308                event.detail.clone(),
309            );
310            ctx.request_render();
311            EventResult::Handled
312        });
313
314        // Subscribe to progress complete events
315        let manager = Arc::clone(&self.manager);
316        bus.subscribe::<ProgressComplete, _>(100, move |event, ctx| {
317            manager.complete_progress(&event.id, event.message.clone());
318            ctx.request_render();
319            EventResult::Handled
320        });
321
322        tracing::debug!("NotificationPlugin: subscribed to events");
323    }
324}