reovim-protocol 0.14.4

Wire protocol types for reovim client-server communication
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
//! Notification type names and payloads.
//!
//! This module defines notification types sent by the server to clients.
//! Notifications are one-way messages that don't expect a response.

use serde::{Deserialize, Serialize};

use super::{
    results::ScreenContentResult,
    types::{BufferId, Position, ScreenFormat, WireLayoutChangeKind, WireLayoutInfo},
};

// Notification type name constants

/// Mode changed notification.
pub const MODE_CHANGED: &str = "notification/mode_changed";

/// Cursor moved notification.
pub const CURSOR_MOVED: &str = "notification/cursor_moved";

/// Buffer modified notification.
pub const BUFFER_MODIFIED: &str = "notification/buffer_modified";

/// Render complete notification.
pub const RENDER_COMPLETE: &str = "notification/render_complete";

/// Log entry notification (for log streaming).
pub const LOG_ENTRY: &str = "notification/log_entry";

/// Detach notification (client should disconnect).
pub const DETACH: &str = "notification/detach";

/// Layout changed notification.
pub const LAYOUT_CHANGED: &str = "notification/layout_changed";

/// Option changed notification.
///
/// Sent to clients when an editor option changes.
pub const OPTION_CHANGED: &str = "notification/option_changed";

/// Cmdline changed notification (#451).
///
/// Sent to clients when cmdline state changes (show/hide/input update).
pub const CMDLINE_CHANGED: &str = "notification/cmdline_changed";

/// TUI capture request notification.
///
/// Sent by server to TUI client to request a frame capture.
pub const CAPTURE_REQUEST: &str = "tui/capture-request";

/// TUI capture response notification.
///
/// Sent by TUI client to server with the captured frame content.
pub const CAPTURE_RESPONSE: &str = "tui/capture-response";

// Notification payload types

/// Source of a log entry.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LogSource {
    /// Log from the server.
    #[default]
    Server,
    /// Log from the client.
    Client,
}

/// Log level for filtering and display.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
    /// Trace level (most verbose).
    Trace = 0,
    /// Debug level.
    Debug = 1,
    /// Info level.
    #[default]
    Info = 2,
    /// Warning level.
    Warn = 3,
    /// Error level (least verbose).
    Error = 4,
}

impl LogLevel {
    /// Parse a log level from a string.
    #[must_use]
    pub fn from_str_lossy(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "trace" => Self::Trace,
            "debug" => Self::Debug,
            "warn" | "warning" => Self::Warn,
            "error" => Self::Error,
            // "info" and anything else defaults to Info
            _ => Self::Info,
        }
    }
}

/// Payload for log entry notification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntryPayload {
    /// Timestamp in ISO 8601 format.
    pub timestamp: String,
    /// Log level.
    pub level: LogLevel,
    /// Target module.
    pub target: String,
    /// Log message.
    pub message: String,
    /// Source of the log entry.
    #[serde(default)]
    pub source: LogSource,
}

impl LogEntryPayload {
    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `LogEntryPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            LOG_ENTRY,
            serde_json::to_value(self).expect("LogEntryPayload serialization cannot fail"),
        )
    }
}

/// Payload for mode changed notification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModeChangedPayload {
    /// The new mode information.
    pub mode: super::types::ModeInfo,
}

impl ModeChangedPayload {
    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `ModeChangedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            MODE_CHANGED,
            serde_json::to_value(self).expect("ModeChangedPayload serialization cannot fail"),
        )
    }
}

/// Payload for cursor moved notification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CursorMovedPayload {
    /// The buffer where cursor moved.
    pub buffer_id: BufferId,
    /// The new cursor position.
    pub position: Position,
}

impl CursorMovedPayload {
    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `CursorMovedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            CURSOR_MOVED,
            serde_json::to_value(self).expect("CursorMovedPayload serialization cannot fail"),
        )
    }
}

/// Payload for buffer modified notification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BufferModifiedPayload {
    /// The buffer that was modified.
    pub buffer_id: BufferId,
    /// Whether the buffer has unsaved changes.
    pub modified: bool,
}

impl BufferModifiedPayload {
    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `BufferModifiedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            BUFFER_MODIFIED,
            serde_json::to_value(self).expect("BufferModifiedPayload serialization cannot fail"),
        )
    }
}

/// Payload for render complete notification.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RenderCompletePayload {
    // Empty for now, can add timing info later
}

impl RenderCompletePayload {
    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `RenderCompletePayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            RENDER_COMPLETE,
            serde_json::to_value(self).expect("RenderCompletePayload serialization cannot fail"),
        )
    }
}

/// Payload for detach notification.
///
/// Sent to clients when they should disconnect gracefully.
/// The server continues running after detach.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DetachPayload {
    /// Optional reason for detach.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
}

impl DetachPayload {
    /// Create a new detach payload.
    #[must_use]
    pub const fn new() -> Self {
        Self { reason: None }
    }

    /// Create a detach payload with a reason.
    #[must_use]
    pub fn with_reason(reason: impl Into<String>) -> Self {
        Self {
            reason: Some(reason.into()),
        }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `DetachPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            DETACH,
            serde_json::to_value(self).expect("DetachPayload serialization cannot fail"),
        )
    }
}

