css-variable-lsp 0.2.3

A fast, Rust-based Language Server Protocol implementation for CSS Variables
Documentation
use ls_types::Uri;
use std::path::{Component, Path, PathBuf};

use crate::runtime_config::PathDisplayMode;

pub struct PathDisplayOptions<'a> {
    pub mode: PathDisplayMode,
    pub abbrev_length: usize,
    pub workspace_folder_paths: &'a [PathBuf],
    pub root_folder_path: Option<&'a PathBuf>,
}

pub fn to_normalized_fs_path(uri: &Uri) -> Option<PathBuf> {
    uri.to_file_path().map(|cow| cow.into_owned())
}

fn find_best_relative_path(fs_path: &Path, roots: &[PathBuf]) -> Option<PathBuf> {
    let mut best: Option<PathBuf> = None;
    for root in roots {
        let relative = match pathdiff::diff_paths(fs_path, root) {
            Some(rel) => rel,
            None => continue,
        };
        if relative.as_os_str().is_empty() {
            continue;
        }
        if relative.is_absolute() {
            continue;
        }
        if matches!(relative.components().next(), Some(Component::ParentDir)) {
            continue;
        }
        let rel_len = relative.to_string_lossy().len();
        let best_len = best
            .as_ref()
            .map(|p| p.to_string_lossy().len())
            .unwrap_or(usize::MAX);
        if rel_len < best_len {
            best = Some(relative);
        }
    }
    best
}

fn abbreviate_path(path: &Path, abbrev_length: usize) -> String {
    let path_str = path.to_string_lossy();
    if abbrev_length == 0 {
        return path_str.to_string();
    }
    let mut parts: Vec<String> = path
        .components()
        .filter_map(|comp| match comp {
            Component::Normal(p) => Some(p.to_string_lossy().to_string()),
            Component::Prefix(prefix) => Some(prefix.as_os_str().to_string_lossy().to_string()),
            Component::RootDir => Some(std::path::MAIN_SEPARATOR.to_string()),
            _ => None,
        })
        .collect();

    if parts.len() <= 1 {
        return path_str.to_string();
    }

    let last_index = parts.len() - 1;
    for (idx, part) in parts.iter_mut().enumerate() {
        if idx == last_index {
            continue;
        }
        if part.len() > abbrev_length {
            *part = part[..abbrev_length].to_string();
        }
    }

    if parts
        .first()
        .map(|p| p == &std::path::MAIN_SEPARATOR.to_string())
        .unwrap_or(false)
    {
        let mut rebuilt = String::new();
        rebuilt.push(std::path::MAIN_SEPARATOR);
        rebuilt.push_str(&parts[1..].join(std::path::MAIN_SEPARATOR_STR));
        return rebuilt;
    }

    parts.join(std::path::MAIN_SEPARATOR_STR)
}

pub fn format_uri_for_display(uri: &Uri, options: PathDisplayOptions<'_>) -> String {
    let fs_path = match to_normalized_fs_path(uri) {
        Some(path) => path,
        None => return uri.to_string(),
    };

    let roots: Vec<PathBuf> = if !options.workspace_folder_paths.is_empty() {
        options.workspace_folder_paths.to_vec()
    } else if let Some(root) = options.root_folder_path {
        vec![root.clone()]
    } else {
        Vec::new()
    };

    let relative = if roots.is_empty() {
        None
    } else {
        find_best_relative_path(&fs_path, &roots)
    };

    match options.mode {
        PathDisplayMode::Absolute => fs_path.to_string_lossy().to_string(),
        PathDisplayMode::Abbreviated => {
            let base = relative.as_ref().unwrap_or(&fs_path);
            abbreviate_path(base, options.abbrev_length)
        }
        PathDisplayMode::Relative => relative.unwrap_or(fs_path).to_string_lossy().to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::MAIN_SEPARATOR;

    #[test]
    fn format_uri_respects_relative_and_abbreviated_modes() {
        let root = std::env::temp_dir().join("css-lsp-test-root");
        let file_path = root.join("src").join("styles").join("main.css");
        let uri = Uri::from_file_path(&file_path).unwrap();

        let workspace_paths = vec![root.clone()];
        let relative = format_uri_for_display(
            &uri,
            PathDisplayOptions {
                mode: PathDisplayMode::Relative,
                abbrev_length: 1,
                workspace_folder_paths: &workspace_paths,
                root_folder_path: None,
            },
        );

        let expected_relative = format!("src{sep}styles{sep}main.css", sep = MAIN_SEPARATOR);
        assert_eq!(relative, expected_relative);

        let abbreviated = format_uri_for_display(
            &uri,
            PathDisplayOptions {
                mode: PathDisplayMode::Abbreviated,
                abbrev_length: 1,
                workspace_folder_paths: &workspace_paths,
                root_folder_path: None,
            },
        );

        let expected_abbrev = format!("s{sep}s{sep}main.css", sep = MAIN_SEPARATOR);
        assert_eq!(abbreviated, expected_abbrev);
    }
}