glua_ls 1.0.27

Language server for Garry's Mod Lua (GLua).
Documentation
use glua_code_analysis::{GlobalId, LuaDeclId, LuaDocument};
use glua_parser::LuaSyntaxToken;
use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType};
use rowan::{TextRange, TextSize};
use std::{
    collections::{HashMap, HashSet},
    vec::Vec,
};

pub struct CustomSemanticTokenType;
impl CustomSemanticTokenType {
    // neovim supports custom semantic token types, we add a custom type for delimiter
    pub const DELIMITER: SemanticTokenType = SemanticTokenType::new("delimiter");
    pub const FIELD: SemanticTokenType = SemanticTokenType::new("field");
    pub const LABEL: SemanticTokenType = SemanticTokenType::new("label");
}

pub struct CustomSemanticTokenModifier;
impl CustomSemanticTokenModifier {
    pub const GLOBAL: SemanticTokenModifier = SemanticTokenModifier::new("global");
    pub const LOCAL: SemanticTokenModifier = SemanticTokenModifier::new("local");
    pub const CALLABLE: SemanticTokenModifier = SemanticTokenModifier::new("callable");
    pub const OBJECT: SemanticTokenModifier = SemanticTokenModifier::new("object");
}

pub const SEMANTIC_TOKEN_TYPES: &[SemanticTokenType] = &[
    SemanticTokenType::NAMESPACE,
    SemanticTokenType::TYPE,
    SemanticTokenType::CLASS,
    SemanticTokenType::ENUM,
    SemanticTokenType::INTERFACE,
    SemanticTokenType::STRUCT,
    SemanticTokenType::TYPE_PARAMETER,
    SemanticTokenType::PARAMETER,
    SemanticTokenType::VARIABLE,
    SemanticTokenType::PROPERTY,
    SemanticTokenType::ENUM_MEMBER,
    SemanticTokenType::EVENT,
    SemanticTokenType::FUNCTION,
    SemanticTokenType::METHOD,
    SemanticTokenType::MACRO,
    SemanticTokenType::KEYWORD,
    SemanticTokenType::MODIFIER,
    SemanticTokenType::COMMENT,
    SemanticTokenType::STRING,
    SemanticTokenType::NUMBER,
    SemanticTokenType::REGEXP,
    SemanticTokenType::OPERATOR,
    SemanticTokenType::DECORATOR,
    // Custom types
    CustomSemanticTokenType::DELIMITER,
    CustomSemanticTokenType::FIELD,
    CustomSemanticTokenType::LABEL,
];

pub const SEMANTIC_TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[
    SemanticTokenModifier::DECLARATION,
    SemanticTokenModifier::DEFINITION,
    SemanticTokenModifier::READONLY,
    SemanticTokenModifier::STATIC,
    SemanticTokenModifier::ABSTRACT,
    SemanticTokenModifier::DEPRECATED,
    SemanticTokenModifier::ASYNC,
    SemanticTokenModifier::MODIFICATION,
    SemanticTokenModifier::DOCUMENTATION,
    SemanticTokenModifier::DEFAULT_LIBRARY,
    CustomSemanticTokenModifier::GLOBAL,
    CustomSemanticTokenModifier::LOCAL,
    CustomSemanticTokenModifier::CALLABLE,
    CustomSemanticTokenModifier::OBJECT,
];

#[derive(Debug)]
struct BasicSemanticTokenData {
    line: u32,
    col: u32,
    length: u32,
    typ: u32,
    modifiers: u32,
}

#[derive(Debug)]
enum SemanticTokenData {
    Basic(BasicSemanticTokenData),
    MultiLine(Vec<BasicSemanticTokenData>),
}

#[derive(Debug)]
pub struct SemanticBuilder<'a> {
    document: &'a LuaDocument<'a>,
    multi_line_support: bool,
    type_to_id: HashMap<SemanticTokenType, u32>,
    modifier_to_id: HashMap<SemanticTokenModifier, u32>,
    data: HashMap<TextSize, SemanticTokenData>,
    string_special_range: HashSet<TextRange>,
    class_like_global_cache: HashMap<GlobalId, bool>,
    alias_target_cache:
        HashMap<LuaDeclId, Option<(SemanticTokenType, Option<SemanticTokenModifier>)>>,
}