/// Payload for layout changed notification.
///
/// Sent to clients when the window layout changes (split, close, focus, resize).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayoutChangedPayload {
    /// Type of layout change that occurred.
    pub kind: WireLayoutChangeKind,
    /// Full layout state after the change.
    pub layout: WireLayoutInfo,
}

impl LayoutChangedPayload {
    /// Create a new layout changed payload.
    #[must_use]
    pub const fn new(kind: WireLayoutChangeKind, layout: WireLayoutInfo) -> Self {
        Self { kind, layout }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `LayoutChangedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            LAYOUT_CHANGED,
            serde_json::to_value(self).expect("LayoutChangedPayload serialization cannot fail"),
        )
    }
}

/// Payload for option changed notification (#445).
///
/// Sent to clients when an editor option changes value.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionChangedPayload {
    /// Name of the option that changed.
    pub name: String,
    /// New value of the option (bool, int, or string).
    pub value: serde_json::Value,
    /// Window ID if this is a window-scoped change, None for global.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub window_id: Option<usize>,
}

impl OptionChangedPayload {
    /// Create a new option changed payload for a global option.
    #[must_use]
    pub fn global(name: impl Into<String>, value: serde_json::Value) -> Self {
        Self {
            name: name.into(),
            value,
            window_id: None,
        }
    }

    /// Create a new option changed payload for a window-scoped option.
    #[must_use]
    pub fn window(name: impl Into<String>, value: serde_json::Value, window_id: usize) -> Self {
        Self {
            name: name.into(),
            value,
            window_id: Some(window_id),
        }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `OptionChangedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            OPTION_CHANGED,
            serde_json::to_value(self).expect("OptionChangedPayload serialization cannot fail"),
        )
    }
}

/// Prompt type for cmdline.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WireCmdlinePrompt {
    /// Ex command prompt (`:`)
    #[default]
    Command,
    /// Forward search prompt (`/`)
    SearchForward,
    /// Backward search prompt (`?`)
    SearchBackward,
}

impl WireCmdlinePrompt {
    /// Get the prompt character for display.
    #[must_use]
    pub const fn char(self) -> char {
        match self {
            Self::Command => ':',
            Self::SearchForward => '/',
            Self::SearchBackward => '?',
        }
    }
}

/// Payload for cmdline changed notification (#451).
///
/// Sent to clients when cmdline state changes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CmdlineChangedPayload {
    /// Whether cmdline is visible/active.
    pub visible: bool,
    /// The prompt type (`:`, `/`, `?`).
    pub prompt: WireCmdlinePrompt,
    /// Current input text.
    pub input: String,
    /// Cursor position within input.
    pub cursor: usize,
}

impl CmdlineChangedPayload {
    /// Create a payload for showing cmdline.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)] // String is not const-compatible
    pub fn show(prompt: WireCmdlinePrompt, input: String, cursor: usize) -> Self {
        Self {
            visible: true,
            prompt,
            input,
            cursor,
        }
    }

    /// Create a payload for hiding cmdline.
    #[must_use]
    pub const fn hide() -> Self {
        Self {
            visible: false,
            prompt: WireCmdlinePrompt::Command,
            input: String::new(),
            cursor: 0,
        }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `CmdlineChangedPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            CMDLINE_CHANGED,
            serde_json::to_value(self).expect("CmdlineChangedPayload serialization cannot fail"),
        )
    }
}

/// Payload for TUI capture request notification (#447).
///
/// Sent by server to TUI client to request a frame capture.
/// TUI responds with `CaptureResponsePayload`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CaptureRequestPayload {
    /// Correlation ID to match request with response.
    pub request_id: u64,
    /// Requested output format.
    #[serde(default)]
    pub format: ScreenFormat,
}

impl CaptureRequestPayload {
    /// Create a new capture request payload.
    #[must_use]
    pub const fn new(request_id: u64, format: ScreenFormat) -> Self {
        Self { request_id, format }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `CaptureRequestPayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            CAPTURE_REQUEST,
            serde_json::to_value(self).expect("CaptureRequestPayload serialization cannot fail"),
        )
    }
}

/// Payload for TUI capture response notification (#447).
///
/// Sent by TUI client to server with the captured frame content.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureResponsePayload {
    /// Correlation ID matching the original request.
    pub request_id: u64,
    /// Captured frame content.
    pub result: ScreenContentResult,
}

impl CaptureResponsePayload {
    /// Create a new capture response payload.
    #[must_use]
    pub const fn new(request_id: u64, result: ScreenContentResult) -> Self {
        Self { request_id, result }
    }

    /// Convert payload to JSON-RPC notification.
    ///
    /// # Panics
    ///
    /// This function will not panic as `CaptureResponsePayload` serialization is infallible.
    #[must_use]
    pub fn into_notification(self) -> super::messages::RpcNotification {
        super::messages::RpcNotification::new(
            CAPTURE_RESPONSE,
            serde_json::to_value(self).expect("CaptureResponsePayload serialization cannot fail"),
        )
    }
}

#[cfg(test)]
#[path = "notifications_tests.rs"]
mod tests;