css_variable_lsp/
document_kind.rs1use 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 Js,
35}
36
37fn is_html_like_extension(ext: &str) -> bool {
38 matches!(ext, ".html" | ".vue" | ".svelte" | ".astro" | ".ripple")
39}
40
41pub fn is_js_like_extension(ext: &str) -> bool {
42 matches!(
43 ext,
44 ".js" | ".jsx" | ".ts" | ".tsx" | ".mjs" | ".cjs" | ".mts" | ".cts"
45 )
46}
47
48pub fn language_id_kind(language_id: &str) -> Option<DocumentKind> {
49 match language_id.to_lowercase().as_str() {
50 "html" | "vue" | "svelte" | "astro" | "ripple" => Some(DocumentKind::Html),
51 "css" | "scss" | "sass" | "less" => Some(DocumentKind::Css),
52 "javascript" | "javascriptreact" | "typescript" | "typescriptreact" | "js" | "jsx"
53 | "ts" | "tsx" => Some(DocumentKind::Js),
54 _ => None,
55 }
56}
57
58pub(crate) fn normalize_extension(ext: &str) -> Option<String> {
59 let trimmed = ext.trim().trim_start_matches('.');
60 if trimmed.is_empty() {
61 return None;
62 }
63 Some(format!(".{}", trimmed.to_lowercase()))
64}
65
66fn extract_extensions(pattern: &str) -> Vec<String> {
67 let pattern = pattern.trim();
68 if let (Some(start), Some(end)) = (pattern.find('{'), pattern.find('}')) {
69 if end > start + 1 {
70 let inner = &pattern[start + 1..end];
71 return inner.split(',').filter_map(normalize_extension).collect();
72 }
73 }
74
75 let ext = std::path::Path::new(pattern)
76 .extension()
77 .and_then(|ext| ext.to_str());
78 ext.and_then(normalize_extension).into_iter().collect()
79}
80
81pub fn build_lookup_extension_map(lookup_files: &[String]) -> HashMap<String, DocumentKind> {
82 let mut map = HashMap::new();
83 for pattern in lookup_files {
84 for ext in extract_extensions(pattern) {
85 let kind = if is_html_like_extension(&ext) {
86 DocumentKind::Html
87 } else if is_js_like_extension(&ext) {
88 DocumentKind::Js
89 } else {
90 DocumentKind::Css
91 };
92 map.insert(ext, kind);
93 }
94 }
95 map
96}
97
98pub fn resolve_document_kind(
99 path: &str,
100 language_id: Option<&str>,
101 lookup_extension_map: &HashMap<String, DocumentKind>,
102) -> Option<DocumentKind> {
103 if let Some(language_id) = language_id {
104 if let Some(kind) = language_id_kind(language_id) {
105 return Some(kind);
106 }
107 }
108
109 let ext = std::path::Path::new(path)
110 .extension()
111 .and_then(|ext| ext.to_str())
112 .and_then(normalize_extension)?;
113
114 if let Some(kind) = lookup_extension_map.get(&ext).copied() {
115 return Some(kind);
116 }
117
118 if is_js_like_extension(&ext) {
120 return Some(DocumentKind::Js);
121 }
122
123 None
124}