css_variable_lsp/
types.rs

1use serde::{Deserialize, Serialize};
2use tower_lsp::lsp_types::{Position, Range, Url};
3
4use crate::runtime_config::RuntimeConfig;
5
6/// Represents a CSS variable definition
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CssVariable {
9    /// Variable name (e.g., "--primary-color")
10    pub name: String,
11
12    /// Variable value (e.g., "#3b82f6")
13    pub value: String,
14
15    /// Document URI where the variable is defined
16    pub uri: Url,
17
18    /// Range of the entire declaration (e.g., "--foo: red")
19    pub range: Range,
20
21    /// Range of just the variable name (e.g., "--foo")
22    pub name_range: Option<Range>,
23
24    /// Range of just the value part (e.g., "red")
25    pub value_range: Option<Range>,
26
27    /// CSS selector where this variable is defined (e.g., ":root", "div", ".class")
28    pub selector: String,
29
30    /// Whether this definition uses !important
31    pub important: bool,
32
33    /// Whether this definition is from an inline style attribute
34    pub inline: bool,
35
36    /// Character position in file (for source order in cascade)
37    pub source_position: usize,
38}
39
40/// Represents a CSS variable usage (var() call)
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct CssVariableUsage {
43    /// Variable name being used
44    pub name: String,
45
46    /// Document URI where the variable is used
47    pub uri: Url,
48
49    /// Range of the var() call
50    pub range: Range,
51
52    /// Range of just the variable name in var()
53    pub name_range: Option<Range>,
54
55    /// CSS selector context where variable is used
56    pub usage_context: String,
57
58    /// DOM node info if usage is in HTML (for inline styles)
59    pub dom_node: Option<DOMNodeInfo>,
60}
61
62/// Information about a DOM node
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DOMNodeInfo {
65    /// Tag name (e.g., "div", "span")
66    pub tag: String,
67
68    /// ID attribute if present
69    pub id: Option<String>,
70
71    /// Classes
72    pub classes: Vec<String>,
73
74    /// Position in document
75    pub position: usize,
76
77    /// Internal node index for selector matching
78    pub node_index: Option<usize>,
79}
80
81/// Configuration settings
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Config {
84    /// File patterns to scan for CSS variables
85    pub lookup_files: Vec<String>,
86
87    /// Glob patterns to ignore
88    pub ignore_globs: Vec<String>,
89
90    /// Enable color provider
91    pub enable_color_provider: bool,
92
93    /// Only show colors on variables (not inline values)
94    pub color_only_on_variables: bool,
95}
96
97impl Default for Config {
98    fn default() -> Self {
99        Self {
100            lookup_files: vec![
101                "**/*.css".to_string(),
102                "**/*.scss".to_string(),
103                "**/*.sass".to_string(),
104                "**/*.less".to_string(),
105                "**/*.html".to_string(),
106                "**/*.vue".to_string(),
107                "**/*.svelte".to_string(),
108                "**/*.astro".to_string(),
109                "**/*.jsx".to_string(),
110                "**/*.tsx".to_string(),
111                "**/*.ripple".to_string(),
112            ],
113            ignore_globs: vec![
114                "**/node_modules/**".to_string(),
115                "**/dist/**".to_string(),
116                "**/out/**".to_string(),
117                "**/.git/**".to_string(),
118            ],
119            enable_color_provider: true,
120            color_only_on_variables: false,
121        }
122    }
123}
124
125impl Config {
126    pub fn from_runtime(runtime: &RuntimeConfig) -> Self {
127        let mut config = Config::default();
128        if let Some(lookup) = &runtime.lookup_files {
129            if !lookup.is_empty() {
130                config.lookup_files = lookup.clone();
131            }
132        }
133        if let Some(ignore) = &runtime.ignore_globs {
134            if !ignore.is_empty() {
135                config.ignore_globs = ignore.clone();
136            }
137        }
138        config.enable_color_provider = runtime.enable_color_provider;
139        config.color_only_on_variables = runtime.color_only_on_variables;
140        config
141    }
142}
143
144/// Helper to convert byte offset to LSP Position
145pub fn offset_to_position(text: &str, offset: usize) -> Position {
146    let mut line = 0;
147    let mut character = 0;
148
149    for (idx, ch) in text.char_indices() {
150        if idx >= offset {
151            break;
152        }
153        if ch == '\n' {
154            line += 1;
155            character = 0;
156        } else {
157            character += ch.len_utf16() as u32;
158        }
159    }
160
161    Position::new(line, character)
162}
163
164/// Helper to convert LSP Position to byte offset
165pub fn position_to_offset(text: &str, position: Position) -> Option<usize> {
166    let mut line = 0;
167    let mut character = 0;
168
169    for (idx, ch) in text.char_indices() {
170        if line == position.line && character == position.character {
171            return Some(idx);
172        }
173        if ch == '\n' {
174            line += 1;
175            character = 0;
176        } else {
177            character += ch.len_utf16() as u32;
178        }
179    }
180
181    if line == position.line && character == position.character {
182        Some(text.len())
183    } else {
184        None
185    }
186}