qlue-ls 2.8.0

A language server for SPARQL
Documentation
use super::super::{CompletionEnvironment, error::CompletionError, utils::matches_search_term};
use crate::server::lsp::{
    Command, CompletionItemBuilder, CompletionList, InsertTextFormat, ItemDefaults,
};
use ll_sparql_parser::syntax_kind::SyntaxKind::*;

pub fn completions(context: &CompletionEnvironment) -> Result<CompletionList, CompletionError> {
    let mut items = Vec::new();
    let search_term = context.search_term.as_deref();
    if context.continuations.contains(&SolutionModifier)
        && matches_search_term("GROUP BY", search_term)
    {
        items.push(
            CompletionItemBuilder::new()
                .label("GROUP BY")
                .detail("Group the results")
                .documentation("Group results by one or more expressions. Used with aggregate functions like COUNT, SUM, AVG, MIN, MAX, and GROUP_CONCAT. Each unique combination of the grouped expressions produces one result row.")
                .insert_text("GROUP BY $0")
                .command(Command {
                    title: "triggerNewCompletion".to_string(),
                    command: "triggerNewCompletion".to_string(),
                    arguments: None,
                })
                .build(),
        );
    }
    if (context.continuations.contains(&SolutionModifier)
        || context.continuations.contains(&HavingClause))
        && matches_search_term("HAVING", search_term)
    {
        items.push(
            CompletionItemBuilder::new()
                .label("HAVING")
                .detail("Filter Groups")
                .documentation("Filter grouped results by applying a condition to aggregated values. HAVING is to GROUP BY what FILTER is to WHERE — it eliminates groups that don't satisfy the condition.")
                .insert_text("HAVING $0")
                .build(),
        );
    }
    if (context.continuations.contains(&SolutionModifier)
        || context.continuations.contains(&OrderClause))
        && matches_search_term("ORDER BY", search_term)
    {
        items.push(
            CompletionItemBuilder::new()
                .label("ORDER BY")
                .detail("sort the results")
                .insert_text("ORDER BY $0")
                .command(Command {
                    title: "triggerNewCompletion".to_string(),
                    command: "triggerNewCompletion".to_string(),
                    arguments: None,
                })
                .build(),
        );
    }
    if (context.continuations.contains(&SolutionModifier)
        || context.continuations.contains(&LimitClause)
        || context.continuations.contains(&LimitOffsetClauses))
        && matches_search_term("LIMIT", search_term)
    {
        items.push(
            CompletionItemBuilder::new()
                .label("LIMIT")
                .detail("Limit the amount of results")
                .insert_text("LIMIT ${0:50}")
                .build(),
        );
    }
    if (context.continuations.contains(&SolutionModifier)
        || context.continuations.contains(&OffsetClause)
        || context.continuations.contains(&LimitOffsetClauses))
        && matches_search_term("OFFSET", search_term)
    {
        items.push(
            CompletionItemBuilder::new()
                .label("OFFSET")
                .detail("Drop the first n results")
                .insert_text("OFFSET ${0:50}")
                .build(),
        );
    }
    Ok(CompletionList {
        is_incomplete: false,
        item_defaults: Some(ItemDefaults {
            insert_text_format: Some(InsertTextFormat::Snippet),
            data: None,
            commit_characters: None,
            edit_range: None,
            insert_text_mode: None,
        }),
        items,
    })
}

#[cfg(test)]
mod tests {
    use super::matches_search_term;

    const SOLUTION_MODIFIER_KEYWORDS: [&str; 5] =
        ["GROUP BY", "HAVING", "ORDER BY", "LIMIT", "OFFSET"];

    fn filter_keywords(search_term: Option<&str>) -> Vec<&'static str> {
        SOLUTION_MODIFIER_KEYWORDS
            .into_iter()
            .filter(|label| matches_search_term(label, search_term))
            .collect()
    }

    #[test]
    fn no_search_term_returns_all_keywords() {
        let labels = filter_keywords(None);
        assert_eq!(labels.len(), 5);
        assert!(labels.contains(&"GROUP BY"));
        assert!(labels.contains(&"HAVING"));
        assert!(labels.contains(&"ORDER BY"));
        assert!(labels.contains(&"LIMIT"));
        assert!(labels.contains(&"OFFSET"));
    }

    #[test]
    fn group_prefix_returns_group_by() {
        let labels = filter_keywords(Some("GR"));
        assert_eq!(labels, vec!["GROUP BY"]);
    }

    #[test]
    fn having_prefix_returns_having() {
        let labels = filter_keywords(Some("HA"));
        assert_eq!(labels, vec!["HAVING"]);
    }

    #[test]
    fn order_prefix_returns_order_by() {
        let labels = filter_keywords(Some("OR"));
        assert_eq!(labels, vec!["ORDER BY"]);
    }

    #[test]
    fn limit_prefix_returns_limit() {
        let labels = filter_keywords(Some("LI"));
        assert_eq!(labels, vec!["LIMIT"]);
    }

    #[test]
    fn offset_prefix_returns_offset() {
        let labels = filter_keywords(Some("OF"));
        assert_eq!(labels, vec!["OFFSET"]);
    }

    #[test]
    fn o_prefix_returns_order_by_and_offset() {
        let labels = filter_keywords(Some("O"));
        assert_eq!(labels.len(), 2);
        assert!(labels.contains(&"ORDER BY"));
        assert!(labels.contains(&"OFFSET"));
    }

    #[test]
    fn non_keyword_prefix_returns_empty() {
        let labels = filter_keywords(Some("Germany"));
        assert!(labels.is_empty());
    }

    #[test]
    fn case_insensitive_matching() {
        let labels = filter_keywords(Some("group"));
        assert_eq!(labels, vec!["GROUP BY"]);
    }
}