Skip to main content

swls_core/systems/
prefix.rs

1use std::{collections::HashSet, ops::Deref};
2
3use bevy_ecs::prelude::*;
4use swls_lov::LocalPrefix;
5use tower_lsp::lsp_types::Range;
6use tracing::{debug, instrument};
7
8use crate::{
9    lsp_types::{CompletionItemKind, TextEdit},
10    prelude::*,
11    systems::PrefixEntry,
12};
13
14pub const PREFIX_CC: &'static str = include_str!("./prefix_cc.txt");
15
16/// One defined prefix, maps prefix name to URL.
17#[derive(Debug, Clone)]
18pub struct Prefix {
19    pub prefix: String,
20    pub url: crate::lsp_types::Url,
21}
22
23/// [`Component`] containing defined prefixes and base URL.
24#[derive(Component, Debug)]
25pub struct Prefixes(pub Vec<Prefix>, pub crate::lsp_types::Url);
26
27impl Deref for Prefixes {
28    type Target = Vec<Prefix>;
29
30    fn deref(&self) -> &Self::Target {
31        &self.0
32    }
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        self.0.iter().flat_map(try_shorten).next()
42    }
43}
44
45enum PrefixLike<'a> {
46    Prefix(&'a Prefix),
47    Local(&'a LocalPrefix),
48    Entry(&'a PrefixEntry),
49}
50impl PrefixLike<'_> {
51    fn as_completion(
52        &self,
53        lang: &DynLang,
54        range: Range,
55        extra: Option<Vec<TextEdit>>,
56    ) -> SimpleCompletion {
57        let (name, title, namespace) = match self {
58            PrefixLike::Prefix(prefix) => (prefix.prefix.as_str(), None, prefix.url.as_str()),
59            PrefixLike::Local(local_prefix) => (
60                local_prefix.name.as_ref(),
61                Some(local_prefix.title.as_ref()),
62                local_prefix.namespace.as_ref(),
63            ),
64            PrefixLike::Entry(prefix_entry) => (
65                prefix_entry.name.as_ref(),
66                None,
67                prefix_entry.namespace.as_ref(),
68            ),
69        };
70
71        let nt = format!("{}:$0", name);
72        let mut completion = SimpleCompletion::new(
73            CompletionItemKind::MODULE,
74            format!("{}", name),
75            crate::lsp_types::TextEdit {
76                new_text: lang.quote(&nt),
77                range,
78            },
79        )
80        .documentation(namespace)
81        // .filter_text(new_text)
82        .as_snippet();
83
84        if let Some(title) = title {
85            completion = completion.label_description(title);
86        }
87
88        if let Some(extra) = extra {
89            for e in extra {
90                completion = completion.text_edit(e);
91            }
92        }
93
94        completion
95    }
96
97    fn name(&self) -> &str {
98        match self {
99            PrefixLike::Prefix(prefix) => prefix.prefix.as_str(),
100            PrefixLike::Local(local_prefix) => local_prefix.name.as_ref(),
101            PrefixLike::Entry(prefix_entry) => prefix_entry.name.as_ref(),
102        }
103    }
104
105    fn namespace(&self) -> &str {
106        match self {
107            PrefixLike::Prefix(prefix) => prefix.url.as_str(),
108            PrefixLike::Local(local_prefix) => local_prefix.namespace.as_ref(),
109            PrefixLike::Entry(prefix_entry) => prefix_entry.namespace.as_ref(),
110        }
111    }
112}
113
114pub fn prefix_completion_helper<'a>(
115    word: &TokenComponent,
116    prefixes: &Prefixes,
117    completions: &mut Vec<SimpleCompletion>,
118    mut extra_edits: impl FnMut(&str, &str) -> Option<Vec<TextEdit>>,
119    lovs: impl Iterator<Item = &'a LocalPrefix>,
120    prefix_cc: impl Iterator<Item = &'a PrefixEntry>,
121    lang: &DynLang,
122) {
123    let mut suggested = HashSet::new();
124    let text = lang.unquote(&word.text);
125    tracing::debug!("completion helper {:?}", word);
126
127    completions.extend(
128        lovs.map(|x| PrefixLike::Local(&x))
129            .chain(prefix_cc.map(|x| PrefixLike::Entry(x)))
130            .chain(prefixes.iter().map(PrefixLike::Prefix))
131            .filter(|p| p.name().starts_with(text))
132            .filter(|p| p.namespace().ends_with('#') || p.namespace().ends_with('/'))
133            .flat_map(|lov| {
134                if suggested.contains(lov.name()) {
135                    return None;
136                }
137
138                let completion = lov.as_completion(
139                    lang,
140                    word.range.clone(),
141                    extra_edits(&lov.name(), &lov.namespace()),
142                );
143
144                suggested.insert(lov.name().to_string());
145                Some(completion)
146            }),
147    );
148
149    // completions.extend(
150    //     lovs.filter(|lov| lov.name.starts_with(text))
151    //         .flat_map(|lov| {
152    //             if suggested.contains(&lov.namespace) {
153    //                 tracing::info!("suggested contains it {:?}", lov);
154    //                 return None;
155    //             }
156    //
157    //             let completion = PrefixLike::Local(lov).as_completion(
158    //                 lang,
159    //                 word.range.clone(),
160    //                 extra_edits(&lov.name, &lov.namespace),
161    //             );
162    //
163    //             suggested.insert(&lov.namespace);
164    //             Some(completion)
165    //         }),
166    // );
167    //
168    // completions.extend(
169    //     prefix_cc
170    //         .filter(|pref| pref.name.starts_with(text))
171    //         // .filter(|pref| !defined.contains(pref.namespace.as_ref()))
172    //         // .filter(|lov| {
173    //         //     !config
174    //         //         .prefix_disabled
175    //         //         .iter()
176    //         //         .any(|x| lov.name.starts_with(x.as_str()))
177    //         // })
178    //         .flat_map(|lov| {
179    //             if suggested.contains(&lov.namespace) {
180    //                 return None;
181    //             }
182    //             let mut new_text = format!("{}:", lov.name);
183    //             let filter_text = new_text.clone();
184    //             if new_text != text {
185    //                 new_text += "$0";
186    //                 let extra_edit = extra_edits(&lov.name, &lov.namespace);
187    //                 let completion = SimpleCompletion::new(
188    //                     CompletionItemKind::MODULE,
189    //                     format!("{}", lov.name),
190    //                     crate::lsp_types::TextEdit {
191    //                         new_text: lang.quote(&new_text),
192    //                         range: word.range.clone(),
193    //                     },
194    //                 )
195    //                 .documentation(lov.namespace.as_ref())
196    //                 .filter_text(filter_text)
197    //                 .as_snippet();
198    //
199    //                 let completion = extra_edit
200    //                     .into_iter()
201    //                     .flatten()
202    //                     .fold(completion, |completion: SimpleCompletion, edit| {
203    //                         completion.text_edit(edit)
204    //                     });
205    //
206    //                 suggested.insert(&lov.namespace);
207    //                 Some(completion)
208    //             } else {
209    //                 tracing::info!("new_text is word.text");
210    //                 None
211    //             }
212    //         }),
213    // );
214}
215
216#[instrument(skip(query))]
217pub fn defined_prefix_completion(
218    mut query: Query<(&TokenComponent, &Prefixes, &mut CompletionRequest, &DynLang)>,
219) {
220    for (word, prefixes, mut req, lang) in &mut query {
221        if lang.handles_prefix_completion() {
222            continue;
223        }
224        let st = lang.unquote(&word.text);
225        let pref = if let Some(idx) = st.find(':') {
226            &st[..idx]
227        } else {
228            &st
229        };
230
231        debug!("matching {}", pref);
232
233        let completions = prefixes
234            .0
235            .iter()
236            .filter(|p| p.prefix.as_str().starts_with(pref))
237            .flat_map(|x| {
238                let mut new_text = format!("{}:", x.prefix.as_str());
239                if new_text != word.text {
240                    new_text += "$0";
241                    let nt = lang.quote(&new_text);
242
243                    Some(
244                        SimpleCompletion::new(
245                            CompletionItemKind::MODULE,
246                            x.prefix.to_string(),
247                            crate::lsp_types::TextEdit {
248                                new_text: nt,
249                                range: word.range.clone(),
250                            },
251                        )
252                        .documentation(x.url.as_str())
253                        .as_snippet(),
254                    )
255                } else {
256                    None
257                }
258            });
259
260        req.0.extend(completions);
261    }
262}