runmat-lsp 0.5.0

Language Server Protocol implementation for RunMat editors and tooling
use runmat_config::project::{
    build_project_composition_graph_async, discover_project_manifest_from_async,
    ProjectCompositionGraph,
};
use std::path::{Path, PathBuf};

#[derive(Clone, Debug)]
pub struct ProjectContext {
    manifest_path: PathBuf,
    all_source_files: Vec<PathBuf>,
}

impl ProjectContext {
    #[cfg(not(target_arch = "wasm32"))]
    pub fn discover_from_source_name(source_name: Option<&str>) -> Option<Self> {
        futures::executor::block_on(Self::discover_from_source_name_async(source_name))
    }

    #[cfg(target_arch = "wasm32")]
    pub fn discover_from_source_name(_source_name: Option<&str>) -> Option<Self> {
        None
    }

    pub async fn discover_from_source_name_async(source_name: Option<&str>) -> Option<Self> {
        let start = match source_name.map(PathBuf::from) {
            Some(path) if path.is_absolute() => {
                if is_file_async(&path).await {
                    path.parent().map(Path::to_path_buf)?
                } else {
                    path
                }
            }
            _ => runmat_filesystem::current_dir().ok()?,
        };
        let manifest_path = discover_project_manifest_from_async(&start).await?;
        let graph = build_project_composition_graph_async(&manifest_path)
            .await
            .ok()?;
        Some(Self::from_graph(manifest_path, graph))
    }

    pub fn all_source_files(&self) -> &[PathBuf] {
        &self.all_source_files
    }

    pub fn manifest_path(&self) -> &Path {
        &self.manifest_path
    }

    fn from_graph(manifest_path: PathBuf, graph: ProjectCompositionGraph) -> Self {
        let mut all_source_files = Vec::new();

        for package in graph.packages.values() {
            for source in &package.source_index.files {
                let source_file = package
                    .project_root
                    .join(&source.source_root)
                    .join(&source.relative_path);
                all_source_files.push(source_file.clone());
            }
        }
        all_source_files.sort();
        all_source_files.dedup();
        Self {
            manifest_path,
            all_source_files,
        }
    }
}

async fn is_file_async(path: &Path) -> bool {
    runmat_filesystem::metadata_async(path)
        .await
        .map(|meta| meta.is_file())
        .unwrap_or(false)
}