lsp_core/systems/
prefix.rs

1use std::{collections::HashSet, ops::Deref};
2
3use bevy_ecs::prelude::*;
4use lov::LocalPrefix;
5use crate::lsp_types::{CompletionItemKind, Diagnostic, DiagnosticSeverity, TextDocumentItem, TextEdit};
6use tracing::{debug, instrument};
7
8use crate::prelude::*;
9
10pub const PREFIX_CC: &'static str = include_str!("./prefix_cc.txt");
11
12/// One defined prefix, maps prefix to url
13#[derive(Debug, Clone)]
14pub struct Prefix {
15    pub prefix: String,
16    pub url: crate::lsp_types::Url,
17}
18
19/// [`Component`] that containing defined prefixes and base URL.
20///
21/// [`lsp_core`](crate) uses [`Prefixes`] in different systems, for example
22/// - to check for undefined prefixes diagnostics with
23/// [`undefined_prefix`](crate::prelude::systems::undefined_prefix)
24/// - derive linked documents [`DocumentLinks`] with
25/// [`derive_prefix_links`](crate::prelude::systems::derive_prefix_links)
26#[derive(Component, Debug)]
27pub struct Prefixes(pub Vec<Prefix>, pub crate::lsp_types::Url);
28impl Deref for Prefixes {
29    type Target = Vec<Prefix>;
30
31    fn deref(&self) -> &Self::Target {
32        &self.0
33    }
34}
35impl Prefixes {
36    pub fn shorten(&self, value: &str) -> Option<String> {
37        let try_shorten = |prefix: &Prefix| {
38            let short = value.strip_prefix(prefix.url.as_str())?;
39            Some(format!("{}:{}", prefix.prefix, short))
40        };
41
42        self.0.iter().flat_map(try_shorten).next()
43    }
44
45    pub fn expand(&self, token: &Token) -> Option<String> {
46        match token {
47            Token::PNameLN(pref, x) => {
48                let pref = pref.as_ref().map(|x| x.as_str()).unwrap_or("");
49                let prefix = self.0.iter().find(|x| &x.prefix == pref)?;
50                Some(format!("{}{}", prefix.url, x))
51            }
52            Token::IRIRef(x) => {
53                return self.1.join(&x).ok().map(|x| x.to_string());
54            }
55            _ => None,
56        }
57    }
58
59    pub fn expand_json(&self, token: &Token) -> Option<String> {
60        match token {
61            Token::Str(pref, _) => {
62                if let Some(x) = pref.find(':') {
63                    let prefix = &pref[..x];
64                    if let Some(exp) = self.0.iter().find(|x| &x.prefix == prefix) {
65                        return Some(format!("{}{}", exp.url.as_str(), &pref[x + 1..]));
66                    }
67                } else {
68                    if let Some(exp) = self.0.iter().find(|x| &x.prefix == pref) {
69                        return Some(exp.url.as_str().to_string());
70                    }
71                }
72
73                return Some(
74                    self.1
75                        .join(&pref)
76                        .ok()
77                        .map(|x| x.to_string())
78                        .unwrap_or(pref.to_string()),
79                );
80            }
81            _ => None,
82        }
83    }
84}
85
86pub fn prefix_completion_helper<'a>(
87    word: &TokenComponent,
88    prefixes: &Prefixes,
89    completions: &mut Vec<SimpleCompletion>,
90    mut extra_edits: impl FnMut(&str, &str) -> Option<Vec<TextEdit>>,
91    lovs: impl Iterator<Item = &'a LocalPrefix>,
92    // known: &KnownPrefixes,
93) {
94    match word.token.value() {
95        Token::Invalid(_) => {}
96        _ => return,
97    }
98
99    let mut defined = HashSet::new();
100    for p in prefixes.0.iter() {
101        defined.insert(p.url.as_str());
102    }
103
104    completions.extend(
105        lovs.filter(|lov| lov.name.starts_with(&word.text))
106            .filter(|lov| !defined.contains(lov.location.as_ref()))
107            .flat_map(|lov| {
108                let new_text = format!("{}:", lov.name);
109                let sort_text = format!("{}", lov.rank);
110                let filter_text = new_text.clone();
111                if new_text != word.text {
112                    let extra_edit = extra_edits(&lov.name, &lov.location)?;
113                    let completion = SimpleCompletion::new(
114                        CompletionItemKind::MODULE,
115                        format!("{}", lov.name),
116                        crate::lsp_types::TextEdit {
117                            new_text,
118                            range: word.range.clone(),
119                        },
120                    )
121                    .label_description(lov.title.as_ref())
122                    .sort_text(sort_text)
123                    .filter_text(filter_text);
124
125                    let completion = extra_edit
126                        .into_iter()
127                        .fold(completion, |completion: SimpleCompletion, edit| {
128                            completion.text_edit(edit)
129                        });
130                    Some(completion)
131                } else {
132                    None
133                }
134            }),
135    );
136}
137
138pub fn undefined_prefix(
139    query: Query<
140        (&Tokens, &Prefixes, &Wrapped<TextDocumentItem>, &RopeC),
141        Or<(Changed<Prefixes>, Changed<Tokens>)>,
142    >,
143    mut client: ResMut<DiagnosticPublisher>,
144) {
145    for (tokens, prefixes, item, rope) in &query {
146        let mut diagnostics: Vec<Diagnostic> = Vec::new();
147        for t in &tokens.0 {
148            match t.value() {
149                Token::PNameLN(x, _) => {
150                    let pref = x.as_ref().map(|x| x.as_str()).unwrap_or("");
151                    let found = prefixes.0.iter().find(|x| x.prefix == pref).is_some();
152                    if !found {
153                        if let Some(range) = range_to_range(t.span(), &rope) {
154                            diagnostics.push(Diagnostic {
155                                range,
156                                severity: Some(DiagnosticSeverity::ERROR),
157                                source: Some(String::from("SWLS")),
158                                message: format!("Undefined prefix {}", pref),
159                                related_information: None,
160                                ..Default::default()
161                            })
162                        }
163                    }
164                }
165                _ => {}
166            }
167        }
168        let _ = client.publish(&item.0, diagnostics, "undefined_prefix");
169    }
170}
171
172#[instrument(skip(query))]
173pub fn defined_prefix_completion(
174    mut query: Query<(&TokenComponent, &Prefixes, &mut CompletionRequest)>,
175) {
176    for (word, prefixes, mut req) in &mut query {
177        let st = &word.text;
178        let pref = if let Some(idx) = st.find(':') {
179            &st[..idx]
180        } else {
181            &st
182        };
183
184        debug!("matching {}", pref);
185
186        let completions = prefixes
187            .0
188            .iter()
189            .filter(|p| p.prefix.as_str().starts_with(pref))
190            .flat_map(|x| {
191                let new_text = format!("{}:", x.prefix.as_str());
192                if new_text != word.text {
193                    Some(
194                        SimpleCompletion::new(
195                            CompletionItemKind::MODULE,
196                            format!("{}", x.prefix.as_str()),
197                            crate::lsp_types::TextEdit {
198                                new_text,
199                                range: word.range.clone(),
200                            },
201                        )
202                        .documentation(x.url.as_str()),
203                    )
204                } else {
205                    None
206                }
207            });
208
209        req.0.extend(completions);
210    }
211}