grug-ls 0.1.2

A Language Server for the grug language
Documentation
use lsp_server::{Connection, ErrorCode, Message, RequestId, Response};
use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind, Position, Range};
use vfs::FileSystem;

use crate::{
    server::{
        Server,
        document::Document,
        mod_api::ModApi,
        utils::get_spot_info,
    },
};

impl Server {
    fn get_hover(
        mod_api: &ModApi,
        document: &Document,
        node: &tree_sitter::Node<'_>,
    ) -> Option<String> {
        let range = node.byte_range();
        if node.kind() == "identifier" {
            let name = &document.content[range];
            let spot_info = get_spot_info(document, node);
            for var in spot_info.variables.into_iter() {
                if var.name.as_bytes() == name {
                    return Some(format!("{}: {}", var.name, var.r#type.as_str()));
                }
            }

            let name = String::from_utf8(name.to_vec()).ok()?;

            if let Some(func) = mod_api.game_functions.get(&name) {
                let mut text = func.format(&name);
                text.push_str("\n\n");

                text.push_str(&func.description);

                return Some(text);
            }
        } else if node.kind() == "helper_identifier" {
            let name = &document.content[range];
            
            if let Some(helper) = document.helpers.iter().find(|helper| helper.name.as_bytes() == name) {
                return Some(helper.format());
            }
        } else if node.kind() == "on_identifier" {
            let name = String::from_utf8(document.content[range].to_vec()).ok()?;
            if let Some(entity) = mod_api.entities.get(&document.entity_type) {
                if let Some(on_func) = entity.on_functions.get(&name) {
                    let mut text = name;
                    text.push_str("\n\n");

                    text.push_str(&on_func.description);
                    return Some(text);
                }
            }
        }

        None
    }
    pub fn handle_hover(&self, params: HoverParams, connection: &mut Connection, id: RequestId) {
        let uri = params.text_document_position_params.text_document.uri.as_str();
        
        // We probably wont need to use this server on TCP
        assert!(uri.starts_with("file://"));

        let path = &uri["file.//".len()..];

        if !self.file_system.exists(path).unwrap_or(false) {
            connection.sender.send(Message::Response(Response::new_err(id, ErrorCode::InvalidRequest as i32, format!("File doesnt exist: {}", path)))).unwrap();
            return;
        }

        let document = &self.document_map.get(path).unwrap();

        let ast = &document.tree;

        let point = tree_sitter::Point {
            column: params.text_document_position_params.position.character as usize,
            row: params.text_document_position_params.position.line as usize,
        };

        let node = ast
            .root_node()
            .named_descendant_for_point_range(point, point)
            .unwrap();

        let range = node.range();

        let content = Self::get_hover(&self.mod_api, document, &node);
        if content.is_none() {
            connection.sender.send(Message::Response(Response::new_ok(id, serde_json::Value::Null))).unwrap();
            return;
        }
        let mut content = content.unwrap().as_bytes().to_vec();

        let var = document
            .global_vars
            .iter()
            .find(|var| var.name.as_bytes() == &content);
        if let Some(var) = var {
            content = format!("{}: {}", var.name, var.r#type.as_str())
                .as_bytes()
                .to_vec();
        }

        let res = Response::new_ok(id, Hover {
            contents: HoverContents::Markup(MarkupContent {
                kind: MarkupKind::PlainText,
                value: String::from_utf8(content).unwrap(),
            }),
            range: Some(Range {
                start: Position {
                    line: range.start_point.row as u32,
                    character: range.start_point.column as u32,
                },
                end: Position {
                    line: range.end_point.row as u32,
                    character: range.end_point.column as u32,
                },
            }),
        });

        connection.sender.send(Message::Response(res)).unwrap();
    }
}