impl<'a> SemanticBuilder<'a> {
    pub fn new(
        document: &'a LuaDocument,
        multi_line_support: bool,
        types: Vec<SemanticTokenType>,
        modifier: Vec<SemanticTokenModifier>,
    ) -> Self {
        let mut type_to_id = HashMap::new();
        for (i, ty) in types.into_iter().enumerate() {
            type_to_id.insert(ty, i as u32);
        }
        let mut modifier_to_id = HashMap::new();
        for (i, modifier) in modifier.into_iter().enumerate() {
            modifier_to_id.insert(modifier, i as u32);
        }

        Self {
            document,
            multi_line_support,
            type_to_id,
            modifier_to_id,
            data: HashMap::new(),
            string_special_range: HashSet::new(),
            class_like_global_cache: HashMap::new(),
            alias_target_cache: HashMap::new(),
        }
    }

    fn push_data(&mut self, range: TextRange, text: &str, typ: u32, modifiers: u32) -> Option<()> {
        let position = range.start();
        if self.data.contains_key(&position) {
            return Some(());
        }

        let lsp_range = self.document.to_lsp_range(range)?;
        let start_line = lsp_range.start.line;
        let start_col = lsp_range.start.character;
        let end_line = lsp_range.end.line;

        if !self.multi_line_support && start_line != end_line {
            let mut multi_line_data = vec![];
            multi_line_data.push(BasicSemanticTokenData {
                line: start_line,
                col: start_col,
                length: 9999,
                typ,
                modifiers,
            });

            for i in start_line + 1..end_line {
                multi_line_data.push(BasicSemanticTokenData {
                    line: i,
                    col: 0,
                    length: 9999,
                    typ,
                    modifiers,
                });
            }

            multi_line_data.push(BasicSemanticTokenData {
                line: end_line,
                col: 0,
                length: lsp_range.end.character,
                typ,
                modifiers,
            });

            self.data
                .insert(position, SemanticTokenData::MultiLine(multi_line_data));
        } else {
            let length = text.encode_utf16().count() as u32;
            self.data.insert(
                position,
                SemanticTokenData::Basic(BasicSemanticTokenData {
                    line: start_line,
                    col: start_col,
                    length,
                    typ,
                    modifiers,
                }),
            );
        }

        Some(())
    }

    pub fn push(&mut self, token: &LuaSyntaxToken, ty: SemanticTokenType) -> Option<()> {
        self.push_data(
            token.text_range(),
            token.text(),
            *self.type_to_id.get(&ty)?,
            0,
        );
        Some(())
    }

    pub fn push_with_modifier(
        &mut self,
        token: &LuaSyntaxToken,
        ty: SemanticTokenType,
        modifier: SemanticTokenModifier,
    ) -> Option<()> {
        let typ = *self.type_to_id.get(&ty)?;
        let modifier = 1 << *self.modifier_to_id.get(&modifier)?;
        self.push_data(token.text_range(), token.text(), typ, modifier);
        Some(())
    }

    pub fn push_with_modifier_force(
        &mut self,
        token: &LuaSyntaxToken,
        ty: SemanticTokenType,
        modifier: SemanticTokenModifier,
    ) -> Option<()> {
        self.data.remove(&token.text_range().start());
        self.push_with_modifier(token, ty, modifier)
    }

    pub fn push_force(&mut self, token: &LuaSyntaxToken, ty: SemanticTokenType) -> Option<()> {
        self.data.remove(&token.text_range().start());
        self.push(token, ty)
    }

