package-json-lsp 0.1.0

Language server for package manager catalog references in package.json
Documentation
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};

static CATALOG_COLORS: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();

pub fn get_catalog_color(name: &str) -> String {
    let colors = CATALOG_COLORS.get_or_init(|| {
        let mut map = HashMap::new();
        map.insert("default".to_string(), "#f69220".to_string());
        Mutex::new(map)
    });

    if let Some(color) = colors
        .lock()
        .expect("catalog colors lock")
        .get(name)
        .cloned()
    {
        return color;
    }

    let mut hash = 0_i32;
    for ch in name.chars() {
        hash = ch as i32 + ((hash << 5) - hash);
    }

    let color = hsl_to_hex(hash % 360, 35, 65);
    colors
        .lock()
        .expect("catalog colors lock")
        .insert(name.to_string(), color.clone());
    color
}

fn hsl_to_hex(h: i32, s: i32, l: i32) -> String {
    let [r, g, b] = hsl_to_rgb(h, s, l);
    format!("#{r:02x}{g:02x}{b:02x}")
}

fn hsl_to_rgb(mut h: i32, s: i32, l: i32) -> [u8; 3] {
    h %= 360;
    let h = h as f64 / 360.0;
    let s = s as f64 / 100.0;
    let l = l as f64 / 100.0;

    if s == 0.0 {
        let v = (l * 255.0).round().clamp(0.0, 255.0) as u8;
        return [v, v, v];
    }

    let q = if l < 0.5 {
        l * (1.0 + s)
    } else {
        l + s - l * s
    };
    let p = 2.0 * l - q;

    [
        (hue_to_rgb(p, q, h + 1.0 / 3.0) * 255.0)
            .round()
            .clamp(0.0, 255.0) as u8,
        (hue_to_rgb(p, q, h) * 255.0).round().clamp(0.0, 255.0) as u8,
        (hue_to_rgb(p, q, h - 1.0 / 3.0) * 255.0)
            .round()
            .clamp(0.0, 255.0) as u8,
    ]
}

fn hue_to_rgb(p: f64, q: f64, mut t: f64) -> f64 {
    if t < 0.0 {
        t += 1.0;
    }
    if t > 1.0 {
        t -= 1.0;
    }
    if t < 1.0 / 6.0 {
        p + (q - p) * 6.0 * t
    } else if t < 1.0 / 2.0 {
        q
    } else if t < 2.0 / 3.0 {
        p + (q - p) * (2.0 / 3.0 - t) * 6.0
    } else {
        p
    }
}

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

    #[test]
    fn returns_default_catalog_color() {
        assert_eq!(get_catalog_color("default"), "#f69220");
    }

    #[test]
    fn hashes_named_catalogs_compatibly() {
        assert_eq!(get_catalog_color("react-lens"), "#87c5a4");
    }
}