emmylua_ls 0.22.0

A language server for emmylua.
Documentation
mod auto_require_provider;
pub(super) mod desc_provider;
pub(super) mod doc_name_token_provider;
pub(super) mod doc_tag_provider;
pub(super) mod doc_type_provider;
mod env_provider;
mod equality_provider;
pub(super) mod file_path_provider;
mod function_provider;
mod keywords_provider;
mod member_provider;
pub(super) mod module_path_provider;
mod postfix_provider;
pub(super) mod table_field_provider;

use super::{completion_builder::CompletionBuilder, completion_context::CompletionContext};
pub use auto_require_provider::AutoRequireProvider;
pub use desc_provider::DescProvider;
pub use doc_name_token_provider::DocNameTokenProvider;
pub use doc_tag_provider::DocTagProvider;
pub use doc_type_provider::DocTypeProvider;
use emmylua_parser::LuaAstToken;
use emmylua_parser::LuaStringToken;
pub use env_provider::EnvProvider;
pub use equality_provider::EqualityProvider;
pub use file_path_provider::FilePathProvider;
pub use function_provider::FunctionProvider;
pub use function_provider::get_function_remove_nil;
pub use keywords_provider::KeywordsProvider;
pub use member_provider::MemberProvider;
pub use module_path_provider::ModulePathProvider;
pub use postfix_provider::PostfixProvider;
use rowan::TextRange;
pub use table_field_provider::TableFieldProvider;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderDecision {
    NoMatch,
    Continue,
    Stop,
}

pub trait CompletionProvider: Sync {
    #[allow(unused)]
    fn name(&self) -> &'static str;

    fn supports(&self, builder: &CompletionBuilder) -> bool;

    fn complete(&self, builder: &mut CompletionBuilder) -> ProviderDecision;
}

static GENERAL_PRIMARY_PROVIDERS: &[&dyn CompletionProvider] = &[
    &PostfixProvider,
    &FunctionProvider,
    &EqualityProvider,
    &TableFieldProvider,
];

static GENERAL_SECONDARY_PROVIDERS: &[&dyn CompletionProvider] =
    &[&EnvProvider, &KeywordsProvider, &MemberProvider];

static GENERAL_TERTIARY_PROVIDERS: &[&dyn CompletionProvider] = &[&AutoRequireProvider];

pub fn add_completions(builder: &mut CompletionBuilder) -> Option<()> {
    if let Some(provider) = get_context_provider(builder.context) {
        run_provider(builder, provider);
        return Some(());
    }

    if matches!(
        run_provider_group(builder, GENERAL_PRIMARY_PROVIDERS),
        ProviderDecision::Stop
    ) {
        return Some(());
    }

    if matches!(
        run_provider_group(builder, GENERAL_SECONDARY_PROVIDERS),
        ProviderDecision::Stop
    ) {
        return Some(());
    }

    if matches!(
        run_provider_group(builder, GENERAL_TERTIARY_PROVIDERS),
        ProviderDecision::Stop
    ) {
        return Some(());
    }

    for (index, item) in builder.get_completion_items_mut().iter_mut().enumerate() {
        if item.sort_text.is_none() {
            item.sort_text = Some(format!("{:04}", index + 32));
        }
    }

    Some(())
}

fn get_context_provider(context: CompletionContext) -> Option<&'static dyn CompletionProvider> {
    match context {
        CompletionContext::DocTag => Some(&DocTagProvider),
        CompletionContext::DocName => Some(&DocNameTokenProvider),
        CompletionContext::DocType => Some(&DocTypeProvider),
        CompletionContext::DocDescription => Some(&DescProvider),
        CompletionContext::ModulePath => Some(&ModulePathProvider),
        CompletionContext::FilePath => Some(&FilePathProvider),
        CompletionContext::TableField => Some(&TableFieldProvider),
        CompletionContext::General => None,
    }
}

fn run_provider(builder: &mut CompletionBuilder, provider: &dyn CompletionProvider) {
    if provider.supports(builder) {
        let _ = provider.complete(builder);
    }
}

fn run_provider_group(
    builder: &mut CompletionBuilder,
    providers: &[&dyn CompletionProvider],
) -> ProviderDecision {
    for provider in providers {
        if !provider.supports(builder) {
            continue;
        }
        match provider.complete(builder) {
            ProviderDecision::NoMatch | ProviderDecision::Continue => {}
            ProviderDecision::Stop => return ProviderDecision::Stop,
        }
        if builder.is_cancelled() {
            return ProviderDecision::Stop;
        }
    }

    ProviderDecision::Continue
}

fn get_text_edit_range_in_string(
    builder: &mut CompletionBuilder,
    string_token: LuaStringToken,
) -> Option<lsp_types::Range> {
    let text = string_token.get_text();
    let range = string_token.get_range();
    if text.is_empty() {
        return None;
    }

    let mut start_offset = u32::from(range.start());
    let mut end_offset = u32::from(range.end());
    if text.starts_with('"') || text.starts_with('\'') {
        start_offset += 1;
    }

    if text.ends_with('"') || text.ends_with('\'') {
        end_offset -= 1;
    }

    let new_text_range = TextRange::new(start_offset.into(), end_offset.into());

    builder
        .semantic_model
        .get_document()
        .to_lsp_range(new_text_range)
}