Skip to main content

kcl_lib/lsp/
mod.rs

1//! The servers that power the text editor.
2
3pub 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        // Calculate the line and column of the error from the source range.
78        // Lines are zero indexed in vscode so we need to subtract 1.
79        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        // Calculate the line and column of the error from the source range.
98        // Lines are zero indexed in vscode so we need to subtract 1.
99        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}