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