1pub mod backend;
4pub mod copilot;
5pub mod kcl;
6#[cfg(any(test, feature = "lsp-test-util"))]
7pub mod test_util;
8#[cfg(test)]
9mod tests;
10pub mod util;
11
12use tower_lsp::lsp_types::Diagnostic;
13use tower_lsp::lsp_types::DiagnosticSeverity;
14use tower_lsp::lsp_types::DiagnosticTag;
15use tower_lsp::lsp_types::Position;
16use tower_lsp::lsp_types::Range;
17pub use util::IntoDiagnostic;
18
19use crate::CompilationError;
20use crate::errors::Severity;
21use crate::errors::Suggestion;
22use crate::errors::Tag;
23
24impl IntoDiagnostic for CompilationError {
25 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
26 let edit = self.suggestion.as_ref().map(|s| to_lsp_edit(s, code));
27
28 vec![Diagnostic {
29 range: self.source_range.to_lsp_range(code),
30 severity: Some(self.severity()),
31 code: None,
32 code_description: None,
33 source: Some("kcl".to_string()),
34 message: self.message.clone(),
35 related_information: None,
36 tags: tag_to_lsp_tags(self.tag),
37 data: edit.map(|e| serde_json::to_value(e).unwrap()),
38 }]
39 }
40
41 fn severity(&self) -> DiagnosticSeverity {
42 match self.severity {
43 Severity::Warning => DiagnosticSeverity::WARNING,
44 _ => DiagnosticSeverity::ERROR,
45 }
46 }
47}
48
49fn tag_to_lsp_tags(tag: Tag) -> Option<Vec<DiagnosticTag>> {
50 match tag {
51 Tag::Deprecated => Some(vec![DiagnosticTag::DEPRECATED]),
52 Tag::Unnecessary => Some(vec![DiagnosticTag::UNNECESSARY]),
53 Tag::UnknownNumericUnits | Tag::None => None,
54 }
55}
56
57pub type LspSuggestion = (Suggestion, tower_lsp::lsp_types::Range);
58
59pub(crate) fn to_lsp_edit(suggestion: &Suggestion, code: &str) -> LspSuggestion {
60 let range = suggestion.source_range.to_lsp_range(code);
61 (suggestion.clone(), range)
62}
63
64pub trait ToLspRange {
65 fn to_lsp_range(&self, code: &str) -> Range {
66 let start = self.start_to_lsp_position(code);
67 let end = self.end_to_lsp_position(code);
68 Range { start, end }
69 }
70
71 fn start_to_lsp_position(&self, code: &str) -> Position;
72 fn end_to_lsp_position(&self, code: &str) -> Position;
73}
74
75impl ToLspRange for crate::SourceRange {
76 fn start_to_lsp_position(&self, code: &str) -> Position {
77 let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
80 if line > 0 {
81 line = line.saturating_sub(1);
82 }
83 let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
84
85 Position {
86 line: line as u32,
87 character: column as u32,
88 }
89 }
90
91 fn end_to_lsp_position(&self, code: &str) -> Position {
92 let lines = code.get(..self.end()).unwrap_or_default().lines();
93 if lines.clone().count() == 0 {
94 return Position { line: 0, character: 0 };
95 }
96
97 let line = lines.clone().count() - 1;
100 let column = lines.last().map(|l| l.len()).unwrap_or_default();
101
102 Position {
103 line: line as u32,
104 character: column as u32,
105 }
106 }
107}