omena-lsp-server 0.3.0

Rust LSP server boundary scaffold for Omena CSS Modules
Documentation
use omena_query::{
    OmenaQuerySourceDomainClassReferenceFactV0 as SourceDomainClassReferenceFact,
    OmenaQueryStyleIntelligenceGraphBindingV0 as StyleIntelligenceGraphBinding,
    OmenaQueryStyleIntelligenceHoverV0 as StyleIntelligenceHover,
    OmenaQueryStyleIntelligenceSnapshotV0 as StyleIntelligenceSnapshot, ParserPositionV0,
    ParserRangeV0, omena_query_style_intelligence_hover_at_offset,
};
use serde_json::{Value, json};

use crate::{
    LspQueryReadView, LspTextDocumentState,
    protocol::{byte_offset_for_parser_position, parser_range_for_byte_span},
    style_selector_definitions_from_open_documents,
};

const UTILITY_PROVIDER_ID: &str = "tailwind-uno-utility-domain";

fn source_domain_reference_at_position(
    document: &LspTextDocumentState,
    position: ParserPositionV0,
) -> Option<&SourceDomainClassReferenceFact> {
    let offset = byte_offset_for_parser_position(document.text.as_str(), position)?;
    document
        .source_syntax_index
        .domain_class_references
        .iter()
        .find(|reference| offset >= reference.byte_span.start && offset <= reference.byte_span.end)
}

pub(crate) fn source_domain_reference_trace_at_position(
    state: &dyn LspQueryReadView,
    document: &LspTextDocumentState,
    position: ParserPositionV0,
) -> Option<Value> {
    let reference = source_domain_reference_at_position(document, position)?;
    let hover = provider_hover_for_reference(state, document, reference)?;
    let current = hover.current_option.as_deref().unwrap_or("*");
    let validity = source_domain_reference_validity(&hover);
    let range = parser_range_for_byte_span(document.text.as_str(), reference.byte_span);
    let definitions = hover
        .graph_bindings
        .iter()
        .map(|binding| {
            json!({
                "uri": binding.uri,
                "name": binding.class_name,
                "range": binding.range,
                "source": "styleSelectorDefinitionIndex",
            })
        })
        .collect::<Vec<_>>();
    let rendered_markdown = render_source_domain_reference_hover_text(&hover);
    let (resolution_path, ready_surfaces) = if hover.provider_id == UTILITY_PROVIDER_ID {
        (
            vec![
                "sourceSyntaxIndex",
                "classValueUniverseProvider",
                "styleSelectorDefinitionIndex",
                "sourceDomainReferenceHover",
            ],
            vec![
                "explainHoverTraceRpc",
                "sourceSyntaxIndex",
                "classValueUniverseProvider",
                "styleSelectorDefinitionIndex",
            ],
        )
    } else {
        (
            vec![
                "sourceSyntaxIndex",
                "classValueUniverseProvider",
                "sourceDomainReferenceHover",
            ],
            vec![
                "explainHoverTraceRpc",
                "sourceSyntaxIndex",
                "classValueUniverseProvider",
            ],
        )
    };

    Some(json!({
        "schemaVersion": "0",
        "product": "omena-lsp-server.explain-hover-trace",
        "documentUri": document.uri.as_str(),
        "workspaceFolderUri": document.workspace_folder_uri.as_deref(),
        "fileKind": "source",
        "languageId": document.language_id.as_str(),
        "queryPosition": position,
        "matched": true,
        "reason": "domainClassReferenceResolved",
        "hoverKind": "domainClassReference",
        "range": range,
        "sourceOwner": hover.owner_name,
        "domain": hover.domain,
        "axisName": hover.axis_name,
        "optionName": reference.option_name,
        "prefix": reference.prefix,
        "currentOption": current,
        "validity": validity,
        "knownOptions": hover.known_options,
        "knownPatterns": hover.known_patterns,
        "unresolvedReasons": hover.unresolved_reasons,
        "candidateCount": 1,
        "definitionCount": definitions.len(),
        "candidates": [],
        "definitions": definitions,
        "renderedMarkdown": rendered_markdown,
        "resolutionPath": resolution_path,
        "readySurfaces": ready_surfaces,
    }))
}

