swls_core/systems/
prefix.rs1use 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#[derive(Debug, Clone)]
18pub struct Prefix {
19 pub prefix: String,
20 pub url: crate::lsp_types::Url,
21}
22
23#[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 .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 }
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}