reovim_plugin_notification/
lib.rs1pub 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
40pub 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
52struct 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
106struct 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
153struct 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
208pub 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 #[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 let _ = ctx.register_command(NotificationDismissAll);
251
252 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 registry.register(Arc::clone(&self.manager));
263
264 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 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 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 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 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 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}