swls_core/systems/properties/
systems.rs1use std::{borrow::Cow, collections::HashSet};
2
3use bevy_ecs::prelude::*;
4use completion::{CompletionRequest, SimpleCompletion};
5use hover::HoverRequest;
6use sophia_api::term::{Term as _, TermKind};
7use tracing::{instrument, trace};
8
9use crate::{
10 lsp_types::{CompletionItemKind, TextEdit},
11 prelude::*,
12};
13
14#[instrument(skip(query, resource, config))]
15pub fn complete_class(
16 mut query: Query<(
17 &TokenComponent,
18 &TripleComponent,
19 &Prefixes,
20 &Types,
21 &mut CompletionRequest,
22 )>,
23 hierarchy: Res<TypeHierarchy>,
24 resource: Res<Ontologies>,
25 config: Res<ServerConfig>,
26) {
27 if config.config.local.is_disabled(Disabled::CompletionClass) {
28 return;
29 }
30 for (token, triple, prefixes, types, mut request) in &mut query {
31 if triple.triple.predicate.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
32 && triple.target == TripleTarget::Object
33 {
34 let tts = types.get(&triple.triple.subject.value);
35
36 let superclasses: HashSet<_> = tts
37 .iter()
38 .flat_map(|x| x.iter())
39 .flat_map(|t| hierarchy.iter_superclass(*t))
40 .collect();
41
42 for class in resource.classes.values() {
43 let to_beat = prefixes
44 .shorten(&class.term.value)
45 .map(|x| Cow::Owned(x))
46 .unwrap_or(class.term.value.clone());
47
48 if to_beat.starts_with(&token.text) {
49 let mut completion = SimpleCompletion::new(
50 CompletionItemKind::CLASS,
51 format!("{}", to_beat),
52 TextEdit {
53 range: token.range.clone(),
54 new_text: to_beat.to_string(),
55 },
56 )
57 .label_description(class.full_title())
58 .documentation(class.full_docs(&hierarchy, &prefixes));
59
60 if superclasses.contains(class.term.as_str()) {
61 completion.kind = CompletionItemKind::INTERFACE;
62 request.push(completion.sort_text(format!("0{}", to_beat)));
63 } else {
64 request.push(completion.sort_text(format!("1{}", to_beat)));
65 }
66 }
67 }
68 }
69 }
70}
71
72pub fn hover_class(
73 mut query: Query<(&TripleComponent, &Prefixes, &mut HoverRequest)>,
74 hierarchy: Res<TypeHierarchy>,
75 resource: Res<Ontologies>,
76 config: Res<ServerConfig>,
77) {
78 if config.config.local.is_disabled(Disabled::HoverClass) {
79 return;
80 }
81 for (triple, prefixes, mut request) in &mut query {
82 let Some(term) = triple.term() else { continue };
83 if term.kind() != TermKind::Iri {
84 continue;
85 }
86 let target = term.as_str();
87 for class in resource.classes.values() {
88 if class.term.value == target {
89 request.0.push(format!(
90 "## {}\n\n{}",
91 class.full_title(),
92 class.full_docs(&hierarchy, &prefixes)
93 ));
94 }
95 }
96 }
97}
98
99#[instrument(skip(query, hierarchy, resource, config))]
100pub fn complete_properties(
101 mut query: Query<(
102 &TokenComponent,
103 &TripleComponent,
104 &Prefixes,
105 &Label,
106 &Types,
107 &mut CompletionRequest,
108 &DynLang,
109 )>,
110 hierarchy: Res<TypeHierarchy>,
111 resource: Res<Ontologies>,
112 config: Res<ServerConfig>,
113) {
114 if config.config.local.is_disabled(Disabled::CompletionProperty) {
115 return;
116 }
117 for (token, triple, prefixes, _this_label, types, mut request, lang) in &mut query {
118 if triple.target == TripleTarget::Predicate {
119 let tts = types.get(&triple.triple.subject.value);
120 let bare_text = lang.unquote(&token.text);
121
122 if let Some(tts) = tts.as_ref() {
123 trace!("Types for {}", triple.triple.subject.value);
124 for t in *tts {
125 trace!(
126 "{} {}",
127 triple.triple.subject.value,
128 hierarchy.type_name(*t)
129 );
130 }
131 } else {
132 trace!("No types for {}", triple.triple.subject.value);
133 }
134
135 let subclasses: HashSet<_> = tts
136 .iter()
137 .flat_map(|x| x.iter())
138 .flat_map(|t| hierarchy.iter_subclass(*t))
139 .collect();
140
141 for property in resource.properties.values() {
142 let correct_domain = property
143 .domains
144 .iter()
145 .any(|domain| subclasses.contains(domain.as_str()));
146
147 if !subclasses.is_empty()
148 && config
149 .config
150 .local
151 .completion
152 .correct_domain_required(&property.term.value)
153 && !correct_domain
154 {
155 continue;
156 }
157
158 let to_beat = prefixes
159 .shorten(&property.term.value)
160 .map(|x| Cow::Owned(x))
161 .unwrap_or(property.term.value.clone());
162
163 if to_beat.starts_with(&bare_text) {
164 let mut completion = SimpleCompletion::new(
165 CompletionItemKind::ENUM_MEMBER,
166 to_beat.to_string(),
167 TextEdit {
168 range: token.range.clone(),
169 new_text: lang.quote(&to_beat),
170 },
171 )
172 .label_description(&property.full_title())
173 .documentation(&property.full_docs(&prefixes));
174
175 if correct_domain {
176 completion.kind = CompletionItemKind::FIELD;
177 request.push(completion.sort_text(format!("0{}", to_beat)));
178 } else {
179 request.push(completion.sort_text(format!("1{}", to_beat)));
180 }
181 }
182 }
183 }
184 }
185}
186
187#[instrument(skip(query, resource, config))]
188pub fn hover_property(
189 mut query: Query<(
190 &TripleComponent,
191 &Prefixes,
192 &DocumentLinks,
193 &mut HoverRequest,
194 )>,
195 resource: Res<Ontologies>,
196 config: Res<ServerConfig>,
197) {
198 if config.config.local.is_disabled(Disabled::HoverProperty) {
199 return;
200 }
201 for (triple, prefixes, _links, mut request) in &mut query {
202 let Some(term) = triple.term() else { continue };
203 if term.kind() != TermKind::Iri {
204 continue;
205 }
206 let target = term.as_str();
207 for c in resource
208 .properties
209 .values()
210 .filter(|c| c.term.value == target)
211 {
212 request.0.push(format!(
213 "## {}\n\n{}",
214 c.full_title(),
215 c.full_docs(&prefixes)
216 ));
217 }
218 }
219}
220
221pub fn hover_excluded_property(
226 mut query: Query<(&TripleComponent, &mut HoverRequest)>,
227 config: Res<ServerConfig>,
228) {
229 let allowed = &config.config.local.allowed_properties;
230 if allowed.is_empty() || config.config.local.is_disabled(Disabled::HoverExcludedProperty) {
231 return;
232 }
233 for (triple, mut request) in &mut query {
234 let Some(term) = triple.term() else { continue };
235 if term.kind() != TermKind::Iri {
236 continue;
237 }
238 let target = term.as_str();
239 if !allowed.contains(target) {
240 continue;
241 }
242 request.0.push(format!(
243 "⚠️ **`{}`** is not defined in its ontology.\n\n\
244 It is accepted because you added it to the `allowed_properties` list. \
245 To restore the \"unknown property\" warning, remove `\"{}\"` from the \
246 `allowed_properties` array in your SWLS config (`config.json`).",
247 target, target
248 ));
249 }
250}