1use std::{borrow::Cow, collections::HashSet};
2
3use bevy_ecs::prelude::*;
4use completion::{CompletionRequest, SimpleCompletion};
5use hover::HoverRequest;
6use sophia_api::{
7 ns::rdfs,
8 prelude::{Any, Dataset},
9 quad::Quad,
10 term::Term,
11};
12use systems::OntologyExtractor;
13use tracing::{debug, info, instrument};
14
15use crate::{
16 lsp_types::{CompletionItemKind, TextEdit},
17 prelude::*,
18 util::{ns::*, triple::MyTerm},
19};
20
21#[derive(PartialEq, Eq, Hash)]
22pub struct DefinedClass {
23 pub term: MyTerm<'static>,
24 pub label: String,
25 pub comment: String,
26 pub reason: &'static str,
27 pub location: std::ops::Range<usize>,
28}
29
30pub type DefinedClasses = HashSet<DefinedClass>;
31
32fn derive_class(
33 subject: <MyTerm<'_> as Term>::BorrowTerm<'_>,
34 triples: &Triples,
35 source: &'static str,
36) -> Option<DefinedClass> {
37 let label = triples
38 .object([subject], [rdfs::label, dc::title])?
39 .to_owned()
40 .as_str()
41 .to_string();
42 let comment = triples
43 .object([subject], [rdfs::comment, dc::description])?
44 .to_owned()
45 .as_str()
46 .to_string();
47 Some(DefinedClass {
48 label,
49 comment,
50 term: subject.to_owned(),
51 reason: source,
52 location: subject.span.clone(),
53 })
54}
55
56pub fn derive_classes(
57 query: Query<(Entity, &Triples, &Label), (Changed<Triples>, Without<Dirty>)>,
58 mut commands: Commands,
59 extractor: Res<OntologyExtractor>,
60) {
61 for (e, triples, label) in &query {
62 let classes: HashSet<_> = triples
63 .0
64 .quads_matching(Any, [rdf::type_], extractor.classes(), Any)
65 .flatten()
66 .flat_map(|x| derive_class(x.s(), &triples, "owl_class"))
67 .collect();
68
69 info!(
70 "({} classes) Found {} classes for {} ({} triples)",
71 extractor.classes().len(),
72 classes.len(),
73 label.0,
74 triples.0.len()
75 );
76 commands.entity(e).insert(Wrapped(classes));
77 }
78}
79
80#[instrument(skip(query, other))]
81pub fn complete_class(
82 mut query: Query<(
83 &TokenComponent,
84 &TripleComponent,
85 &Prefixes,
86 &DocumentLinks,
87 &Label,
88 &mut CompletionRequest,
89 )>,
90 other: Query<(&Label, &Wrapped<DefinedClasses>)>,
91) {
92 for (token, triple, prefixes, links, this_label, mut request) in &mut query {
93 if triple.triple.predicate.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
94 && triple.target == TripleTarget::Object
95 {
96 for (label, classes) in &other {
97 if links
99 .iter()
100 .find(|link| link.0.as_str().starts_with(label.0.as_str()))
101 .is_none()
102 && label.0 != this_label.0
103 {
104 debug!(
105 "Not looking for defined classes in {} (not linked)",
106 label.0
107 );
108 continue;
109 }
110
111 let st = classes.0.iter().take(5).fold(String::new(), |mut st, b| {
112 if let Some(short) = prefixes.shorten(&b.term.value) {
113 st += &short;
114 } else {
115 st += &b.term.value;
116 }
117 st += ", ";
118 st
119 });
120 debug!(
121 "Looking for defined classes in {} (found {} {})",
122 label.0,
123 classes.0.len(),
124 st
125 );
126
127 for class in classes.0.iter() {
128 let to_beat = prefixes
129 .shorten(&class.term.value)
130 .map(|x| Cow::Owned(x))
131 .unwrap_or(class.term.value.clone());
132
133 if to_beat.starts_with(&token.text) {
134 request.push(
135 SimpleCompletion::new(
136 CompletionItemKind::CLASS,
137 format!("{}", to_beat),
138 TextEdit {
139 range: token.range.clone(),
140 new_text: to_beat.to_string(),
141 },
142 )
143 .documentation(&class.comment),
144 );
145 }
146 }
147 }
148 }
149 }
150}
151
152pub fn hover_class(
153 mut query: Query<(
154 &TokenComponent,
155 &Prefixes,
156 &DocumentLinks,
157 &mut HoverRequest,
158 )>,
159 other: Query<(&Label, &Wrapped<DefinedClasses>)>,
160) {
161 for (token, prefixes, links, mut request) in &mut query {
162 if let Some(target) = prefixes.expand(token.token.value()) {
163 for (label, classes) in &other {
164 if links.iter().find(|link| link.0 == label.0).is_none() {
166 continue;
167 }
168
169 for c in classes.iter().filter(|c| c.term.value == target) {
170 request.0.push(format!("{}: {}", c.label, c.comment));
171 }
172 }
173 }
174 }
175}
176
177#[derive(PartialEq, Eq, Hash)]
178pub struct DefinedProperty {
179 pub predicate: MyTerm<'static>,
180 pub comment: String,
181 pub label: String,
182 pub range: Vec<String>,
183 pub domain: Vec<String>,
184 pub reason: &'static str,
185}
186pub type DefinedProperties = HashSet<DefinedProperty>;
187
188fn derive_property(
189 subject: <MyTerm<'_> as Term>::BorrowTerm<'_>,
190 triples: &Triples,
191 source: &'static str,
192) -> Option<DefinedProperty> {
193 let label = triples
194 .object([subject], [rdfs::label, dc::title])?
195 .to_owned()
196 .as_str()
197 .to_string();
198 let comment = triples
199 .object([subject], [rdfs::comment, dc::description])?
200 .to_owned()
201 .as_str()
202 .to_string();
203 let domain: Vec<_> = triples
204 .objects([subject], [rdfs::domain])
205 .map(|x| x.as_str().to_string())
206 .collect();
207
208 let range: Vec<_> = triples
209 .objects([subject], [rdfs::range])
210 .map(|x| x.as_str().to_string())
211 .collect();
212
213 Some(DefinedProperty {
214 predicate: subject.to_owned(),
215 range,
216 domain,
217 label,
218 comment,
219 reason: source,
220 })
221}
222
223pub fn derive_properties(
224 query: Query<(Entity, &Triples, &Label), (Changed<Triples>, Without<Dirty>)>,
225 mut commands: Commands,
226 extractor: Res<OntologyExtractor>,
227) {
228 for (e, triples, label) in &query {
229 let classes: HashSet<_> = triples
230 .0
231 .quads_matching(Any, [rdf::type_], extractor.properties(), Any)
232 .flatten()
233 .flat_map(|x| derive_property(x.s(), &triples, "owl_property"))
234 .collect();
235
236 info!(
237 "({} properties) Found {} properties for {} ({} triples)",
238 extractor.properties().len(),
239 classes.len(),
240 label.0.as_str(),
241 triples.0.len()
242 );
243 commands.entity(e).insert(Wrapped(classes));
244 }
245}
246
247#[instrument(skip(query, other, hierarchy))]
248pub fn complete_properties(
249 mut query: Query<(
250 &TokenComponent,
251 &TripleComponent,
252 &Prefixes,
253 &DocumentLinks,
254 &Label,
255 &Types,
256 &mut CompletionRequest,
257 )>,
258 other: Query<(&Label, &Wrapped<DefinedProperties>)>,
259 hierarchy: Res<TypeHierarchy<'static>>,
260) {
261 debug!("Complete properties");
262 for (token, triple, prefixes, links, this_label, types, mut request) in &mut query {
263 debug!("target {:?} text {}", triple.target, token.text);
264 debug!("links {:?}", links);
265 if triple.target == TripleTarget::Predicate {
266 let tts = types.get(&triple.triple.subject.value);
267 for (label, properties) in &other {
268 if links
270 .iter()
271 .find(|link| link.0.as_str().starts_with(label.0.as_str()))
272 .is_none()
273 && label.0 != this_label.0
274 {
275 debug!("This link is ignored {}", label.as_str());
276 continue;
277 }
278
279 for class in properties.0.iter() {
280 let to_beat = prefixes
281 .shorten(&class.predicate.value)
282 .map(|x| Cow::Owned(x))
283 .unwrap_or(class.predicate.value.clone());
284
285 debug!(
286 "{} starts with {} = {}",
287 to_beat,
288 token.text,
289 to_beat.starts_with(&token.text)
290 );
291
292 if to_beat.starts_with(&token.text) {
293 let correct_domain = class.domain.iter().any(|domain| {
294 if let Some(domain_id) = hierarchy.get_id_ref(&domain) {
295 if let Some(tts) = tts {
296 tts.iter().any(|tt| *tt == domain_id)
297 } else {
298 false
299 }
300 } else {
301 false
302 }
303 });
304
305 let mut completion = SimpleCompletion::new(
306 CompletionItemKind::PROPERTY,
307 format!("{}", to_beat),
308 TextEdit {
309 range: token.range.clone(),
310 new_text: to_beat.to_string(),
311 },
312 )
313 .label_description(&class.comment);
314
315 if correct_domain {
316 completion.kind = CompletionItemKind::FIELD;
317 debug!("Property has correct domain {}", to_beat);
318 request.push(completion.sort_text("1"));
319 } else {
320 request.push(completion);
321 }
322 }
323 }
324 }
325 }
326 }
327}
328
329#[instrument(skip(query, other))]
330pub fn hover_property(
331 mut query: Query<(
332 &TokenComponent,
333 &Prefixes,
334 &DocumentLinks,
335 &mut HoverRequest,
336 )>,
337 other: Query<(&Label, Option<&Prefixes>, &Wrapped<DefinedProperties>)>,
338) {
339 for (token, prefixes, links, mut request) in &mut query {
340 if let Some(target) = prefixes.expand(token.token.value()) {
341 for (label, p2, classes) in &other {
342 if links.iter().find(|link| link.0 == label.0).is_none() {
344 continue;
345 }
346
347 let shorten = |from: &str| {
348 if let Some(x) = prefixes.shorten(from) {
349 return Some(x);
350 }
351
352 if let Some(p) = p2 {
353 return p.shorten(from);
354 }
355
356 None
357 };
358
359 for c in classes.iter().filter(|c| c.predicate.value == target) {
360 request.0.push(format!("{}: {}", c.label, c.comment));
361 for r in &c.range {
362 let range = shorten(&r);
363 request.0.push(format!(
364 "Range {}",
365 range.as_ref().map(|x| x.as_str()).unwrap_or(r.as_str())
366 ));
367 }
368
369 for d in &c.domain {
370 let domain = shorten(&d);
371 request.0.push(format!(
372 "Domain {}",
373 domain.as_ref().map(|x| x.as_str()).unwrap_or(d.as_str())
374 ));
375 }
376 }
377 }
378 }
379 }
380}