css_variable_lsp/
workspace.rs

1use globset::{Glob, GlobSetBuilder};
2use std::fs;
3use std::path::PathBuf;
4use tower_lsp::lsp_types::Url;
5use walkdir::WalkDir;
6
7use crate::manager::CssVariableManager;
8use crate::parsers::{parse_css_document, parse_html_document};
9
10/// Scan workspace folders for CSS and HTML files
11pub async fn scan_workspace(
12    folders: Vec<Url>,
13    manager: &CssVariableManager,
14    mut on_progress: impl FnMut(usize, usize),
15) -> Result<(), String> {
16    let config = manager.get_config().await;
17
18    // Build glob matchers for lookup patterns
19    let mut lookup_builder = GlobSetBuilder::new();
20    for pattern in &config.lookup_files {
21        if let Ok(glob) = Glob::new(pattern) {
22            lookup_builder.add(glob);
23        }
24    }
25    let lookup_set = lookup_builder
26        .build()
27        .map_err(|e| format!("Failed to build lookup glob set: {}", e))?;
28
29    // Build glob matchers for ignore patterns
30    let mut ignore_builder = GlobSetBuilder::new();
31    for pattern in &config.ignore_globs {
32        if let Ok(glob) = Glob::new(pattern) {
33            ignore_builder.add(glob);
34        }
35    }
36    let ignore_set = ignore_builder
37        .build()
38        .map_err(|e| format!("Failed to build ignore glob set: {}", e))?;
39
40    // Collect all files from all folders
41    let mut all_files = Vec::new();
42
43    for folder_uri in folders {
44        let folder_path = PathBuf::from(folder_uri.path());
45
46        for entry in WalkDir::new(&folder_path)
47            .follow_links(false)
48            .into_iter()
49            .filter_map(|e| e.ok())
50        {
51            let path = entry.path();
52
53            // Skip if not a file
54            if !path.is_file() {
55                continue;
56            }
57
58            // Get relative path for glob matching
59            let relative = match path.strip_prefix(&folder_path) {
60                Ok(rel) => rel,
61                Err(_) => continue,
62            };
63
64            // Convert to string for glob matching
65            let path_str = relative.to_string_lossy();
66
67            // Skip if matches ignore pattern
68            if ignore_set.is_match(&*path_str) {
69                continue;
70            }
71
72            // Include if matches lookup pattern
73            if lookup_set.is_match(&*path_str) {
74                all_files.push(path.to_path_buf());
75            }
76        }
77    }
78
79    let total = all_files.len();
80
81    // Parse each file
82    for (i, file_path) in all_files.iter().enumerate() {
83        // Report progress
84        on_progress(i + 1, total);
85
86        // Read file content
87        let content = match fs::read_to_string(file_path) {
88            Ok(c) => c,
89            Err(_) => continue,
90        };
91
92        // Convert to URI
93        let file_uri = match Url::from_file_path(file_path) {
94            Ok(u) => u,
95            Err(_) => continue,
96        };
97
98        // Determine file type and parse
99        let path_str = file_path.to_string_lossy();
100        let result = if path_str.ends_with(".html")
101            || path_str.ends_with(".vue")
102            || path_str.ends_with(".svelte")
103            || path_str.ends_with(".astro")
104            || path_str.ends_with(".ripple")
105        {
106            parse_html_document(&content, &file_uri, manager).await
107        } else if path_str.ends_with(".css")
108            || path_str.ends_with(".scss")
109            || path_str.ends_with(".sass")
110            || path_str.ends_with(".less")
111        {
112            parse_css_document(&content, &file_uri, manager).await
113        } else {
114            continue;
115        };
116
117        // Log errors but continue
118        if let Err(_e) = result {
119            // Silent error - could log if needed
120        }
121    }
122
123    Ok(())
124}