⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/devtools/ide/
lsp_client.rs

1//! Language Server Protocol (LSP) Client
2//!
3//! JSON-RPC 2.0 transport over stdin/stdout, with support for
4//! initialization, completion, diagnostics, go-to-definition, and hover.
5
6use alloc::{
7    collections::BTreeMap,
8    string::{String, ToString},
9    vec::Vec,
10};
11
12/// LSP message ID
13type MessageId = u64;
14
15/// JSON value (minimal subset)
16#[derive(Debug, Clone, PartialEq)]
17pub(crate) enum JsonValue {
18    Null,
19    Bool(bool),
20    Number(i64),
21    Str(String),
22    Array(Vec<JsonValue>),
23    Object(BTreeMap<String, JsonValue>),
24}
25
26impl JsonValue {
27    pub(crate) fn as_str(&self) -> Option<&str> {
28        match self {
29            Self::Str(s) => Some(s),
30            _ => None,
31        }
32    }
33
34    pub(crate) fn as_i64(&self) -> Option<i64> {
35        match self {
36            Self::Number(n) => Some(*n),
37            _ => None,
38        }
39    }
40
41    pub(crate) fn as_object(&self) -> Option<&BTreeMap<String, JsonValue>> {
42        match self {
43            Self::Object(m) => Some(m),
44            _ => None,
45        }
46    }
47
48    pub(crate) fn get(&self, key: &str) -> Option<&JsonValue> {
49        self.as_object()?.get(key)
50    }
51
52    /// Serialize to JSON string
53    pub(crate) fn to_json(&self) -> String {
54        match self {
55            Self::Null => "null".to_string(),
56            Self::Bool(b) => if *b { "true" } else { "false" }.to_string(),
57            Self::Number(n) => alloc::format!("{}", n),
58            Self::Str(s) => {
59                let escaped = s
60                    .replace('\\', "\\\\")
61                    .replace('"', "\\\"")
62                    .replace('\n', "\\n")
63                    .replace('\r', "\\r")
64                    .replace('\t', "\\t");
65                alloc::format!("\"{}\"", escaped)
66            }
67            Self::Array(arr) => {
68                let items: Vec<String> = arr.iter().map(|v| v.to_json()).collect();
69                alloc::format!("[{}]", items.join(","))
70            }
71            Self::Object(map) => {
72                let items: Vec<String> = map
73                    .iter()
74                    .map(|(k, v)| alloc::format!("\"{}\":{}", k, v.to_json()))
75                    .collect();
76                alloc::format!("{{{}}}", items.join(","))
77            }
78        }
79    }
80}
81
82/// Minimal JSON parser
83pub(crate) fn parse_json(input: &str) -> Option<JsonValue> {
84    let trimmed = input.trim();
85    let (val, _) = parse_value(trimmed.as_bytes(), 0)?;
86    Some(val)
87}
88
89fn parse_value(data: &[u8], mut pos: usize) -> Option<(JsonValue, usize)> {
90    pos = skip_whitespace(data, pos);
91    if pos >= data.len() {
92        return None;
93    }
94
95    match data[pos] {
96        b'"' => parse_string(data, pos),
97        b'{' => parse_object(data, pos),
98        b'[' => parse_array(data, pos),
99        b't' | b'f' => parse_bool(data, pos),
100        b'n' => parse_null(data, pos),
101        b'-' | b'0'..=b'9' => parse_number(data, pos),
102        _ => None,
103    }
104}
105
106fn skip_whitespace(data: &[u8], mut pos: usize) -> usize {
107    while pos < data.len() && matches!(data[pos], b' ' | b'\t' | b'\n' | b'\r') {
108        pos += 1;
109    }
110    pos
111}
112
113fn parse_string(data: &[u8], mut pos: usize) -> Option<(JsonValue, usize)> {
114    if data[pos] != b'"' {
115        return None;
116    }
117    pos += 1;
118    let mut s = String::new();
119    while pos < data.len() && data[pos] != b'"' {
120        if data[pos] == b'\\' && pos + 1 < data.len() {
121            pos += 1;
122            match data[pos] {
123                b'"' => s.push('"'),
124                b'\\' => s.push('\\'),
125                b'n' => s.push('\n'),
126                b'r' => s.push('\r'),
127                b't' => s.push('\t'),
128                _ => {
129                    s.push('\\');
130                    s.push(data[pos] as char);
131                }
132            }
133        } else {
134            s.push(data[pos] as char);
135        }
136        pos += 1;
137    }
138    if pos >= data.len() {
139        return None;
140    }
141    pos += 1; // Skip closing quote
142    Some((JsonValue::Str(s), pos))
143}
144
145fn parse_object(data: &[u8], mut pos: usize) -> Option<(JsonValue, usize)> {
146    pos += 1; // Skip '{'
147    let mut map = BTreeMap::new();
148    pos = skip_whitespace(data, pos);
149
150    if pos < data.len() && data[pos] == b'}' {
151        return Some((JsonValue::Object(map), pos + 1));
152    }
153
154    loop {
155        pos = skip_whitespace(data, pos);
156        let (key_val, new_pos) = parse_string(data, pos)?;
157        pos = new_pos;
158        let key = match key_val {
159            JsonValue::Str(s) => s,
160            _ => return None,
161        };
162
163        pos = skip_whitespace(data, pos);
164        if pos >= data.len() || data[pos] != b':' {
165            return None;
166        }
167        pos += 1;
168
169        let (val, new_pos) = parse_value(data, pos)?;
170        pos = new_pos;
171        map.insert(key, val);
172
173        pos = skip_whitespace(data, pos);
174        if pos >= data.len() {
175            return None;
176        }
177        if data[pos] == b'}' {
178            return Some((JsonValue::Object(map), pos + 1));
179        }
180        if data[pos] == b',' {
181            pos += 1;
182        }
183    }
184}
185
186fn parse_array(data: &[u8], mut pos: usize) -> Option<(JsonValue, usize)> {
187    pos += 1; // Skip '['
188    let mut arr = Vec::new();
189    pos = skip_whitespace(data, pos);
190
191    if pos < data.len() && data[pos] == b']' {
192        return Some((JsonValue::Array(arr), pos + 1));
193    }
194
195    loop {
196        let (val, new_pos) = parse_value(data, pos)?;
197        pos = new_pos;
198        arr.push(val);
199
200        pos = skip_whitespace(data, pos);
201        if pos >= data.len() {
202            return None;
203        }
204        if data[pos] == b']' {
205            return Some((JsonValue::Array(arr), pos + 1));
206        }
207        if data[pos] == b',' {
208            pos += 1;
209        }
210    }
211}
212
213fn parse_bool(data: &[u8], pos: usize) -> Option<(JsonValue, usize)> {
214    if data[pos..].starts_with(b"true") {
215        Some((JsonValue::Bool(true), pos + 4))
216    } else if data[pos..].starts_with(b"false") {
217        Some((JsonValue::Bool(false), pos + 5))
218    } else {
219        None
220    }
221}
222
223fn parse_null(data: &[u8], pos: usize) -> Option<(JsonValue, usize)> {
224    if data[pos..].starts_with(b"null") {
225        Some((JsonValue::Null, pos + 4))
226    } else {
227        None
228    }
229}
230
231fn parse_number(data: &[u8], mut pos: usize) -> Option<(JsonValue, usize)> {
232    let start = pos;
233    if pos < data.len() && data[pos] == b'-' {
234        pos += 1;
235    }
236    while pos < data.len() && data[pos].is_ascii_digit() {
237        pos += 1;
238    }
239    let num_str = core::str::from_utf8(&data[start..pos]).ok()?;
240    let num: i64 = num_str.parse().ok()?;
241    Some((JsonValue::Number(num), pos))
242}
243
244// ---------------------------------------------------------------------------
245// LSP Protocol Types
246// ---------------------------------------------------------------------------
247
248/// LSP completion item
249#[derive(Debug, Clone)]
250pub(crate) struct CompletionItem {
251    pub(crate) label: String,
252    pub(crate) kind: CompletionKind,
253    pub(crate) detail: Option<String>,
254    pub(crate) insert_text: Option<String>,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub(crate) enum CompletionKind {
259    Text = 1,
260    Method = 2,
261    Function = 3,
262    Constructor = 4,
263    Field = 5,
264    Variable = 6,
265    Class = 7,
266    Interface = 8,
267    Module = 9,
268    Property = 10,
269    Keyword = 14,
270    Snippet = 15,
271}
272
273/// LSP diagnostic
274#[derive(Debug, Clone)]
275pub(crate) struct Diagnostic {
276    pub(crate) range_start_line: u32,
277    pub(crate) range_start_col: u32,
278    pub(crate) range_end_line: u32,
279    pub(crate) range_end_col: u32,
280    pub(crate) severity: DiagnosticSeverity,
281    pub(crate) message: String,
282    pub(crate) source: Option<String>,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub(crate) enum DiagnosticSeverity {
287    Error = 1,
288    Warning = 2,
289    Information = 3,
290    Hint = 4,
291}
292
293/// LSP location (for go-to-definition)
294#[derive(Debug, Clone)]
295pub(crate) struct Location {
296    pub(crate) uri: String,
297    pub(crate) line: u32,
298    pub(crate) col: u32,
299}
300
301/// LSP hover result
302#[derive(Debug, Clone)]
303pub(crate) struct HoverResult {
304    pub(crate) contents: String,
305    pub(crate) range: Option<(u32, u32, u32, u32)>,
306}
307
308/// LSP client state
309pub(crate) struct LspClient {
310    next_id: MessageId,
311    pub(crate) server_capabilities: Option<JsonValue>,
312    pub(crate) diagnostics: Vec<Diagnostic>,
313    initialized: bool,
314}
315
316impl Default for LspClient {
317    fn default() -> Self {
318        Self::new()
319    }
320}
321
322impl LspClient {
323    pub(crate) fn new() -> Self {
324        Self {
325            next_id: 1,
326            server_capabilities: None,
327            diagnostics: Vec::new(),
328            initialized: false,
329        }
330    }
331
332    fn next_id(&mut self) -> MessageId {
333        let id = self.next_id;
334        self.next_id += 1;
335        id
336    }
337
338    /// Build JSON-RPC request
339    pub(crate) fn build_request(&mut self, method: &str, params: JsonValue) -> String {
340        let id = self.next_id();
341        let mut obj = BTreeMap::new();
342        obj.insert("jsonrpc".to_string(), JsonValue::Str("2.0".to_string()));
343        obj.insert("id".to_string(), JsonValue::Number(id as i64));
344        obj.insert("method".to_string(), JsonValue::Str(method.to_string()));
345        obj.insert("params".to_string(), params);
346
347        let json = JsonValue::Object(obj).to_json();
348        alloc::format!("Content-Length: {}\r\n\r\n{}", json.len(), json)
349    }
350
351    /// Build JSON-RPC notification (no id)
352    pub(crate) fn build_notification(&self, method: &str, params: JsonValue) -> String {
353        let mut obj = BTreeMap::new();
354        obj.insert("jsonrpc".to_string(), JsonValue::Str("2.0".to_string()));
355        obj.insert("method".to_string(), JsonValue::Str(method.to_string()));
356        obj.insert("params".to_string(), params);
357
358        let json = JsonValue::Object(obj).to_json();
359        alloc::format!("Content-Length: {}\r\n\r\n{}", json.len(), json)
360    }
361
362    /// Build initialize request
363    pub(crate) fn build_initialize(&mut self, root_uri: &str) -> String {
364        let mut capabilities = BTreeMap::new();
365        let mut text_doc = BTreeMap::new();
366        let mut completion = BTreeMap::new();
367        completion.insert("dynamicRegistration".to_string(), JsonValue::Bool(false));
368        text_doc.insert("completion".to_string(), JsonValue::Object(completion));
369        capabilities.insert("textDocument".to_string(), JsonValue::Object(text_doc));
370
371        let mut params = BTreeMap::new();
372        params.insert("processId".to_string(), JsonValue::Number(1));
373        params.insert("rootUri".to_string(), JsonValue::Str(root_uri.to_string()));
374        params.insert("capabilities".to_string(), JsonValue::Object(capabilities));
375
376        self.build_request("initialize", JsonValue::Object(params))
377    }
378
379    /// Parse an LSP response
380    pub(crate) fn parse_response(&mut self, data: &str) -> Option<JsonValue> {
381        // Skip Content-Length header
382        let body_start = data.find("\r\n\r\n")?;
383        let body = &data[body_start + 4..];
384        let val = parse_json(body)?;
385
386        // Check for server capabilities in initialize response
387        if let Some(result) = val.get("result") {
388            if let Some(caps) = result.get("capabilities") {
389                self.server_capabilities = Some(caps.clone());
390                self.initialized = true;
391            }
392        }
393
394        Some(val)
395    }
396
397    /// Parse completion items from response
398    pub(crate) fn parse_completions(&self, result: &JsonValue) -> Vec<CompletionItem> {
399        let mut items = Vec::new();
400
401        let arr = match result {
402            JsonValue::Array(a) => a,
403            JsonValue::Object(obj) => match obj.get("items") {
404                Some(JsonValue::Array(a)) => a,
405                _ => return items,
406            },
407            _ => return items,
408        };
409
410        for item in arr {
411            if let Some(obj) = item.as_object() {
412                let label = obj
413                    .get("label")
414                    .and_then(|v| v.as_str())
415                    .unwrap_or("")
416                    .to_string();
417
418                let kind_num = obj.get("kind").and_then(|v| v.as_i64()).unwrap_or(1);
419
420                let kind = match kind_num {
421                    2 => CompletionKind::Method,
422                    3 => CompletionKind::Function,
423                    4 => CompletionKind::Constructor,
424                    5 => CompletionKind::Field,
425                    6 => CompletionKind::Variable,
426                    7 => CompletionKind::Class,
427                    8 => CompletionKind::Interface,
428                    9 => CompletionKind::Module,
429                    10 => CompletionKind::Property,
430                    14 => CompletionKind::Keyword,
431                    15 => CompletionKind::Snippet,
432                    _ => CompletionKind::Text,
433                };
434
435                let detail = obj
436                    .get("detail")
437                    .and_then(|v| v.as_str())
438                    .map(|s| s.to_string());
439
440                let insert_text = obj
441                    .get("insertText")
442                    .and_then(|v| v.as_str())
443                    .map(|s| s.to_string());
444
445                items.push(CompletionItem {
446                    label,
447                    kind,
448                    detail,
449                    insert_text,
450                });
451            }
452        }
453
454        items
455    }
456
457    pub(crate) fn is_initialized(&self) -> bool {
458        self.initialized
459    }
460}
461
462// ---------------------------------------------------------------------------
463// Tests
464// ---------------------------------------------------------------------------
465
466#[cfg(test)]
467mod tests {
468    #[allow(unused_imports)]
469    use alloc::vec;
470
471    use super::*;
472
473    #[test]
474    fn test_parse_json_null() {
475        assert_eq!(parse_json("null"), Some(JsonValue::Null));
476    }
477
478    #[test]
479    fn test_parse_json_bool() {
480        assert_eq!(parse_json("true"), Some(JsonValue::Bool(true)));
481        assert_eq!(parse_json("false"), Some(JsonValue::Bool(false)));
482    }
483
484    #[test]
485    fn test_parse_json_number() {
486        assert_eq!(parse_json("42"), Some(JsonValue::Number(42)));
487        assert_eq!(parse_json("-1"), Some(JsonValue::Number(-1)));
488    }
489
490    #[test]
491    fn test_parse_json_string() {
492        assert_eq!(
493            parse_json("\"hello\""),
494            Some(JsonValue::Str("hello".to_string()))
495        );
496    }
497
498    #[test]
499    fn test_parse_json_array() {
500        let val = parse_json("[1, 2, 3]").unwrap();
501        match val {
502            JsonValue::Array(arr) => assert_eq!(arr.len(), 3),
503            _ => panic!("Expected array"),
504        }
505    }
506
507    #[test]
508    fn test_parse_json_object() {
509        let val = parse_json("{\"key\": \"value\"}").unwrap();
510        assert_eq!(val.get("key"), Some(&JsonValue::Str("value".to_string())));
511    }
512
513    #[test]
514    fn test_json_serialize() {
515        let val = JsonValue::Str("hello".to_string());
516        assert_eq!(val.to_json(), "\"hello\"");
517
518        let val = JsonValue::Number(42);
519        assert_eq!(val.to_json(), "42");
520    }
521
522    #[test]
523    fn test_lsp_client_build_request() {
524        let mut client = LspClient::new();
525        let req = client.build_request("test/method", JsonValue::Null);
526        assert!(req.contains("Content-Length:"));
527        assert!(req.contains("test/method"));
528        assert!(req.contains("\"id\":1"));
529    }
530
531    #[test]
532    fn test_lsp_client_initialize() {
533        let mut client = LspClient::new();
534        let req = client.build_initialize("file:///workspace");
535        assert!(req.contains("initialize"));
536        assert!(req.contains("file:///workspace"));
537    }
538
539    #[test]
540    fn test_lsp_client_not_initialized() {
541        let client = LspClient::new();
542        assert!(!client.is_initialized());
543    }
544
545    #[test]
546    fn test_parse_completions_empty() {
547        let client = LspClient::new();
548        let result = JsonValue::Array(Vec::new());
549        let items = client.parse_completions(&result);
550        assert!(items.is_empty());
551    }
552
553    #[test]
554    fn test_parse_completions_items() {
555        let client = LspClient::new();
556        let mut item = BTreeMap::new();
557        item.insert("label".to_string(), JsonValue::Str("println!".to_string()));
558        item.insert("kind".to_string(), JsonValue::Number(3)); // Function
559
560        let result = JsonValue::Array(vec![JsonValue::Object(item)]);
561        let items = client.parse_completions(&result);
562        assert_eq!(items.len(), 1);
563        assert_eq!(items[0].label, "println!");
564        assert_eq!(items[0].kind, CompletionKind::Function);
565    }
566
567    #[test]
568    fn test_diagnostic_severity() {
569        assert_eq!(DiagnosticSeverity::Error as u8, 1);
570        assert_eq!(DiagnosticSeverity::Warning as u8, 2);
571    }
572
573    #[test]
574    fn test_json_escape() {
575        let val = JsonValue::Str("line1\nline2".to_string());
576        let json = val.to_json();
577        assert!(json.contains("\\n"));
578    }
579}