grug-ls 0.1.2

A Language Server for the grug language
Documentation
use lsp_types::Position;
use tree_sitter::Node;

use crate::server::document::{parser_utils, Document, Variable};

#[derive(PartialEq, Eq, Debug)]
pub struct SpotInfo {
    pub variables: Vec<Variable>,
}

pub fn get_nearest_node<'a>(document: &'a Document, position: Position) -> Node<'a> {
    let point = tree_sitter::Point {
        column: position.character as usize,
        row: position.line as usize,
    };
    let maybe_nearest = document.tree.root_node().descendant_for_point_range(point, point).unwrap();

    let mut ret = maybe_nearest;
    
    let mut cursor = maybe_nearest.walk();
    for child in ret.children(&mut cursor) {
        let child_point = child.range().start_point;

        if child_point.row == point.row {
            if child_point.column > point.column {
                break;
            }
        } else if child_point.row > point.row {
            break;
        }

        ret = child;
    }

    ret
}

pub fn get_spot_info(document: &Document, node: &tree_sitter::Node) -> SpotInfo {
    let mut variables: Vec<Variable> = Vec::new();
    for global_var in document.global_vars.iter() {
        variables.push(Variable {
            name: global_var.name.clone(),
            r#type: global_var.r#type.clone(),
        });
    }

    let mut parent = node.clone();

    while let Some(next_parent) = parent.parent() {
        if next_parent.kind() == "source_file" {
            parent = next_parent;
            continue;
        }

        macro_rules! handle {
            ($node:expr) => {
                if $node.kind() == "variable_declaration" {
                    if let Ok(decl) =
                        parser_utils::parse_variable_declaration(&document.content, &$node)
                    {
                        variables.push(decl);
                    }
                }

                if $node.kind() == "function_parameter" {
                    if let Ok(param) =
                        parser_utils::parse_variable_declaration(&document.content, &$node)
                    {
                        variables.push(param);
                    }
                }
            }
        }
        
        let mut current_node = parent;
        handle!(current_node);
        while let Some(sibling) = current_node.prev_sibling() {
            handle!(sibling);
            
            current_node = sibling;
        }

        parent = next_parent;
    }

    SpotInfo { variables }
}

#[test]
pub fn test_var_get() {
    let source = r#"a: i32 = 2
b: f32 = 4.

on_spawn(str: string) {
    c: f32 = 6
    if true {
        no: i32 = 3
    }
    print()
    
    d: f32 = 5
}
"#;

    let mut parser = tree_sitter::Parser::new();
    parser
        .set_language(&tree_sitter_grug::LANGUAGE.into())
        .unwrap();

    let tree = parser.parse(source.as_bytes(), None).unwrap();
    let func_call = tree
        .root_node()
        .named_descendant_for_point_range(
            tree_sitter::Point { row: 8, column: 5 },
            tree_sitter::Point { row: 8, column: 11 },
        )
        .unwrap();

    assert!(func_call.kind() == "function_call");
    assert!(func_call.child_by_field_name("name").unwrap().kind() == "identifier");

    let document = Document::new(
        &mut parser,
        source.as_bytes().to_vec(),
        "tired-box.grug".to_string(),
    );
    assert_eq!(document.entity_type, "box");

    let mut spot_info = get_spot_info(&document, &func_call);
    spot_info.variables.sort();

    use crate::server::document::Type;
    let mut expected = vec![
            Variable {
                name: "a".to_string(),
                r#type: Type::I32,
            },
            Variable {
                name: "b".to_string(),
                r#type: Type::F32,
            },
            Variable {
                name: "c".to_string(),
                r#type: Type::F32,
            },
            Variable {
                name: "str".to_string(),
                r#type: Type::String,
            },
    ];
    expected.sort();

    assert_eq!(
        spot_info,
        SpotInfo {
            variables: expected,
        }
    );
}