pub(crate) fn source_domain_reference_hover_at_position(
    state: &dyn LspQueryReadView,
    document: &LspTextDocumentState,
    position: ParserPositionV0,
) -> Option<(ParserRangeV0, String)> {
    let reference = source_domain_reference_at_position(document, position)?;
    let hover = provider_hover_for_reference(state, document, reference)?;
    Some((
        parser_range_for_byte_span(document.text.as_str(), reference.byte_span),
        render_source_domain_reference_hover_text(&hover),
    ))
}

fn provider_hover_for_reference(
    state: &dyn LspQueryReadView,
    document: &LspTextDocumentState,
    reference: &SourceDomainClassReferenceFact,
) -> Option<StyleIntelligenceHover> {
    let graph_bindings = if reference.plugin_id == UTILITY_PROVIDER_ID {
        reference
            .option_name
            .as_deref()
            .into_iter()
            .flat_map(|class_name| {
                style_selector_definitions_from_open_documents(
                    state,
                    class_name,
                    document.workspace_folder_uri.as_deref(),
                )
                .into_iter()
                .map(move |(uri, definition)| StyleIntelligenceGraphBinding {
                    class_name: class_name.to_string(),
                    uri,
                    range: definition.range,
                })
            })
            .collect::<Vec<_>>()
    } else {
        Vec::new()
    };
    let snapshot = StyleIntelligenceSnapshot::with_graph_bindings(
        &document.source_syntax_index,
        graph_bindings.as_slice(),
    );
    omena_query_style_intelligence_hover_at_offset(&snapshot, reference.byte_span.start)
}

fn source_domain_reference_validity(hover: &StyleIntelligenceHover) -> &'static str {
    hover
        .current_option
        .as_ref()
        .map_or("prefix option", |option| {
            if hover.known_options.iter().any(|known| known == option) {
                "known option"
            } else if hover.unresolved_reasons.is_empty() {
                "unknown option"
            } else {
                "indeterminate option"
            }
        })
}

fn render_source_domain_reference_hover_text(hover: &StyleIntelligenceHover) -> String {
    let current = hover.current_option.as_deref().unwrap_or("*");
    let validity = source_domain_reference_validity(hover);
    let known_options =
        if hover.known_options.is_empty() && hover.provider_id == UTILITY_PROVIDER_ID {
            "No enumerated options are indexed.".to_string()
        } else if hover.known_options.is_empty() {
            "No known options indexed.".to_string()
        } else {
            format!("Known options: `{}`.", hover.known_options.join("`, `"))
        };
    let patterns = if hover.known_patterns.is_empty() {
        String::new()
    } else {
        format!("\n\nPatterns: `{}`.", hover.known_patterns.join("`, `"))
    };
    let unresolved = if hover.unresolved_reasons.is_empty() {
        String::new()
    } else {
        format!(
            "\n\nUnresolved config: `{}`. Unknown classes remain indeterminate.",
            hover.unresolved_reasons.join("`, `")
        )
    };
    let graph = if hover.provider_id != UTILITY_PROVIDER_ID {
        String::new()
    } else if hover.graph_bindings.is_empty() {
        "\n\nNo matching selector definition is indexed in the CSS graph.".to_string()
    } else {
        let locations = hover
            .graph_bindings
            .iter()
            .map(|binding| {
                format!(
                    "`{}` at {}:{}",
                    binding.uri,
                    binding.range.start.line + 1,
                    binding.range.start.character + 1
                )
            })
            .collect::<Vec<_>>()
            .join(", ");
        format!("\n\nCSS graph bindings: {locations}.")
    };
    format!(
        "**`{}.{}.{}`**\n\n{} from `{}`.\n\n{}{}{}{}",
        hover.owner_name,
        hover.axis_name,
        current,
        validity,
        hover.domain,
        known_options,
        patterns,
        unresolved,
        graph,
    )
}