1use std::{
2 collections::{HashMap, HashSet},
3 ops::Deref,
4};
5
6use crate::{
7 lsp_types::{CompletionItemKind, Diagnostic, DiagnosticSeverity, TextDocumentItem, TextEdit},
8 systems::PrefixEntry,
9};
10use bevy_ecs::prelude::*;
11use swls_lov::LocalPrefix;
12use tower_lsp::lsp_types::DiagnosticTag;
13use tracing::{debug, instrument};
14
15use crate::prelude::*;
16
17pub const PREFIX_CC: &'static str = include_str!("./prefix_cc.txt");
18
19#[derive(Debug, Clone)]
21pub struct Prefix {
22 pub prefix: String,
23 pub url: crate::lsp_types::Url,
24}
25
26#[derive(Component, Debug)]
34pub struct Prefixes(pub Vec<Prefix>, pub crate::lsp_types::Url);
35impl Deref for Prefixes {
36 type Target = Vec<Prefix>;
37
38 fn deref(&self) -> &Self::Target {
39 &self.0
40 }
41}
42impl Prefixes {
43 pub fn shorten(&self, value: &str) -> Option<String> {
44 let try_shorten = |prefix: &Prefix| {
45 let short = value.strip_prefix(prefix.url.as_str())?;
46 Some(format!("{}:{}", prefix.prefix, short))
47 };
48
49 self.0.iter().flat_map(try_shorten).next()
50 }
51
52 pub fn expand(&self, token: &Token) -> Option<String> {
53 match token {
54 Token::PNameLN(pref, x) => {
55 let pref = pref.as_ref().map(|x| x.as_str()).unwrap_or("");
56 let prefix = self.0.iter().find(|x| &x.prefix == pref)?;
57 Some(format!("{}{}", prefix.url, x))
58 }
59 Token::IRIRef(x) => {
60 return self.1.join(&x).ok().map(|x| x.to_string());
61 }
62 _ => None,
63 }
64 }
65
66 pub fn expand_json(&self, token: &Token) -> Option<String> {
67 match token {
68 Token::Str(pref, _) => {
69 if let Some(x) = pref.find(':') {
70 let prefix = &pref[..x];
71 if let Some(exp) = self.0.iter().find(|x| &x.prefix == prefix) {
72 return Some(format!("{}{}", exp.url.as_str(), &pref[x + 1..]));
73 }
74 } else {
75 if let Some(exp) = self.0.iter().find(|x| &x.prefix == pref) {
76 return Some(exp.url.as_str().to_string());
77 }
78 }
79
80 return Some(
81 self.1
82 .join(&pref)
83 .ok()
84 .map(|x| x.to_string())
85 .unwrap_or(pref.to_string()),
86 );
87 }
88 _ => None,
89 }
90 }
91}
92
93pub fn prefix_completion_helper<'a>(
94 word: &TokenComponent,
95 prefixes: &Prefixes,
96 completions: &mut Vec<SimpleCompletion>,
97 mut extra_edits: impl FnMut(&str, &str) -> Option<Vec<TextEdit>>,
98 lovs: impl Iterator<Item = &'a LocalPrefix>,
99 prefix_cc: impl Iterator<Item = &'a PrefixEntry>,
100 config: &LocalConfig,
101 ) {
103 match word.token.value() {
104 Token::Invalid(_) => {}
105 _ => return,
106 }
107
108 let mut defined = HashSet::new();
109 for p in prefixes.0.iter() {
110 defined.insert(p.url.as_str());
111 }
112
113 let mut suggested = HashSet::new();
114 completions.extend(
115 lovs.filter(|lov| lov.name.starts_with(&word.text))
116 .filter(|lov| !defined.contains(lov.namespace.as_ref()))
117 .flat_map(|lov| {
118 if suggested.contains(&lov.namespace) {
119 return None;
120 }
121 let new_text = format!("{}:", lov.name);
122 let filter_text = new_text.clone();
124 if new_text != word.text {
125 let extra_edit = extra_edits(&lov.name, &lov.namespace)?;
126 let completion = SimpleCompletion::new(
127 CompletionItemKind::MODULE,
128 format!("{}", lov.name),
129 crate::lsp_types::TextEdit {
130 new_text,
131 range: word.range.clone(),
132 },
133 )
134 .label_description(lov.title.as_ref())
135 .documentation(lov.namespace.as_ref())
136 .filter_text(filter_text);
138
139 let completion = extra_edit
140 .into_iter()
141 .fold(completion, |completion: SimpleCompletion, edit| {
142 completion.text_edit(edit)
143 });
144 suggested.insert(&lov.namespace);
145 Some(completion)
146 } else {
147 None
148 }
149 }),
150 );
151 completions.extend(
152 prefix_cc
153 .filter(|pref| pref.name.starts_with(&word.text))
154 .filter(|pref| !defined.contains(pref.namespace.as_ref()))
155 .filter(|lov| {
156 !config
157 .prefix_disabled
158 .iter()
159 .any(|x| lov.name.starts_with(x.as_str()))
160 })
161 .flat_map(|lov| {
162 if suggested.contains(&lov.namespace) {
163 return None;
164 }
165 let new_text = format!("{}:", lov.name);
166 let filter_text = new_text.clone();
168 if new_text != word.text {
169 let extra_edit = extra_edits(&lov.name, &lov.namespace)?;
170 let completion = SimpleCompletion::new(
171 CompletionItemKind::MODULE,
172 format!("{}", lov.name),
173 crate::lsp_types::TextEdit {
174 new_text,
175 range: word.range.clone(),
176 },
177 )
178 .documentation(lov.namespace.as_ref())
179 .filter_text(filter_text);
181
182 let completion = extra_edit
183 .into_iter()
184 .fold(completion, |completion: SimpleCompletion, edit| {
185 completion.text_edit(edit)
186 });
187 suggested.insert(&lov.namespace);
188 Some(completion)
189 } else {
190 None
191 }
192 }),
193 );
194}
195
196pub fn undefined_prefix(
197 query: Query<
198 (&Tokens, &Prefixes, &Wrapped<TextDocumentItem>, &RopeC),
199 (Or<(Changed<Prefixes>, Changed<Tokens>)>, With<Open>),
200 >,
201 mut client: ResMut<DiagnosticPublisher>,
202) {
203 for (tokens, prefixes, item, rope) in &query {
204 let mut diagnostics: Vec<Diagnostic> = Vec::new();
205 for t in &tokens.0 {
206 match t.value() {
207 Token::PNameLN(x, _) => {
208 let pref = x.as_ref().map(|x| x.as_str()).unwrap_or("");
209 let found = prefixes.0.iter().find(|x| x.prefix == pref).is_some();
210 if !found {
211 if let Some(range) = range_to_range(t.span(), &rope) {
212 diagnostics.push(Diagnostic {
213 range,
214 severity: Some(DiagnosticSeverity::ERROR),
215 source: Some(String::from("SWLS")),
216 message: format!("Undefined prefix {}", pref),
217 related_information: None,
218 ..Default::default()
219 })
220 }
221 }
222 }
223 _ => {}
224 }
225 }
226 let _ = client.publish(&item.0, diagnostics, "undefined_prefix");
227 }
228}
229
230pub fn unused_prefix(
235 query: Query<
236 (&Tokens, &Prefixes, &Wrapped<TextDocumentItem>, &RopeC),
237 (Or<(Changed<Prefixes>, Changed<Tokens>)>, With<Open>),
238 >,
239 mut client: ResMut<DiagnosticPublisher>,
240) {
241 for (tokens, prefixes, item, rope) in &query {
242 let mut diagnostics: Vec<Diagnostic> = Vec::new();
243
244 let mut used_prefixes: HashSet<&str> = HashSet::new();
247 let mut declaration_spans: HashMap<&str, std::ops::Range<usize>> = HashMap::new();
248
249 for (i, t) in tokens.0.iter().enumerate() {
250 if let Token::PNameLN(Some(pref), _) = t.value() {
251 let is_declaration = tokens.0[..i]
254 .iter()
255 .rev()
256 .find(|tok| !matches!(tok.value(), Token::Comment(_)))
257 .map(|tok| matches!(tok.value(), Token::PrefixTag | Token::SparqlPrefix))
258 .unwrap_or(false);
259
260 if is_declaration {
261 declaration_spans.insert(pref.as_str(), t.span().clone());
262 } else {
263 used_prefixes.insert(pref.as_str());
264 }
265 }
266 }
267
268 for prefix in prefixes.0.iter() {
270 if !used_prefixes.contains(prefix.prefix.as_str()) {
271 if let Some(span) = declaration_spans.get(prefix.prefix.as_str()) {
272 if let Some(range) = range_to_range(span, rope) {
273 diagnostics.push(Diagnostic {
274 range,
275 tags: Some(vec![DiagnosticTag::UNNECESSARY]),
276 severity: Some(DiagnosticSeverity::INFORMATION),
277 source: Some(String::from("SWLS")),
278 message: format!(
279 "Prefix '{}' is declared but never used",
280 prefix.prefix
281 ),
282 related_information: None,
283 ..Default::default()
284 });
285 }
286 }
287 }
288 }
289
290 let _ = client.publish(&item.0, diagnostics, "unused_prefix");
291 }
292}
293
294#[instrument(skip(query))]
295pub fn defined_prefix_completion(
296 mut query: Query<(&TokenComponent, &Prefixes, &mut CompletionRequest)>,
297) {
298 for (word, prefixes, mut req) in &mut query {
299 let st = &word.text;
300 let pref = if let Some(idx) = st.find(':') {
301 &st[..idx]
302 } else {
303 &st
304 };
305
306 debug!("matching {}", pref);
307
308 let completions = prefixes
309 .0
310 .iter()
311 .filter(|p| p.prefix.as_str().starts_with(pref))
312 .flat_map(|x| {
313 let new_text = format!("{}:", x.prefix.as_str());
314 if new_text != word.text {
315 Some(
316 SimpleCompletion::new(
317 CompletionItemKind::MODULE,
318 format!("{}", x.prefix.as_str()),
319 crate::lsp_types::TextEdit {
320 new_text,
321 range: word.range.clone(),
322 },
323 )
324 .documentation(x.url.as_str()),
325 )
326 } else {
327 None
328 }
329 });
330
331 req.0.extend(completions);
332 }
333}