Skip to main content

nvrim/diagnostics/
formatter.rs

1//! Diagnostic formatting helpers.
2//!
3//! Converts raw LSP diagnostics plus embedded `user_data` into concise messages with source / code.
4//! Missing required fields trigger user notifications and yield `None`.
5
6use serde::Deserialize;
7
8/// Formats a diagnostic into a human-readable string.
9#[allow(clippy::needless_pass_by_value)]
10pub fn format(diagnostic: Diagnostic) -> Option<String> {
11    let Some(msg) = get_msg(&diagnostic).map(|s| s.trim_end_matches('.').to_string()) else {
12        ytil_noxi::notify::error(format!("error missing diagnostic message | diagnostic={diagnostic:#?}"));
13        return None;
14    };
15
16    let Some(src) = get_src(&diagnostic).map(str::to_string) else {
17        ytil_noxi::notify::error(format!("error missing diagnostic source | diagnostic={diagnostic:#?}"));
18        return None;
19    };
20
21    let src_and_code = get_code(&diagnostic).map_or_else(|| src.clone(), |c| format!("{src}: {c}"));
22
23    Some(format!("▶ {msg} [{src_and_code}]"))
24}
25
26/// Extracts LSP diagnostic message from [`LspData::rendered`] or directly from the supplied [`Diagnostic`].
27fn get_msg(diag: &Diagnostic) -> Option<&str> {
28    diag.user_data
29        .as_ref()
30        .and_then(|user_data| {
31            user_data
32                .lsp
33                .as_ref()
34                .and_then(|lsp| {
35                    lsp.data
36                        .as_ref()
37                        .and_then(|lsp_data| lsp_data.rendered.as_deref())
38                        .or(lsp.message.as_deref())
39                })
40                .or(diag.message.as_deref())
41        })
42        .or(diag.message.as_deref())
43}
44
45/// Extracts the "source" from [`Diagnostic::user_data`] or [`Diagnostic::source`].
46fn get_src(diag: &Diagnostic) -> Option<&str> {
47    diag.user_data
48        .as_ref()
49        .and_then(|user_data| user_data.lsp.as_ref().and_then(|lsp| lsp.source.as_deref()))
50        .or(diag.source.as_deref())
51}
52
53/// Extracts the "code" from [`Diagnostic::user_data`] or [`Diagnostic::code`].
54fn get_code(diag: &Diagnostic) -> Option<&str> {
55    diag.user_data
56        .as_ref()
57        .and_then(|user_data| user_data.lsp.as_ref().and_then(|lsp| lsp.code.as_deref()))
58        .or(diag.code.as_deref())
59}
60
61/// Represents a diagnostic from Nvim.
62#[derive(Debug, Deserialize)]
63pub struct Diagnostic {
64    /// The diagnostic code.
65    code: Option<String>,
66    /// The diagnostic message.
67    message: Option<String>,
68    /// The source of the diagnostic.
69    source: Option<String>,
70    /// Additional user data.
71    user_data: Option<UserData>,
72}
73
74ytil_noxi::impl_nvim_deserializable!(Diagnostic);
75
76/// User data associated with a diagnostic.
77#[derive(Debug, Deserialize)]
78pub struct UserData {
79    /// LSP-specific diagnostic payload injected by Nvim.
80    lsp: Option<Lsp>,
81}
82
83/// LSP data within user data.
84#[derive(Debug, Deserialize)]
85pub struct Lsp {
86    /// The diagnostic code.
87    code: Option<String>,
88    /// Additional LSP data.
89    data: Option<LspData>,
90    /// The diagnostic message.
91    message: Option<String>,
92    /// The source of the diagnostic.
93    source: Option<String>,
94}
95
96/// Additional LSP data.
97#[derive(Debug, Deserialize)]
98pub struct LspData {
99    rendered: Option<String>,
100}