cargo-lsp 0.0.3

LSP for Cargo.toml files
use std::ops::{Deref, DerefMut};
use tree_sitter::{Node, Parser, Point, Tree, TreeCursor};

use mylsp::prelude::*;

#[derive(Clone, Debug)]
pub struct Doc {
    uri: String,
    text: String,
    tree: Tree,
    diagnostics: Diagnostics,
}

impl Doc {
    pub fn new(uri: &str, text: String) -> Self {
        let mut parser = Parser::new();
        parser
            .set_language(tree_sitter_toml::language())
            .expect("Error loading toml grammar");
        let tree = parser
            .parse(&text, None)
            .expect("Error parsing with tree-sitter");
        let diagnostics = make_diagnostics(&tree);
        Self {
            uri: uri.to_string(),
            text,
            tree,
            diagnostics,
        }
    }

    pub fn diagnostics(&self) -> &Diagnostics {
        &self.diagnostics
    }

    pub fn completions_at(&self, pos: &Position) -> CompletionList {
        let mut c = self.tree.walk();
        let mut items = vec![];
        let empty_result = CompletionList {
            is_incomplete: false,
            items: vec![],
        };
        if !walk_to_node_at_position(&mut c, pos) {
            return empty_result;
        }
        if !walk_to_named_node_before(&mut c) {
            return empty_result;
        }
        let block = &self.text[c.node().start_byte()..c.node().end_byte()];
        if is_node_key(c.node()) {
            items.push(CompletionItem {
                label: block.to_string(),
                kind: Some(CompletionItemKind::Keyword),
                detail: None,
            });
        }
        CompletionList {
            is_incomplete: false,
            items,
        }
    }
}

#[derive(Default, Clone, Debug)]
pub struct Diagnostics(Vec<Diagnostic>);

impl From<Vec<Diagnostic>> for Diagnostics {
    fn from(value: Vec<Diagnostic>) -> Self {
        Self(value)
    }
}

impl Deref for Diagnostics {
    type Target = Vec<Diagnostic>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Diagnostics {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl From<&Doc> for Notification {
    fn from(value: &Doc) -> Self {
        (
            "textDocument/publishDiagnostics",
            PublishDiagnosticsParams {
                uri: value.uri.clone(),
                version: None,
                diagnostics: value.diagnostics().to_vec(),
            },
        )
            .into()
    }
}

impl From<&Doc> for DocumentDiagnosticReport {
    fn from(value: &Doc) -> Self {
        DocumentDiagnosticReport {
            kind: "full".to_string(),
            result_id: None,
            items: value.diagnostics().to_vec(),
        }
    }
}

fn make_diagnostics(tree: &Tree) -> Diagnostics {
    let mut res = vec![];
    visit_all(tree, |n| {
        if n.is_error() {
            res.push(error_node_to_diagnostic(n));
        }
    });
    res.into()
}

fn error_node_to_diagnostic(n: Node) -> Diagnostic {
    Diagnostic {
        range: node_to_range(&n),
        message: n.to_sexp(),
        severity: Some(DiagnosticSeverity::Error),
        ..Default::default()
    }
}

fn node_to_range(n: &Node) -> Range {
    let start = n.start_position();
    let end = n.end_position();
    Range {
        start: Position {
            line: start.row,
            character: start.column,
        },
        end: Position {
            line: end.row,
            character: end.column,
        },
    }
}

fn visit_all<F: FnMut(Node)>(tree: &Tree, mut f: F) {
    let mut c = tree.walk();
    let mut visited = vec![];
    loop {
        let n = c.node();
        if !visited.contains(&n.id()) {
            visited.push(n.id());
            f(n);
            if c.goto_first_child() {
                continue;
            }
        }
        if !c.goto_next_sibling() && !c.goto_parent() {
            break;
        }
    }
}

fn walk_to_node_at_position(c: &mut TreeCursor, pos: &Position) -> bool {
    let p = Point {
        row: pos.line,
        column: pos.character,
    };
    let mut id = None;
    while let Some(i) = c.goto_first_child_for_point(p) {
        id = Some(i);
    }
    id.is_some()
}

fn walk_to_named_node_before(c: &mut TreeCursor) -> bool {
    while !c.node().is_named() && c.goto_parent() {}
    c.node().is_named()
}

fn is_node_key(n: Node) -> bool {
    n.kind() == "dotted_key" || n.kind() == "bare_key" || n.kind() == "quoted_key"
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_empty() {
        let doc = Doc::new("", String::new());
        assert_no_errors(&doc);
    }

    #[test]
    fn test_error() {
        let doc = Doc::new("", "[a]b".to_string());
        assert_errors(&doc, &[r(0, 0, 0, 4)]);
    }

    #[test]
    fn test_error_nested() {
        let doc = Doc::new(
            "",
            r#"
[a]
b={c="d"}
[a]b
            "#
            .to_string(),
        );
        assert_errors(&doc, &[r(3, 3, 4, 0)]);
    }

    #[test]
    fn test_generic_completions_empty_doc() {
        let doc = Doc::new("", String::new());
        let comps = doc.completions_at(&Position {
            line: 1,
            character: 2,
        });
        assert!(comps.items.is_empty());
    }

    #[test]
    fn test_completion_keys() {
        let doc = Doc::new("", "key = \"value\"".to_string());
        assert_completions(&doc, &(0, 2).into(), &[c("key", KW)]);
    }

    const KW: CompletionItemKind = CompletionItemKind::Keyword;

    // c for completions
    fn c(text: &str, kind: CompletionItemKind) -> CompletionItem {
        CompletionItem {
            label: text.to_string(),
            kind: Some(kind),
            detail: None,
        }
    }

    fn assert_completions(doc: &Doc, pos: &Position, completions: &[CompletionItem]) {
        let dcs_at = doc.completions_at(pos);
        let mut dcs = dcs_at.items.iter();
        for c in completions {
            let dc = dcs
                .next()
                .expect("Assert failed, completions count not matching");
            assert_eq!(dc, c);
        }
        assert!(dcs.next().is_none());
    }

    // r for range
    fn r(
        start_line: usize,
        start_character: usize,
        end_line: usize,
        end_character: usize,
    ) -> Range {
        ((start_line, start_character).into()..(end_line, end_character).into()).into()
    }

    fn assert_no_errors(doc: &Doc) {
        assert!(doc.diagnostics().is_empty());
        let root = doc.tree.root_node();
        assert!(!root.has_error());
    }

    fn assert_errors(doc: &Doc, ranges: &[Range]) {
        let mut dgs = doc.diagnostics().iter();
        for range in ranges {
            let dg = dgs
                .next()
                .expect("Assert failed, ranges and diagnostic count not matching");
            assert_eq!(&dg.range, range);
        }
        assert!(dgs.next().is_none());

        let root = doc.tree.root_node();
        assert!(root.has_error());
    }
}