1use std::{borrow::Cow, collections::HashSet};
2
3use bevy_ecs::prelude::*;
4use completion::{CompletionRequest, SimpleCompletion};
5use hover::HoverRequest;
6use lsp_types::{CompletionItemKind, TextEdit};
7use sophia_api::{
8 ns::rdfs,
9 prelude::{Any, Dataset},
10 quad::Quad,
11 term::Term,
12};
13use systems::OntologyExtractor;
14use tracing::{debug, info, instrument};
15
16use crate::{
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])?
39 .to_owned()
40 .as_str()
41 .to_string();
42 let comment = triples
43 .object([subject], [rdfs::comment])?
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 debug!("Looking for defined classes in {}", label.0);
111
112 for class in classes.0.iter() {
113 let to_beat = prefixes
114 .shorten(&class.term.value)
115 .map(|x| Cow::Owned(x))
116 .unwrap_or(class.term.value.clone());
117
118 if to_beat.starts_with(&token.text) {
119 request.push(
120 SimpleCompletion::new(
121 CompletionItemKind::CLASS,
122 format!("{}", to_beat),
123 TextEdit {
124 range: token.range.clone(),
125 new_text: to_beat.to_string(),
126 },
127 )
128 .documentation(&class.comment),
129 );
130 }
131 }
132 }
133 }
134 }
135}
136
137pub fn hover_class(
138 mut query: Query<(
139 &TokenComponent,
140 &Prefixes,
141 &DocumentLinks,
142 &mut HoverRequest,
143 )>,
144 other: Query<(&Label, &Wrapped<DefinedClasses>)>,
145) {
146 for (token, prefixes, links, mut request) in &mut query {
147 if let Some(target) = prefixes.expand(token.token.value()) {
148 for (label, classes) in &other {
149 if links.iter().find(|link| link.0 == label.0).is_none() {
151 continue;
152 }
153
154 for c in classes.iter().filter(|c| c.term.value == target) {
155 request.0.push(format!("{}: {}", c.label, c.comment));
156 }
157 }
158 }
159 }
160}
161
162#[derive(PartialEq, Eq, Hash)]
163pub struct DefinedProperty {
164 pub predicate: MyTerm<'static>,
165 pub comment: String,
166 pub label: String,
167 pub range: Vec<String>,
168 pub domain: Vec<String>,
169 pub reason: &'static str,
170}
171pub type DefinedProperties = HashSet<DefinedProperty>;
172
173fn derive_property(
174 subject: <MyTerm<'_> as Term>::BorrowTerm<'_>,
175 triples: &Triples,
176 source: &'static str,
177) -> Option<DefinedProperty> {
178 let label = triples
179 .object([subject], [rdfs::label])?
180 .to_owned()
181 .as_str()
182 .to_string();
183 let comment = triples
184 .object([subject], [rdfs::comment])?
185 .to_owned()
186 .as_str()
187 .to_string();
188 let domain: Vec<_> = triples
189 .objects([subject], [rdfs::domain])
190 .map(|x| x.as_str().to_string())
191 .collect();
192
193 let range: Vec<_> = triples
194 .objects([subject], [rdfs::range])
195 .map(|x| x.as_str().to_string())
196 .collect();
197
198 Some(DefinedProperty {
199 predicate: subject.to_owned(),
200 range,
201 domain,
202 label,
203 comment,
204 reason: source,
205 })
206}
207
208pub fn derive_properties(
209 query: Query<(Entity, &Triples, &Label), (Changed<Triples>, Without<Dirty>)>,
210 mut commands: Commands,
211 extractor: Res<OntologyExtractor>,
212) {
213 for (e, triples, label) in &query {
214 let classes: HashSet<_> = triples
215 .0
216 .quads_matching(Any, [rdf::type_], extractor.properties(), Any)
217 .flatten()
218 .flat_map(|x| derive_property(x.s(), &triples, "owl_property"))
219 .collect();
220
221 info!(
222 "({} properties) Found {} properties for {} ({} triples)",
223 extractor.properties().len(),
224 classes.len(),
225 label.0.as_str(),
226 triples.0.len()
227 );
228 commands.entity(e).insert(Wrapped(classes));
229 }
230}
231
232#[instrument(skip(query, other, hierarchy))]
233pub fn complete_properties(
234 mut query: Query<(
235 &TokenComponent,
236 &TripleComponent,
237 &Prefixes,
238 &DocumentLinks,
239 &Label,
240 &Types,
241 &mut CompletionRequest,
242 )>,
243 other: Query<(&Label, &Wrapped<DefinedProperties>)>,
244 hierarchy: Res<TypeHierarchy<'static>>,
245) {
246 debug!("Complete properties");
247 for (token, triple, prefixes, links, this_label, types, mut request) in &mut query {
248 debug!("target {:?} text {}", triple.target, token.text);
249 debug!("links {:?}", links);
250 if triple.target == TripleTarget::Predicate {
251 let tts = types.get(&triple.triple.subject.value);
252 for (label, properties) in &other {
253 if links
255 .iter()
256 .find(|link| link.0.as_str().starts_with(label.0.as_str()))
257 .is_none()
258 && label.0 != this_label.0
259 {
260 debug!("This link is ignored {}", label.as_str());
261 continue;
262 }
263
264 for class in properties.0.iter() {
265 let to_beat = prefixes
266 .shorten(&class.predicate.value)
267 .map(|x| Cow::Owned(x))
268 .unwrap_or(class.predicate.value.clone());
269
270 debug!(
271 "{} starts with {} = {}",
272 to_beat,
273 token.text,
274 to_beat.starts_with(&token.text)
275 );
276
277 if to_beat.starts_with(&token.text) {
278 let correct_domain = class.domain.iter().any(|domain| {
279 if let Some(domain_id) = hierarchy.get_id_ref(&domain) {
280 if let Some(tts) = tts {
281 tts.iter().any(|tt| *tt == domain_id)
282 } else {
283 false
284 }
285 } else {
286 false
287 }
288 });
289
290 let mut completion = SimpleCompletion::new(
291 CompletionItemKind::PROPERTY,
292 format!("{}", to_beat),
293 TextEdit {
294 range: token.range.clone(),
295 new_text: to_beat.to_string(),
296 },
297 )
298 .label_description(&class.comment);
299
300 if correct_domain {
301 completion.kind = CompletionItemKind::FIELD;
302 debug!("Property has correct domain {}", to_beat);
303 request.push(completion.sort_text("1"));
304 } else {
305 request.push(completion);
306 }
307 }
308 }
309 }
310 }
311 }
312}
313
314#[instrument(skip(query, other))]
315pub fn hover_property(
316 mut query: Query<(
317 &TokenComponent,
318 &Prefixes,
319 &DocumentLinks,
320 &mut HoverRequest,
321 )>,
322 other: Query<(&Label, Option<&Prefixes>, &Wrapped<DefinedProperties>)>,
323) {
324 for (token, prefixes, links, mut request) in &mut query {
325 if let Some(target) = prefixes.expand(token.token.value()) {
326 for (label, p2, classes) in &other {
327 if links.iter().find(|link| link.0 == label.0).is_none() {
329 continue;
330 }
331
332 let shorten = |from: &str| {
333 if let Some(x) = prefixes.shorten(from) {
334 return Some(x);
335 }
336
337 if let Some(p) = p2 {
338 return p.shorten(from);
339 }
340
341 None
342 };
343
344 for c in classes.iter().filter(|c| c.predicate.value == target) {
345 request.0.push(format!("{}: {}", c.label, c.comment));
346 for r in &c.range {
347 let range = shorten(&r);
348 request.0.push(format!(
349 "Range {}",
350 range.as_ref().map(|x| x.as_str()).unwrap_or(r.as_str())
351 ));
352 }
353
354 for d in &c.domain {
355 let domain = shorten(&d);
356 request.0.push(format!(
357 "Domain {}",
358 domain.as_ref().map(|x| x.as_str()).unwrap_or(d.as_str())
359 ));
360 }
361 }
362 }
363 }
364 }
365}