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                "**/*.ripple".to_string(),
110            ],
111            ignore_globs: vec![
112                "**/node_modules/**".to_string(),
113                "**/dist/**".to_string(),
114                "**/out/**".to_string(),
115                "**/.git/**".to_string(),
116            ],
117            enable_color_provider: true,
118            color_only_on_variables: false,
119        }
120    }
121}
122
123impl Config {
124    pub fn from_runtime(runtime: &RuntimeConfig) -> Self {
125        let mut config = Config::default();
126        if let Some(lookup) = &runtime.lookup_files {
127            if !lookup.is_empty() {
128                config.lookup_files = lookup.clone();
129            }
130        }
131        if let Some(ignore) = &runtime.ignore_globs {
132            if !ignore.is_empty() {
133                config.ignore_globs = ignore.clone();
134            }
135        }
136        config.enable_color_provider = runtime.enable_color_provider;
137        config.color_only_on_variables = runtime.color_only_on_variables;
138        config
139    }
140}
141
142/// Helper to convert byte offset to LSP Position
143pub fn offset_to_position(text: &str, offset: usize) -> Position {
144    let mut line = 0;
145    let mut character = 0;
146
147    for (idx, ch) in text.char_indices() {
148        if idx >= offset {
149            break;
150        }
151        if ch == '\n' {
152            line += 1;
153            character = 0;
154        } else {
155            character += ch.len_utf16() as u32;
156        }
157    }
158
159    Position::new(line, character)
160}
161
162/// Helper to convert LSP Position to byte offset
163pub fn position_to_offset(text: &str, position: Position) -> Option<usize> {
164    let mut line = 0;
165    let mut character = 0;
166
167    for (idx, ch) in text.char_indices() {
168        if line == position.line && character == position.character {
169            return Some(idx);
170        }
171        if ch == '\n' {
172            line += 1;
173            character = 0;
174        } else {
175            character += ch.len_utf16() as u32;
176        }
177    }
178
179    if line == position.line && character == position.character {
180        Some(text.len())
181    } else {
182        None
183    }
184}