Skip to main content

reovim_driver_session/
notification_queue.rs

1//! Thread-safe pending notification queue.
2//!
3//! Background threads (LSP completion, snippet reload) push notifications here.
4//! A synchronous command handler drains the queue into `NotificationState` on
5//! each invocation.
6//!
7//! This avoids the problem that background threads lack `SessionRuntime`
8//! access needed for `NotificationState`.
9//!
10//! # Architecture
11//!
12//! This lives in the driver layer (mechanism) because multiple modules
13//! need to push notifications without depending on each other:
14//! - `completion` module: LSP completion results
15//! - `snippet` module: reload/catalog notifications
16//! - `lsp` module: `$/progress`, `window/showMessage`
17//! - `notification` module: drains into display state
18
19use {parking_lot::Mutex, reovim_kernel::api::v1::Service};
20
21/// Notification level for pending notifications.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PendingLevel {
24    /// Informational message.
25    Info,
26    /// Success confirmation.
27    Success,
28    /// Warning message.
29    Warning,
30    /// Error message.
31    Error,
32}
33
34/// Operation type for the pending notification queue.
35///
36/// Extends the original flat push with progress lifecycle operations
37/// for `$/progress` and similar producer protocols (#691).
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum PendingOp {
40    /// Simple notification (existing behavior).
41    Push {
42        /// Severity level.
43        level: PendingLevel,
44        /// Short title.
45        title: String,
46    },
47    /// Begin a progress notification.
48    ProgressBegin {
49        /// Opaque token from the producer (e.g., LSP progress token).
50        token: String,
51        /// Short title (e.g., "Indexing").
52        title: String,
53        /// Optional detail message.
54        message: String,
55        /// Initial percentage (0..=100), or 0 if indeterminate.
56        percentage: u8,
57    },
58    /// Update a progress notification.
59    ProgressReport {
60        /// Token matching a previous `ProgressBegin`.
61        token: String,
62        /// Updated detail message (if any).
63        message: Option<String>,
64        /// Updated percentage (if any).
65        percentage: Option<u8>,
66    },
67    /// End a progress notification.
68    ProgressEnd {
69        /// Token matching a previous `ProgressBegin`.
70        token: String,
71        /// Optional final message.
72        message: Option<String>,
73    },
74}
75
76/// A pending notification entry with optional source attribution.
77///
78/// Wraps a [`PendingOp`] with an optional source tag for producer grouping.
79/// The source identifies the notification producer (e.g., `"rust-analyzer"`,
80/// `"git"`) so the TUI can group notifications into bordered boxes (#691).
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct PendingEntry {
83    /// Optional producer name for grouped display.
84    pub source: Option<String>,
85    /// The notification operation.
86    pub op: PendingOp,
87}
88
89/// A pending notification to be flushed to display state.
90///
91/// Retained for backward compatibility. New code should use
92/// [`PendingEntry`] + [`PendingOp`] via [`PendingNotificationQueue::push_op`].
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct PendingNotification {
95    /// Severity level.
96    pub level: PendingLevel,
97    /// Short title.
98    pub title: String,
99}
100
101/// Thread-safe queue for notifications from background threads.
102///
103/// Registered as a [`Service`] in `ServiceRegistry` so that both the
104/// background threads (which have `Arc<ServiceRegistry>`) and the
105/// command handler (which has `SessionRuntime`) can access it.
106#[derive(Debug)]
107pub struct PendingNotificationQueue {
108    queue: Mutex<Vec<PendingEntry>>,
109}
110
111impl PendingNotificationQueue {
112    /// Create a new empty queue.
113    #[must_use]
114    pub const fn new() -> Self {
115        Self {
116            queue: Mutex::new(Vec::new()),
117        }
118    }
119
120    /// Push a simple notification from any thread.
121    ///
122    /// Convenience method for sourceless push operations.
123    /// For source-tagged or progress notifications, use [`push_op`](Self::push_op).
124    pub fn push(&self, level: PendingLevel, title: impl Into<String>) {
125        self.queue.lock().push(PendingEntry {
126            source: None,
127            op: PendingOp::Push {
128                level,
129                title: title.into(),
130            },
131        });
132    }
133
134    /// Push a notification operation with optional source attribution.
135    pub fn push_op(&self, source: Option<String>, op: PendingOp) {
136        self.queue.lock().push(PendingEntry { source, op });
137    }
138
139    /// Drain all pending entries.
140    ///
141    /// Called from the command handler context where `SessionRuntime`
142    /// is available to forward them to the display system.
143    pub fn drain(&self) -> Vec<PendingEntry> {
144        let mut queue = self.queue.lock();
145        std::mem::take(&mut *queue)
146    }
147
148    /// Number of pending entries.
149    #[must_use]
150    pub fn len(&self) -> usize {
151        self.queue.lock().len()
152    }
153
154    /// Whether the queue is empty.
155    #[must_use]
156    pub fn is_empty(&self) -> bool {
157        self.queue.lock().is_empty()
158    }
159}
160
161impl Default for PendingNotificationQueue {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl Service for PendingNotificationQueue {}
168#[cfg(test)]
169#[path = "notification_queue_tests.rs"]
170mod tests;