lsp_core/systems/
prefix.rs

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