Skip to main content

swls_core/systems/properties/
systems.rs

1use 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
221/// Hover system: when the cursor sits on an IRI that the user has explicitly
222/// allow-listed via `allowed_properties`, explain that the property is *not*
223/// part of the ontology and is only accepted because the user excluded it from
224/// validation — including how to undo that.
225pub 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}