summaryrefslogtreecommitdiff
path: root/support/texlab/src/completion/latex/include.rs
blob: 2f4090b3f331f17be0a49139297f57884e6c0709 (plain)
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use super::combinators::{self, Parameter};
use crate::completion::factory;
use crate::range::RangeExt;
use crate::syntax::*;
use crate::workspace::*;
use futures_boxed::boxed;
use lsp_types::{CompletionItem, CompletionParams, Range, TextEdit};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct LatexIncludeCompletionProvider;

impl FeatureProvider for LatexIncludeCompletionProvider {
    type Params = CompletionParams;
    type Output = Vec<CompletionItem>;

    #[boxed]
    async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
        let parameters = LANGUAGE_DATA
            .include_commands
            .iter()
            .map(|cmd| Parameter::new(&cmd.name, cmd.index));

        combinators::argument_word(request, parameters, |command, index| {
            async move {
                if !request.document().is_file() {
                    return Vec::new();
                }

                let position = request.params.text_document_position.position;
                let mut items = Vec::new();
                let path_word = command.extract_word(index);
                let name_range = match path_word {
                    Some(path_word) => Range::new_simple(
                        path_word.start().line,
                        path_word.end().character
                            - path_word.text().split('/').last().unwrap().chars().count() as u64,
                        path_word.end().line,
                        path_word.end().character,
                    ),
                    None => Range::new(position, position),
                };
                let directory = current_directory(&request, &command);

                for entry in WalkDir::new(directory)
                    .min_depth(1)
                    .max_depth(1)
                    .follow_links(false)
                    .into_iter()
                    .filter_map(std::result::Result::ok)
                {
                    if entry.file_type().is_file() && is_included(&command, &entry.path()) {
                        let mut path = entry.into_path();
                        let include_extension = LANGUAGE_DATA
                            .include_commands
                            .iter()
                            .find(|cmd| command.name.text() == cmd.name)
                            .unwrap()
                            .include_extension;

                        if !include_extension {
                            remove_extension(&mut path);
                        }
                        let text_edit = make_text_edit(name_range, &path);
                        items.push(factory::file(request, &path, text_edit));
                    } else if entry.file_type().is_dir() {
                        let path = entry.into_path();
                        let text_edit = make_text_edit(name_range, &path);
                        items.push(factory::folder(request, &path, text_edit));
                    }
                }
                items
            }
        })
        .await
    }
}

fn current_directory(
    request: &FeatureRequest<CompletionParams>,
    command: &LatexCommand,
) -> PathBuf {
    let mut path = request.document().uri.to_file_path().unwrap();
    path = PathBuf::from(path.to_string_lossy().into_owned().replace('\\', "/"));

    path.pop();
    if let Some(include) = command.extract_word(0) {
        path.push(include.text());
        if !include.text().ends_with('/') {
            path.pop();
        }
    }
    path
}

fn is_included(command: &LatexCommand, file: &Path) -> bool {
    if let Some(allowed_extensions) = LANGUAGE_DATA
        .include_commands
        .iter()
        .find(|cmd| command.name.text() == cmd.name)
        .unwrap()
        .kind
        .extensions()
    {
        file.extension()
            .map(|extension| extension.to_string_lossy().to_lowercase())
            .map(|extension| allowed_extensions.contains(&extension.as_str()))
            .unwrap_or(false)
    } else {
        true
    }
}

fn remove_extension(path: &mut PathBuf) {
    let stem = path
        .file_stem()
        .map(|stem| stem.to_string_lossy().into_owned());

    if let Some(stem) = stem {
        path.pop();
        path.push(PathBuf::from(stem));
    }
}

fn make_text_edit(range: Range, path: &Path) -> TextEdit {
    let text = path.file_name().unwrap().to_string_lossy().into_owned();
    TextEdit::new(range, text)
}