Skip to main content

css_variable_lsp/
document_kind.rs

1use std::collections::HashMap;
2
3use crate::types::Config;
4
5#[derive(Debug, Clone, Default, serde::Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct ClientConfigPatch {
8    pub lookup_files: Option<Vec<String>>,
9    pub ignore_globs: Option<Vec<String>>,
10    pub enable_color_provider: Option<bool>,
11    pub color_only_on_variables: Option<bool>,
12}
13
14pub fn apply_config_patch(mut base: Config, patch: ClientConfigPatch) -> Config {
15    if let Some(lookup_files) = patch.lookup_files {
16        base.lookup_files = lookup_files;
17    }
18    if let Some(ignore_globs) = patch.ignore_globs {
19        base.ignore_globs = ignore_globs;
20    }
21    if let Some(enable_color_provider) = patch.enable_color_provider {
22        base.enable_color_provider = enable_color_provider;
23    }
24    if let Some(color_only_on_variables) = patch.color_only_on_variables {
25        base.color_only_on_variables = color_only_on_variables;
26    }
27    base
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DocumentKind {
32    Css,
33    Html,
34}
35
36fn is_html_like_extension(ext: &str) -> bool {
37    matches!(ext, ".html" | ".vue" | ".svelte" | ".astro" | ".ripple")
38}
39
40pub fn language_id_kind(language_id: &str) -> Option<DocumentKind> {
41    match language_id.to_lowercase().as_str() {
42        "html" | "vue" | "svelte" | "astro" | "ripple" => Some(DocumentKind::Html),
43        "css" | "scss" | "sass" | "less" => Some(DocumentKind::Css),
44        _ => None,
45    }
46}
47
48pub(crate) fn normalize_extension(ext: &str) -> Option<String> {
49    let trimmed = ext.trim().trim_start_matches('.');
50    if trimmed.is_empty() {
51        return None;
52    }
53    Some(format!(".{}", trimmed.to_lowercase()))
54}
55
56fn extract_extensions(pattern: &str) -> Vec<String> {
57    let pattern = pattern.trim();
58    if let (Some(start), Some(end)) = (pattern.find('{'), pattern.find('}')) {
59        if end > start + 1 {
60            let inner = &pattern[start + 1..end];
61            return inner.split(',').filter_map(normalize_extension).collect();
62        }
63    }
64
65    let ext = std::path::Path::new(pattern)
66        .extension()
67        .and_then(|ext| ext.to_str());
68    ext.and_then(normalize_extension).into_iter().collect()
69}
70
71pub fn build_lookup_extension_map(lookup_files: &[String]) -> HashMap<String, DocumentKind> {
72    let mut map = HashMap::new();
73    for pattern in lookup_files {
74        for ext in extract_extensions(pattern) {
75            let kind = if is_html_like_extension(&ext) {
76                DocumentKind::Html
77            } else {
78                DocumentKind::Css
79            };
80            map.insert(ext, kind);
81        }
82    }
83    map
84}
85
86pub fn resolve_document_kind(
87    path: &str,
88    language_id: Option<&str>,
89    lookup_extension_map: &HashMap<String, DocumentKind>,
90) -> Option<DocumentKind> {
91    if let Some(language_id) = language_id {
92        if let Some(kind) = language_id_kind(language_id) {
93            return Some(kind);
94        }
95    }
96
97    let ext = std::path::Path::new(path)
98        .extension()
99        .and_then(|ext| ext.to_str())
100        .and_then(normalize_extension)?;
101
102    lookup_extension_map.get(&ext).copied()
103}