1use std::ops::Range as StdRange;
18
19use bevy_ecs::prelude::*;
20use rdf_parsers::model::{BlankNode, NamedNode, Term, Turtle, PO};
21use swls_core::{
22 feature::rename::{PrepareRenameRequest, RenameEdits},
23 lsp_types::TextEdit,
24 prelude::*,
25 util::{offsets_to_range, position_to_offset},
26};
27
28use crate::traits::NamedNodeExt;
29
30#[derive(Clone, PartialEq, Eq)]
33enum RenameKey {
34 Iri(String),
36 Blank(String),
38 Var(String),
40}
41
42fn term_key(term: &Term, turtle: &Turtle) -> Option<RenameKey> {
45 match term {
46 Term::NamedNode(nn) => match nn {
47 NamedNode::A(_) | NamedNode::Invalid => None,
48 _ => nn.expand(turtle).map(RenameKey::Iri),
49 },
50 Term::BlankNode(BlankNode::Named(label, _)) => Some(RenameKey::Blank(label.clone())),
51 Term::Variable(v) => Some(RenameKey::Var(v.0.clone())),
52 _ => None,
53 }
54}
55
56fn term_placeholder(term: &Term) -> Option<String> {
58 match term {
59 Term::NamedNode(NamedNode::Full(iri, _)) => Some(iri.clone()),
60 Term::NamedNode(NamedNode::Prefixed { prefix, value, .. }) => {
61 Some(format!("{}:{}", prefix, value))
62 }
63 Term::BlankNode(BlankNode::Named(label, _)) => Some(format!("_:{}", label)),
64 Term::Variable(v) => Some(format!("?{}", v.0)),
65 _ => None,
66 }
67}
68
69fn walk_terms(turtle: &Turtle, mut f: impl FnMut(&Spanned<Term>)) {
72 fn walk(term: &Spanned<Term>, f: &mut impl FnMut(&Spanned<Term>)) {
73 f(term);
74 match term.value() {
75 Term::Collection(items) => {
76 for item in items {
77 walk(item, f);
78 }
79 }
80 Term::BlankNode(BlankNode::Unnamed(pos, _, _)) => {
81 for po in pos {
82 walk_po(po, f);
83 }
84 }
85 _ => {}
86 }
87 }
88 fn walk_po(po: &Spanned<PO>, f: &mut impl FnMut(&Spanned<Term>)) {
89 walk(&po.value().predicate, f);
90 for object in &po.value().object {
91 walk(object, f);
92 }
93 }
94 for triple in &turtle.triples {
95 let triple = triple.value();
96 walk(&triple.subject, &mut f);
97 for po in &triple.po {
98 walk_po(po, &mut f);
99 }
100 }
101}
102
103fn find_renameable_at(
116 turtle: &Turtle,
117 offset: usize,
118) -> Option<(StdRange<usize>, RenameKey, String)> {
119 find_renameable_with(turtle, offset, false)
120 .or_else(|| find_renameable_with(turtle, offset + 1, false))
121 .or_else(|| find_renameable_with(turtle, offset, true))
122}
123
124fn find_renameable_with(
128 turtle: &Turtle,
129 offset: usize,
130 inclusive: bool,
131) -> Option<(StdRange<usize>, RenameKey, String)> {
132 let mut best: Option<(StdRange<usize>, RenameKey, String)> = None;
133 walk_terms(turtle, |term| {
134 let span = term.span();
135 let contains = span.start <= offset && (offset < span.end || (inclusive && offset == span.end));
136 if !contains {
137 return;
138 }
139 let value = term.value();
140 let (Some(key), Some(placeholder)) = (term_key(value, turtle), term_placeholder(value))
141 else {
142 return;
143 };
144 let width = span.end - span.start;
145 let is_better = best
146 .as_ref()
147 .map(|(bspan, _, _)| width < bspan.end - bspan.start)
148 .unwrap_or(true);
149 if is_better {
150 best = Some((span.clone(), key, placeholder));
151 }
152 });
153 best
154}
155
156pub fn prepare_rename(
159 query: Query<(Entity, &Element, &RopeC, &PositionComponent, &DynLang)>,
160 mut commands: Commands,
161) {
162 for (entity, element, rope, position, lang) in &query {
163 if !lang.0.model_based_rename() {
166 continue;
167 }
168 commands.entity(entity).remove::<PrepareRenameRequest>();
169
170 let Some(offset) = position_to_offset(position.0, &rope.0) else {
171 continue;
172 };
173 let Some((span, _key, placeholder)) = find_renameable_at(element.value(), offset) else {
174 continue;
175 };
176 let Some(range) = offsets_to_range(span.start, span.end, &rope.0) else {
177 continue;
178 };
179 commands
180 .entity(entity)
181 .insert(PrepareRenameRequest { range, placeholder });
182 }
183}
184
185pub fn rename(
188 mut query: Query<(
189 &Element,
190 &RopeC,
191 &Label,
192 &PositionComponent,
193 &DynLang,
194 &mut RenameEdits,
195 )>,
196) {
197 for (element, rope, label, position, lang, mut edits) in &mut query {
198 if !lang.0.model_based_rename() {
199 continue;
200 }
201 let Some(offset) = position_to_offset(position.0, &rope.0) else {
202 continue;
203 };
204 let turtle = element.value();
205 let Some((_span, key, _placeholder)) = find_renameable_at(turtle, offset) else {
206 continue;
207 };
208 let new_text = lang.0.rename_wrap(&edits.1);
209
210 let mut collected: Vec<TextEdit> = Vec::new();
211 walk_terms(turtle, |term| {
212 if term_key(term.value(), turtle).as_ref() == Some(&key) {
213 if let Some(range) = offsets_to_range(term.span().start, term.span().end, &rope.0) {
214 collected.push(TextEdit {
215 range,
216 new_text: new_text.clone(),
217 });
218 }
219 }
220 });
221 for edit in collected {
222 edits.0.push((label.0.clone(), edit));
223 }
224 }
225}
226
227