emmylua_ls 0.4.4

A language server for emmylua.
use std::collections::HashSet;

use emmylua_code_analysis::DiagnosticCode;
use emmylua_parser::{
    LuaAstNode, LuaClosureExpr, LuaComment, LuaDocTagParam, LuaSyntaxKind,
    LuaTokenKind,
};
use lsp_types::CompletionItem;

use crate::handlers::completion::completion_builder::CompletionBuilder;

pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
    if builder.is_cancelled() {
        return None;
    }

    let trigger_token = &builder.trigger_token;
    if !matches!(trigger_token.kind().into(), LuaTokenKind::TkName) {
        return None;
    }

    let parent_node = trigger_token.parent()?;
    match parent_node.kind().into() {
        LuaSyntaxKind::DocTagParam => {
            add_tag_param_name_completion(builder, LuaDocTagParam::cast(parent_node)?);
        }
        LuaSyntaxKind::DocTagCast => {
            add_tag_cast_name_completion(builder);
        }
        LuaSyntaxKind::DocTagDiagnostic => {
            add_tag_diagnostic_action_completion(builder);
        }
        LuaSyntaxKind::DocDiagnosticCodeList => {
            add_tag_diagnostic_code_completion(builder);
        }
        _ => {}
    }

    builder.stop_here();

    Some(())
}

fn add_tag_param_name_completion(
    builder: &mut CompletionBuilder,
    node: LuaDocTagParam,
) -> Option<()> {
    let comment = node.ancestors::<LuaComment>().next()?;
    let owner = comment.get_owner()?;
    let closure = owner.descendants::<LuaClosureExpr>().next()?;
    let params = closure.get_params_list()?.get_params();
    for param in params {
        let completion_item = CompletionItem {
            label: param.get_name_token()?.get_name_text().to_string(),
            kind: Some(lsp_types::CompletionItemKind::VARIABLE),
            ..Default::default()
        };

        builder.add_completion_item(completion_item);
    }

    Some(())
}

fn add_tag_cast_name_completion(
    builder: &mut CompletionBuilder,
) -> Option<()> {
    let file_id = builder.semantic_model.get_file_id();
    let decl_tree = builder
        .semantic_model
        .get_db()
        .get_decl_index()
        .get_decl_tree(&file_id)?;
    let mut duplicated_name = HashSet::new();
    let local_env = decl_tree.get_env_decls(builder.trigger_token.text_range().start())?;
    for decl_id in local_env.iter() {
        let name = {
            let decl = builder
                .semantic_model
                .get_db()
                .get_decl_index()
                .get_decl(&decl_id)?;
            
            decl.get_name().to_string()
        };
        if duplicated_name.contains(&name) {
            continue;
        }

        duplicated_name.insert(name.clone());
        let completion_item = CompletionItem {
            label: name,
            kind: Some(lsp_types::CompletionItemKind::VARIABLE),
            ..Default::default()
        };
        builder.add_completion_item(completion_item);
    }

    Some(())
}

fn add_tag_diagnostic_action_completion(builder: &mut CompletionBuilder) {
    let actions = vec!["disable", "disable-next-line"];
    for (sorted_index, action) in actions.iter().enumerate() {
        let completion_item = CompletionItem {
            label: action.to_string(),
            kind: Some(lsp_types::CompletionItemKind::EVENT),
            sort_text: Some(format!("{:03}", sorted_index)),
            ..Default::default()
        };

        builder.add_completion_item(completion_item);
    }
}

fn add_tag_diagnostic_code_completion(builder: &mut CompletionBuilder) {
    let codes = DiagnosticCode::all();
    for (sorted_index, code) in codes.iter().enumerate() {
        let completion_item = CompletionItem {
            label: code.get_name().to_string(),
            kind: Some(lsp_types::CompletionItemKind::EVENT),
            sort_text: Some(format!("{:03}", sorted_index)),
            ..Default::default()
        };

        builder.add_completion_item(completion_item);
    }
}