omena-lsp-server 0.2.0

Rust LSP server boundary scaffold for Omena CSS Modules
Documentation
use crate::{LspShellState, file_uri_to_path, normalize_path};
use omena_query::OmenaQueryStylePackageManifestV0;
use serde_json::Value;
use std::{fs, path::PathBuf};

pub(crate) fn apply_feature_settings(state: &mut LspShellState, features: Option<&Value>) {
    let Some(features) = features.and_then(Value::as_object) else {
        return;
    };
    if let Some(value) = features.get("definition").and_then(Value::as_bool) {
        state.features.definition = value;
    }
    if let Some(value) = features.get("hover").and_then(Value::as_bool) {
        state.features.hover = value;
    }
    if let Some(value) = features.get("completion").and_then(Value::as_bool) {
        state.features.completion = value;
    }
    if let Some(value) = features.get("references").and_then(Value::as_bool) {
        state.features.references = value;
    }
    if let Some(value) = features.get("rename").and_then(Value::as_bool) {
        state.features.rename = value;
    }
}

pub(crate) fn apply_diagnostic_settings(state: &mut LspShellState, diagnostics: Option<&Value>) {
    let Some(diagnostics) = diagnostics.and_then(Value::as_object) else {
        return;
    };
    if let Some(value) = diagnostics
        .get("severity")
        .and_then(Value::as_str)
        .and_then(diagnostic_severity_code)
    {
        state.diagnostics.severity = value;
    }
}

pub(crate) fn apply_resolution_settings(
    state: &mut LspShellState,
    resolution: Option<&Value>,
) -> bool {
    let Some(resolution) = resolution.and_then(Value::as_object) else {
        return false;
    };
    let Some(package_manifest_paths) = resolution
        .get("packageManifestPaths")
        .and_then(Value::as_array)
    else {
        return false;
    };

    let package_manifests = package_manifest_paths
        .iter()
        .filter_map(Value::as_str)
        .filter_map(read_package_manifest_setting)
        .collect::<Vec<_>>();
    let normalized_paths = package_manifests
        .iter()
        .map(|manifest| manifest.package_json_path.clone())
        .collect::<Vec<_>>();

    let changed = state.resolution.package_manifest_paths != normalized_paths
        || state.resolution.package_manifests != package_manifests;
    if changed {
        state.resolution.package_manifest_paths = normalized_paths;
        state.resolution.package_manifests = package_manifests;
    }
    changed
}

fn diagnostic_severity_code(value: &str) -> Option<u8> {
    match value {
        "error" => Some(1),
        "warning" => Some(2),
        "information" => Some(3),
        "hint" => Some(4),
        _ => None,
    }
}

fn read_package_manifest_setting(value: &str) -> Option<OmenaQueryStylePackageManifestV0> {
    let path = setting_path(value)?;
    let package_json_path = normalize_path(path).to_string_lossy().to_string();
    let package_json_source = fs::read_to_string(package_json_path.as_str()).ok()?;
    Some(OmenaQueryStylePackageManifestV0 {
        package_json_path,
        package_json_source,
    })
}

fn setting_path(value: &str) -> Option<PathBuf> {
    file_uri_to_path(value).or_else(|| Some(PathBuf::from(value)))
}