1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
use crate::{
    doc::{ItemType, ParseItemTypeError},
    Error
};
use std::{
    path::{Path, PathBuf},
    str::FromStr
};

#[derive(Debug, Error)]
pub enum LocationError {
    #[error("Invalid format")]
    InvalidFormat,
    #[error(transparent)]
    ParseItemTypeError(#[from] ParseItemTypeError),
    #[error("File not found")]
    FileNotFound,
    #[error("Item not found")]
    ItemNotFound
}

pub const FILETYPE: &[ItemType] = &[
    ItemType::Struct,
    ItemType::Union,
    ItemType::Enum,
    ItemType::Function,
    ItemType::Typedef,
    ItemType::Static,
    ItemType::Trait,
    ItemType::Macro,
    ItemType::Primitive,
    ItemType::Constant,
    ItemType::Keyword,
    ItemType::ProcAttribute,
    ItemType::ProcDerive
];

pub async fn location_from_line(line: &str) -> Result<Option<String>, Error> {
    let (fst, snd) = {
        let mut a = line.split_whitespace();
        a.next()
            .and_then(|fst| a.next().map(|snd| (fst, snd)))
            .ok_or(LocationError::InvalidFormat)
    }?;
    let ty = ItemType::from_str(snd).map_err(LocationError::from)?;
    let path_components = fst.split("::").collect::<Vec<_>>();
    let krate_name = *path_components.get(0).ok_or(LocationError::InvalidFormat)?;
    let search_indexes = crate::search_index::search_indexes().await?;
    let doc_dirs: Vec<_> = search_indexes
        .iter()
        .map(|file| file.parent().unwrap())
        .collect();
    let doc_dir = match doc_dirs.iter().find(|dir| dir.join(krate_name).is_dir()) {
        Some(p) => *p,
        None => return Ok(None)
    };
    let (file, rest) = find_file(doc_dir, &path_components).ok_or(LocationError::FileNotFound)?;
    let url = item_url(&file, rest, ty);
    Ok(Some(url))
}

fn item_url(file: &Path, rest: &[&str], ty: ItemType) -> String {
    if rest.is_empty() {
        format!("file://{}", file.display())
    } else {
        let top = rest[0];
        format!("file://{}#{}.{}", file.display(), ty.as_str(), top)
    }
}

// TODO: escaping??
fn find_file<'a, 'b>(
    dir: &Path,
    path_components: &'a [&'b str]
) -> Option<(PathBuf, &'a [&'b str])> {
    let (cd, mut rest) = step_into_module(dir, path_components);
    if rest.is_empty() {
        return Some((cd.join("index.html"), rest));
    }
    let top = rest[0];
    let found = FILETYPE
        .iter()
        .map(|ty| cd.join(format!("{}.{}.html", ty.as_str(), top)))
        .find(|p| p.is_file())?;
    rest = &rest[1..];
    Some((found, rest))
}

fn step_into_module<'a, 'b>(
    dir: &Path,
    path_components: &'a [&'b str]
) -> (PathBuf, &'a [&'b str]) {
    let mut cd: PathBuf = dir.into();
    let mut rest: &[&str] = path_components;
    while !rest.is_empty() {
        let top = rest[0];
        let attempt = cd.join(top);
        if !attempt.is_dir() {
            break;
        }
        rest = &rest[1..];
        cd = attempt;
    }
    (cd, rest)
}