cargo-lsp 0.0.4

LSP for Cargo.toml files
use mylsp::Position;
use tree_sitter::{Node, Point, TreeCursor};

pub struct TomlCursor<'a> {
    text: &'a str,
    cursor: TreeCursor<'a>,
}

impl TomlCursor<'_> {
    pub fn visit_all<F: FnMut(Node)>(&mut self, mut f: F) {
        let c = &mut self.cursor;
        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;
            }
        }
    }

    pub fn base_keys(&mut self, pos: &Position) -> Vec<String> {
        let mut res = vec![];
        if !self.walk_to(pos) {
            return res;
        }
        if !self.parent_table() {
            return res;
        }
        res.push(self.text().to_string());
        self.cursor.goto_parent();
        res
    }

    fn parent_table(&mut self) -> bool {
        loop {
            if self.is_table() {
                break;
            }
            let id = self.cursor.node().id();
            if !self.cursor.goto_parent() {
                return false;
            }
            if !self.cursor.goto_first_child() {
                return false;
            }
            if self.is_table() {
                break;
            }
            if self.cursor.node().id() == id {
                if !self.cursor.goto_parent() {
                    return false;
                }
                continue;
            }
            if !self.cursor.goto_parent() {
                return false;
            }
        }
        if !self.cursor.goto_first_child() {
            return false;
        }
        while self.cursor.goto_next_sibling() && !self.is_key() {}
        self.is_key()
    }

    fn walk_to(&mut self, pos: &Position) -> bool {
        let c = &mut self.cursor;
        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 text(&self) -> &str {
        let n = self.cursor.node();
        &self.text[n.start_byte()..n.end_byte()]
    }

    fn is_key(&mut self) -> bool {
        let n = self.cursor.node();
        n.kind() == "bare_key" || n.kind() == "quoted_key"
    }

    fn is_table(&mut self) -> bool {
        let n = self.cursor.node();
        n.kind() == "table"
    }
}

impl<'a> From<(&'a str, TreeCursor<'a>)> for TomlCursor<'a> {
    fn from(value: (&'a str, TreeCursor<'a>)) -> Self {
        Self {
            text: value.0,
            cursor: value.1,
        }
    }
}

#[cfg(test)]
mod test {
    use tree_sitter::{Parser, Tree};

    use crate::toml::TomlCursor;

    const EMPTYFILE: &str = "\n";

    #[test]
    fn test_base_no_table() {
        let text = EMPTYFILE;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(0, 0).into());
        assert!(keys.is_empty());
    }

    const PACKAGEFILE: &str = "[package]\n";

    #[test]
    fn test_base_keys_line_table() {
        let text = PACKAGEFILE;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(0, 0).into());
        assert_eq!(keys, &["package"]);
    }

    #[test]
    fn test_base_keys_inside_table_empty() {
        let text = PACKAGEFILE;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(1, 0).into());
        assert_eq!(keys, &["package"]);
    }

    const INCOMPLETENAME: &str = "[package]\nna";

    #[test]
    fn test_base_keys_inside_table_prop() {
        let text = INCOMPLETENAME;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(1, 2).into());
        assert_eq!(keys, &["package"]);
    }

    const DOUBLETABLE: &str = "[package]\n\n[dependencies]\n";

    #[test]
    fn test_double_table_empty() {
        let text = DOUBLETABLE;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(3, 0).into());
        assert_eq!(keys, &["dependencies"]);
    }

    const DOUBLETABLEPROP: &str = "[package]\n\n[dependencies]\na='b'\n";

    #[test]
    fn test_double_table_prop() {
        let text = DOUBLETABLEPROP;
        let tree = tree_of(text);
        let mut tc: TomlCursor = (text, tree.walk()).into();
        let keys = tc.base_keys(&(4, 0).into());
        assert_eq!(keys, &["dependencies"]);
    }

    fn tree_of(text: &str) -> Tree {
        let mut parser = Parser::new();
        parser
            .set_language(tree_sitter_toml::language())
            .expect("Error loading toml grammar");
        parser
            .parse(text, None)
            .expect("Error parsing with tree-sitter")
    }
}