    pub fn push_at_position(
        &mut self,
        position: TextSize,
        length: u32,
        ty: SemanticTokenType,
        modifiers: Option<SemanticTokenModifier>,
    ) -> Option<()> {
        let lsp_position = self.document.to_lsp_position(position)?;
        let start_line = lsp_position.line;
        let start_col = lsp_position.character;

        self.data.insert(
            position,
            SemanticTokenData::Basic(BasicSemanticTokenData {
                line: start_line,
                col: start_col,
                length,
                typ: *self.type_to_id.get(&ty)?,
                modifiers: modifiers.map_or(0, |m| 1 << *self.modifier_to_id.get(&m).unwrap_or(&0)),
            }),
        );
        Some(())
    }

    pub fn cached_class_like_global(&self, global_id: &GlobalId) -> Option<bool> {
        self.class_like_global_cache.get(global_id).copied()
    }

    pub fn cache_class_like_global(&mut self, global_id: GlobalId, is_class_like: bool) {
        self.class_like_global_cache
            .insert(global_id, is_class_like);
    }

    pub fn cached_alias_target(
        &self,
        decl_id: &LuaDeclId,
    ) -> Option<Option<(SemanticTokenType, Option<SemanticTokenModifier>)>> {
        self.alias_target_cache.get(decl_id).cloned()
    }

    pub fn cache_alias_target(
        &mut self,
        decl_id: LuaDeclId,
        target: Option<(SemanticTokenType, Option<SemanticTokenModifier>)>,
    ) {
        self.alias_target_cache.insert(decl_id, target);
    }

    pub fn push_at_range(
        &mut self,
        token_text: &str,
        range: TextRange,
        ty: SemanticTokenType,
        modifiers: &[SemanticTokenModifier],
    ) -> Option<()> {
        let mut modifier = 0;
        for m in modifiers {
            modifier |= 1 << *self.modifier_to_id.get(m)?;
        }
        self.push_data(range, token_text, *self.type_to_id.get(&ty)?, modifier);
        Some(())
    }

    #[allow(unused)]
    pub fn push_with_modifiers(
        &mut self,
        token: &LuaSyntaxToken,
        ty: SemanticTokenType,
        modifiers: &[SemanticTokenModifier],
    ) -> Option<()> {
        let typ = *self.type_to_id.get(&ty)?;
        let mut modifier = 0;
        for m in modifiers {
            modifier |= 1 << *self.modifier_to_id.get(m)?;
        }
        self.push_data(token.text_range(), token.text(), typ, modifier);

        Some(())
    }

    pub fn build(self) -> Vec<SemanticToken> {
        let mut data: Vec<BasicSemanticTokenData> = vec![];
        for (_, token_data) in self.data {
            match token_data {
                SemanticTokenData::Basic(basic_data) => {
                    data.push(basic_data);
                }
                SemanticTokenData::MultiLine(multi_data) => {
                    for basic_data in multi_data {
                        data.push(basic_data);
                    }
                }
            }
        }

        data.sort_by(|a, b| {
            let line1 = a.line;
            let line2 = b.line;
            if line1 == line2 {
                let character1 = a.col;
                let character2 = b.col;
                return character1.cmp(&character2);
            }
            line1.cmp(&line2)
        });

        let mut result = Vec::with_capacity(data.len());
        let mut prev_line = 0;
        let mut prev_col = 0;

        for token_data in data {
            let line_diff = token_data.line - prev_line;
            if line_diff != 0 {
                prev_col = 0;
            }
            let col_diff = token_data.col - prev_col;

            result.push(SemanticToken {
                delta_line: line_diff,
                delta_start: col_diff,
                length: token_data.length,
                token_type: token_data.typ,
                token_modifiers_bitset: token_data.modifiers,
            });

            prev_line = token_data.line;
            prev_col = token_data.col;
        }

        result
    }

    pub fn add_special_string_range(&mut self, range: TextRange) {
        self.string_special_range.insert(range);
    }

    pub fn is_special_string_range(&self, range: &TextRange) -> bool {
        self.string_special_range.contains(range)
    }

    pub fn contains_position(&self, position: TextSize) -> bool {
        self.data.contains_key(&position)
    }

    pub fn contains_token(&self, token: &LuaSyntaxToken) -> bool {
        self.contains_position(token.text_range().start())
    }
}