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
use miette::{Diagnostic, NamedSource, SourceSpan};
use thiserror::Error;

use super::{sexpr::Span, *};

pub type MResult<T> = miette::Result<T>;
pub type Result<T> = std::result::Result<T, ParseError>;

#[derive(Debug, Clone)]
pub struct ParseError {
    pub msg: String,
    pub span: Option<Span>,
}

impl ParseError {
    pub fn new(span: Span, err_msg: impl AsRef<str>) -> Self {
        Self {
            msg: err_msg.as_ref().to_string(),
            span: Some(span),
        }
    }

    pub fn new_without_span(err_msg: impl AsRef<str>) -> Self {
        Self {
            msg: err_msg.as_ref().to_string(),
            span: None,
        }
    }

    pub fn from_expr(expr: &sexpr::SExpr, err_msg: impl AsRef<str>) -> Self {
        Self::new(expr.span(), err_msg)
    }

    pub fn from_spanned<T>(spanned: &Spanned<T>, err_msg: impl AsRef<str>) -> Self {
        Self::new(spanned.span.clone(), err_msg)
    }
}

impl From<anyhow::Error> for ParseError {
    fn from(value: anyhow::Error) -> Self {
        Self::new_without_span(value.to_string())
    }
}

impl From<ParseError> for miette::Error {
    fn from(val: ParseError) -> Self {
        let diagnostic = CfgError {
            err_span: val
                .span
                .as_ref()
                .map(|s| SourceSpan::new(s.start().into(), (s.end() - s.start()).into())),
            help_msg: help(val.msg),
            file_name: val.span.as_ref().map(|s| s.file_name()),
            file_content: val.span.as_ref().map(|s| s.file_content()),
        };

        let report: miette::Error = diagnostic.into();

        if let Some(span) = val.span {
            report.with_source_code(NamedSource::new(span.file_name(), span.file_content()))
        } else {
            report
        }
    }
}

#[derive(Error, Debug, Diagnostic, Clone)]
#[error("Error in configuration file")]
#[diagnostic()]
struct CfgError {
    // Snippets and highlights can be included in the diagnostic!
    #[label("Error here")]
    err_span: Option<SourceSpan>,
    #[help]
    help_msg: String,
    file_name: Option<String>,
    file_content: Option<String>,
}

pub(super) fn help(err_msg: impl AsRef<str>) -> String {
    format!(
        r"{}

For more info, see the configuration guide or ask in GitHub discussions.
    guide : https://github.com/jtroo/kanata/blob/main/docs/config.adoc
    ask   : https://github.com/jtroo/kanata/discussions",
        err_msg.as_ref(),
    )
}