lang_sparql/ecs/
mod.rs

1use std::collections::{HashMap, HashSet};
2
3use bevy_ecs::{prelude::*, world::World};
4use completion::{CompletionRequest, SimpleCompletion};
5use lang_turtle::lang::{
6    context::{Context, TokenIdx},
7    model::TriplesBuilder,
8};
9use lsp_core::{components::*, prelude::*, systems::prefix::prefix_completion_helper};
10use lsp_types::CompletionItemKind;
11use sophia_iri::resolve::BaseIri;
12
13use crate::{
14    lang::{parsing::parse, tokenizer::parse_tokens_str},
15    Sparql,
16};
17
18pub fn setup_parse(world: &mut World) {
19    use lsp_core::feature::parse::*;
20    world.schedule_scope(Label, |_, schedule| {
21        schedule.add_systems((
22            parse_source,
23            parse_sparql_system.after(parse_source),
24            derive_triples
25                .after(parse_sparql_system)
26                .before(prefixes)
27                .before(triples),
28        ));
29    });
30}
31
32pub fn setup_completion(world: &mut World) {
33    use lsp_core::feature::completion::*;
34    world.schedule_scope(Label, |_, schedule| {
35        schedule.add_systems((
36            sparql_lov_undefined_prefix_completion.after(get_current_token),
37            variable_completion.after(get_current_token),
38        ));
39    });
40}
41
42#[instrument(skip(query, commands))]
43fn parse_source(
44    query: Query<(Entity, &Source), (Changed<Source>, With<Sparql>)>,
45    mut commands: Commands,
46) {
47    for (entity, source) in &query {
48        let (tok, es) = parse_tokens_str(source.0.as_str());
49        info!("tokenized  {} tokens ({} errors)", tok.len(), es.len());
50        commands.entity(entity).insert((Tokens(tok), Errors(es)));
51    }
52}
53
54#[instrument(skip(query, commands))]
55fn parse_sparql_system(
56    query: Query<(Entity, &Source, &Tokens, &Label), (Changed<Tokens>, With<Sparql>)>,
57    mut commands: Commands,
58    mut old: Local<HashMap<String, (Vec<Spanned<Token>>, Context)>>,
59    config: Res<ServerConfig>,
60) {
61    if !config.config.sparql.unwrap_or(true) {
62        return;
63    }
64    for (entity, source, tokens, label) in &query {
65        let (ref mut old_tokens, ref mut context) = old.entry(label.to_string()).or_default();
66
67        context.setup_current_to_prev(
68            TokenIdx { tokens: &tokens },
69            tokens.len(),
70            TokenIdx {
71                tokens: &old_tokens,
72            },
73            old_tokens.len(),
74        );
75        let ctx = context.ctx();
76
77        let (jsonld, es) = parse(source.as_str(), label.0.clone(), tokens.0.clone(), ctx);
78
79        *old_tokens = tokens.0.clone();
80        context.clear();
81
82        jsonld.add_to_context(context);
83
84        // turtle.set_context(context);
85        info!("{} triples ({} errors)", label.0, es.len());
86
87        if es.is_empty() {
88            let element = Element::<Sparql>(jsonld);
89            commands
90                .entity(entity)
91                .insert((element, Errors(es)))
92                .remove::<Dirty>();
93        } else {
94            let element = Element::<Sparql>(jsonld);
95            commands.entity(entity).insert((Errors(es), element, Dirty));
96        }
97    }
98}
99
100#[instrument(skip(query, commands))]
101fn derive_triples(
102    query: Query<(Entity, &Label, &Element<Sparql>), Changed<Element<Sparql>>>,
103    mut commands: Commands,
104) {
105    for (e, l, el) in &query {
106        let query = el.0.value();
107
108        let prefixes: Vec<_> = query
109            .prefixes
110            .iter()
111            .flat_map(|prefix| {
112                let url = prefix.value.expand(query)?;
113                let url = lsp_types::Url::parse(&url).ok()?;
114                Some(Prefix {
115                    url,
116                    prefix: prefix.prefix.value().clone(),
117                })
118            })
119            .collect();
120
121        commands.entity(e).insert(Prefixes(prefixes, l.0.clone()));
122
123        if let Ok(base) = BaseIri::new(query.base.to_string()) {
124            let mut builder = TriplesBuilder::new(query, base);
125            let _ = query.ingest_triples(&mut builder);
126            let triples: Vec<_> = builder.triples.into_iter().map(|x| x.to_owned()).collect();
127
128            commands.entity(e).insert(Triples(triples));
129        }
130    }
131}
132
133#[instrument(skip(query,))]
134pub fn variable_completion(
135    mut query: Query<(&Tokens, &TokenComponent, &mut CompletionRequest), With<Sparql>>,
136) {
137    for (tokens, token, mut req) in &mut query {
138        if token.text.starts_with('?') {
139            let token_set: HashSet<&str> = tokens
140                .0
141                .iter()
142                .flat_map(|x| match x.value() {
143                    Token::Variable(x) => Some(x.as_str()),
144                    _ => None,
145                })
146                .collect();
147
148            for x in token_set {
149                let t = format!("?{}", x);
150                let completion = SimpleCompletion::new(
151                    CompletionItemKind::VARIABLE,
152                    t.clone(),
153                    lsp_types::TextEdit {
154                        range: token.range.clone(),
155                        new_text: t,
156                    },
157                );
158                req.push(completion);
159            }
160        }
161    }
162}
163
164pub fn sparql_lov_undefined_prefix_completion(
165    mut query: Query<(
166        &TokenComponent,
167        &Element<Sparql>,
168        &Prefixes,
169        &mut CompletionRequest,
170    )>,
171) {
172    for (word, turtle, prefixes, mut req) in &mut query {
173        let mut start = Position::new(0, 0);
174
175        if turtle.base_statement.is_some() {
176            start = Position::new(1, 0);
177        }
178
179        use lsp_types::{Position, Range};
180        prefix_completion_helper(word, prefixes, &mut req.0, |name, location| {
181            Some(vec![lsp_types::TextEdit {
182                range: Range::new(start.clone(), start),
183                new_text: format!("PREFIX {}: <{}>\n", name, location),
184            }])
185        });
186    }
187}