gluon_language-server 0.9.2

Language server providing completion for gluon
//! Name translation between LSP and gluon

use std::{
    env, fs,
    path::{Path, PathBuf},
};

use gluon::{base::filename_to_module, import::Import, Thread};

use check_importer::CheckImporter;

use codespan;
use failure;
use url::{self, Url};


pub(crate) fn codespan_name_to_file(name: &codespan::FileName) -> Result<Url, failure::Error> {
    match *name {
        codespan::FileName::Virtual(ref s) => module_name_to_file_(s),
        codespan::FileName::Real(ref p) => filename_to_url(p),
    }
}

fn codspan_name_to_module(name: &codespan::FileName) -> String {
    match *name {
        codespan::FileName::Virtual(ref s) => s.to_string(),
        codespan::FileName::Real(ref p) => filename_to_module(&p.display().to_string()),
    }
}

pub(crate) fn module_name_to_file_(s: &str) -> Result<Url, failure::Error> {
    let mut result = s.replace(".", "/");
    result.push_str(".glu");
    Ok(filename_to_url(Path::new(&result))
        .or_else(|_| url::Url::from_file_path(s))
        .map_err(|_| {
            failure::err_msg(format!("Unable to convert module name to a url: `{}`", s))
        })?)
}

pub(crate) fn filename_to_url(result: &Path) -> Result<Url, failure::Error> {
    let path = fs::canonicalize(&*result).or_else(|err| match env::current_dir() {
        Ok(path) => Ok(path.join(result)),
        Err(_) => Err(err),
    })?;
    Ok(url::Url::from_file_path(path).map_err(|_| {
        failure::err_msg(format!(
            "Unable to convert module name to a url: `{}`",
            result.display()
        ))
    })?)
}

pub(crate) fn module_name_to_file(importer: &CheckImporter, name: &codespan::FileName) -> Url {
    let s = codspan_name_to_module(name);
    importer
        .0
        .lock()
        .unwrap()
        .get(&s)
        .map(|source| source.uri.clone())
        .unwrap_or_else(|| module_name_to_file_(&s).unwrap())
}

pub(crate) fn with_import<F, R>(thread: &Thread, f: F) -> R
where
    F: FnOnce(&Import<CheckImporter>) -> R,
{
    let import = thread.get_macros().get("import").expect("Import macro");
    let import = import
        .downcast_ref::<Import<CheckImporter>>()
        .expect("Check importer");
    f(import)
}

pub(crate) fn strip_file_prefix_with_thread(thread: &Thread, url: &Url) -> String {
    with_import(thread, |import| {
        let paths = import.paths.read().unwrap();
        strip_file_prefix(&paths, url).unwrap_or_else(|err| panic!("{}", err))
    })
}

pub(crate) fn strip_file_prefix(paths: &[PathBuf], url: &Url) -> Result<String, failure::Error> {
    use std::env;

    let path = url
        .to_file_path()
        .map_err(|_| failure::err_msg("Expected a file uri"))?;
    let name = match fs::canonicalize(&*path) {
        Ok(name) => name,
        Err(_) => env::current_dir()?.join(&*path),
    };

    for path in paths {
        let canonicalized = fs::canonicalize(path).ok();

        let result = canonicalized
            .as_ref()
            .and_then(|path| name.strip_prefix(path).ok())
            .and_then(|path| path.to_str());
        if let Some(path) = result {
            return Ok(format!("{}", path));
        }
    }
    Ok(format!(
        "{}",
        name.strip_prefix(&env::current_dir()?)
            .unwrap_or_else(|_| &name)
            .display()
    ))
}

#[cfg(test)]
mod tests {
    use std::env;
    use std::path::PathBuf;

    use url::Url;

    use super::*;

    #[test]
    fn test_strip_file_prefix() {
        let renamed = strip_file_prefix(
            &[PathBuf::from(".")],
            &Url::from_file_path(env::current_dir().unwrap().join("test")).unwrap(),
        )
        .unwrap();
        assert_eq!(renamed, "test");
    }
}