rototo 0.1.0-alpha.3

Control plane for runtime configuration of your application.
Documentation
use super::super::index::*;
use super::{WorkspaceCompletionItem, WorkspaceCompletionItemKind};
use crate::diagnostics::SourcePosition;

const CUSTOM_LINT_FIELD_SELECTORS: &[&str] = &[
    "description",
    "extends",
    "id",
    "json",
    "json.",
    "key",
    "predicates",
    "resolve",
    "schema",
    "type",
    "value",
    "value.",
    "values",
];

pub(crate) fn completion_items(
    index: &SemanticIndex,
    path: &str,
    position: SourcePosition,
) -> Vec<WorkspaceCompletionItem> {
    let mut items = Vec::new();

    match completion_context(index, path, position) {
        CompletionContext::Manifest => {}
        CompletionContext::Qualifier => {
            items.extend(qualifier_completion_items(index));
            items.extend(predicate_operator_completion_items());
        }
        CompletionContext::Variable => {
            items.extend(qualifier_completion_items(index));
            items.extend(current_variable_value_completion_items(index, path));
        }
        CompletionContext::CustomLint => {
            items.extend(custom_lint_field_selector_completion_items());
        }
        CompletionContext::Other => {}
    }

    sort_and_deduplicate_workspace_completion_items(&mut items);
    items
}

enum CompletionContext {
    Manifest,
    Qualifier,
    Variable,
    CustomLint,
    Other,
}

fn completion_context(
    index: &SemanticIndex,
    path: &str,
    _position: SourcePosition,
) -> CompletionContext {
    if path == super::super::WORKSPACE_MANIFEST {
        return CompletionContext::Manifest;
    }
    if index
        .custom_lints
        .files
        .values()
        .any(|file| file.path == path)
    {
        return CompletionContext::CustomLint;
    }
    if index
        .qualifiers
        .values()
        .any(|qualifier| qualifier.location.path == path)
    {
        return CompletionContext::Qualifier;
    }
    if let Some(variable) = current_variable_for_path(index, path) {
        let _ = variable;
        return CompletionContext::Variable;
    }
    CompletionContext::Other
}

fn qualifier_completion_items(index: &SemanticIndex) -> Vec<WorkspaceCompletionItem> {
    index
        .qualifiers
        .keys()
        .map(|qualifier| {
            WorkspaceCompletionItem::new(
                qualifier.clone(),
                WorkspaceCompletionItemKind::Qualifier,
                "qualifier",
            )
        })
        .collect()
}

fn current_variable_value_completion_items(
    index: &SemanticIndex,
    path: &str,
) -> Vec<WorkspaceCompletionItem> {
    let Some(variable) = current_variable_for_path(index, path) else {
        return Vec::new();
    };

    match &variable.type_source {
        TypeSourceNode::Resource(resource) => index
            .resource_objects
            .get(&resource.value)
            .into_iter()
            .flat_map(|objects| objects.keys())
            .map(|value| {
                WorkspaceCompletionItem::new(
                    value.clone(),
                    WorkspaceCompletionItemKind::Value,
                    "resource object",
                )
            })
            .collect(),
        _ => variable
            .values
            .inline_values
            .keys()
            .map(|value| {
                WorkspaceCompletionItem::new(
                    value.clone(),
                    WorkspaceCompletionItemKind::Value,
                    "variable value",
                )
            })
            .collect(),
    }
}

fn current_variable_for_path<'a>(index: &'a SemanticIndex, path: &str) -> Option<&'a VariableNode> {
    index
        .variables
        .values()
        .find(|variable| variable.location.path == path)
}

fn predicate_operator_completion_items() -> Vec<WorkspaceCompletionItem> {
    PredicateOp::COMPLETION_LABELS
        .iter()
        .copied()
        .map(|op| {
            WorkspaceCompletionItem::new(
                op,
                WorkspaceCompletionItemKind::PredicateOperator,
                "predicate operator",
            )
        })
        .collect()
}

fn custom_lint_field_selector_completion_items() -> Vec<WorkspaceCompletionItem> {
    CUSTOM_LINT_FIELD_SELECTORS
        .iter()
        .copied()
        .map(|field| {
            WorkspaceCompletionItem::new(
                field,
                WorkspaceCompletionItemKind::FieldSelector,
                "custom lint field selector",
            )
        })
        .collect()
}

fn sort_and_deduplicate_workspace_completion_items(items: &mut Vec<WorkspaceCompletionItem>) {
    items.sort_by(|left, right| {
        left.label
            .cmp(&right.label)
            .then_with(|| {
                completion_item_kind_rank(left.kind).cmp(&completion_item_kind_rank(right.kind))
            })
            .then_with(|| left.detail.cmp(right.detail))
    });
    items.dedup_by(|left, right| {
        left.label == right.label && left.kind == right.kind && left.detail == right.detail
    });
}

fn completion_item_kind_rank(kind: WorkspaceCompletionItemKind) -> u8 {
    match kind {
        WorkspaceCompletionItemKind::Qualifier => 0,
        WorkspaceCompletionItemKind::Value => 1,
        WorkspaceCompletionItemKind::PredicateOperator => 2,
        WorkspaceCompletionItemKind::FieldSelector => 3,
    }
}