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, 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        // Calculate the line and column of the error from the source range.
74        // Lines are zero indexed in vscode so we need to subtract 1.
75        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        // Calculate the line and column of the error from the source range.
94        // Lines are zero indexed in vscode so we need to subtract 1.
95        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}