yaml_edit/nodes/mapping/
movements.rs1use super::Mapping;
5use crate::lex::SyntaxKind;
6use crate::nodes::trivia::Slot;
7use crate::nodes::trivia::{self, ends_with_line_break, FlowSeparator};
8use crate::nodes::{fresh_token, Lang, SyntaxNode};
9use rowan::ast::AstNode;
10
11type Element = rowan::SyntaxElement<Lang>;
12
13struct Group {
15 lines_before: Vec<Element>,
18 entry: SyntaxNode,
19}
20
21struct SlotLayout {
24 flow_separators: Vec<Option<FlowSeparator>>,
27 last_has_comma: bool,
29 last_terminated: bool,
31}
32
33impl Mapping {
34 pub(super) fn arrange_entries(&self, order: &[SyntaxNode]) {
40 let all: Vec<Element> = self.0.children_with_tokens().collect();
41 let starts: Vec<usize> = all
42 .iter()
43 .enumerate()
44 .filter(|(_, c)| c.kind() == SyntaxKind::MAPPING_ENTRY)
45 .map(|(i, _)| i)
46 .collect();
47 assert_eq!(
48 starts.len(),
49 order.len(),
50 "the order names every entry once"
51 );
52 let Some(&first) = starts.first() else {
53 return;
54 };
55 let layout = self.slot_layout();
56 let flow = self.is_flow_style();
57 let mut prefix = all[..first].to_vec();
60 let first_indent = prefix
61 .last()
62 .filter(|c| flow && c.kind() == SyntaxKind::INDENT)
63 .cloned();
64 if first_indent.is_some() {
65 prefix.pop();
66 }
67
68 let mut groups: Vec<Group> = Vec::new();
71 let mut gaps: Vec<Vec<Element>> = Vec::new();
72 let mut line_indents = Vec::new();
73 let mut trailing = Vec::new();
74 let mut lines_before = Vec::new();
75 for (i, &at) in starts.iter().enumerate() {
76 let entry = all[at].as_node().expect("an entry is a node");
77 groups.push(Group {
78 lines_before: std::mem::take(&mut lines_before),
79 entry: entry.clone(),
80 });
81 let Some(&next) = starts.get(i + 1) else {
82 trailing = all[at + 1..].to_vec();
83 let gap_end = leading_blank_lines_end(&trailing);
84 gaps.push(trailing.drain(..gap_end).collect());
85 break;
86 };
87 let mut between = all[at + 1..next].to_vec();
88 let indent = between
89 .last()
90 .is_some_and(|c| c.kind() == SyntaxKind::INDENT)
91 .then(|| between.pop())
92 .flatten();
93 line_indents.push(indent);
94 lines_before = between.split_off(leading_blank_lines_end(&between));
95 gaps.push(between);
96 }
97
98 let ordered: Vec<Group> = order
99 .iter()
100 .map(|wanted| {
101 let pos = groups
102 .iter()
103 .position(|group| group.entry == *wanted)
104 .expect("the order names entries of this mapping");
105 groups.remove(pos)
106 })
107 .collect();
108
109 let ends_in_block_scalar: Vec<bool> = ordered
113 .iter()
114 .map(|group| crate::nodes::ending_block_scalar(&group.entry).is_some())
115 .collect();
116 for i in 0..gaps.len() {
117 if !ends_in_block_scalar[i] || gaps[i].is_empty() {
118 continue;
119 }
120 let home = (i + 1..gaps.len())
121 .chain((0..i).rev())
122 .find(|&j| !ends_in_block_scalar[j])
123 .expect("blank lines follow an entry that does not end in a block scalar");
124 let moved = std::mem::take(&mut gaps[i]);
125 gaps[home].extend(moved);
126 }
127
128 let lines_lead = !ordered[0].lines_before.is_empty();
131 let opening_line_open = flow
134 && prefix
135 .last()
136 .is_some_and(|c| c.kind() != SyntaxKind::NEWLINE);
137 let mut new_children = prefix;
138 let last_gap = gaps.pop().expect("there is an entry");
139 for (i, mut group) in ordered.into_iter().enumerate() {
140 let indent = match i.checked_sub(1) {
141 Some(i) => {
142 new_children.append(&mut gaps[i]);
143 line_indents[i].clone()
144 }
145 None if first_indent.is_some() => first_indent.clone(),
146 None if lines_lead => line_indents[0]
149 .as_ref()
150 .and_then(|c| c.as_token())
151 .map(|t| fresh_token(t.kind(), t.text()).into()),
152 None => None,
153 };
154 if i == 0 && opening_line_open {
155 if let Some(first) = group
156 .lines_before
157 .first_mut()
158 .filter(|c| c.kind() == SyntaxKind::INDENT)
159 {
160 *first = fresh_token(SyntaxKind::WHITESPACE, &first.to_string()).into();
161 }
162 }
163 new_children.extend(group.lines_before);
164 new_children.extend(indent);
165 new_children.push(group.entry.into());
166 }
167 new_children.extend(last_gap);
168 new_children.extend(trailing);
169
170 crate::nodes::kept_aliases::keeping_aliases_moved(&self.0, || {
171 crate::nodes::replace_children(&self.0, 0..all.len(), new_children);
172 if lines_lead && !flow {
173 crate::yaml::move_leading_lines_out(&self.0);
174 }
175 self.restore_slot_layout(&layout);
176 for entry in order {
177 crate::nodes::replaced_value::settle_lines_after_block_scalar(entry);
178 }
179 });
180 }
181
182 fn slot_layout(&self) -> SlotLayout {
185 let entries: Vec<SyntaxNode> = self.entries().map(|e| e.0).collect();
186 let flow_separators = if self.is_flow_style() {
187 entries.iter().map(FlowSeparator::of).collect()
188 } else {
189 Vec::new()
190 };
191 let last = entries.last();
192 SlotLayout {
193 flow_separators,
194 last_has_comma: last.is_some_and(trivia::has_comma),
195 last_terminated: last.map_or(true, ends_with_line_break),
196 }
197 }
198
199 fn restore_slot_layout(&self, layout: &SlotLayout) {
202 let entries: Vec<SyntaxNode> = self.entries().map(|e| e.0).collect();
203 let Some((last, rest)) = entries.split_last() else {
204 return;
205 };
206 if !self.is_flow_style() {
207 for entry in rest {
208 if !ends_with_line_break(entry) {
209 trivia::terminate(entry);
210 }
211 }
212 if !layout.last_terminated {
213 trivia::unterminate_last(&self.0);
214 }
215 return;
216 }
217 assert_eq!(
218 entries.len(),
219 layout.flow_separators.len(),
220 "moving entries keeps their number"
221 );
222 for (entry, slot) in entries.iter().zip(&layout.flow_separators) {
223 let is_last = entry == last;
224 match slot {
225 Some(slot) if FlowSeparator::swap(entry, slot) => {}
226 _ if is_last && !layout.last_has_comma => FlowSeparator::strip(entry),
227 _ => FlowSeparator::insert_comma(entry),
228 }
229 }
230 }
231
232 pub(super) fn move_around(
236 &self,
237 ref_key: &impl crate::AsYaml,
238 new_key: &impl crate::AsYaml,
239 new_value: &impl crate::AsYaml,
240 after: bool,
241 ) -> bool {
242 let Some(target) = self.find_entry_by_key(ref_key) else {
243 return false;
244 };
245 if let Some(existing) = self.find_entry_by_key(new_key) {
246 let value = existing.value().children().next();
251 let own_value = value.is_some() && value == new_value.value_node();
252 if existing.syntax() == target.syntax() {
253 if !own_value {
254 self.set(new_key, new_value);
255 }
256 return true;
257 }
258 let old = if own_value {
259 existing.0
260 } else {
261 self.replace_entry(&existing.0, new_key, new_value, false)
262 };
263 let mut order: Vec<SyntaxNode> =
264 self.entries().map(|e| e.0).filter(|e| *e != old).collect();
265 let at = order
266 .iter()
267 .position(|e| e == target.syntax())
268 .expect("the target is in its mapping");
269 order.insert(at + usize::from(after), old);
270 self.arrange_entries(&order);
271 return true;
272 }
273 let target = target.syntax().clone();
274 let slot = if after {
275 Slot::After(target)
276 } else {
277 Slot::Before(target)
278 };
279 self.insert_new(slot, new_key, new_value);
280 true
281 }
282
283 pub fn move_after(
299 &self,
300 after_key: impl crate::AsYaml,
301 new_key: impl crate::AsYaml,
302 new_value: impl crate::AsYaml,
303 ) -> bool {
304 self.move_around(&after_key, &new_key, &new_value, true)
305 }
306
307 pub fn move_before(
318 &self,
319 before_key: impl crate::AsYaml,
320 new_key: impl crate::AsYaml,
321 new_value: impl crate::AsYaml,
322 ) -> bool {
323 self.move_around(&before_key, &new_key, &new_value, false)
324 }
325}
326
327fn is_blank(element: &Element) -> bool {
328 matches!(
329 element.kind(),
330 SyntaxKind::NEWLINE | SyntaxKind::WHITESPACE | SyntaxKind::INDENT
331 )
332}
333
334fn leading_blank_lines_end(elements: &[Element]) -> usize {
337 let first = elements
338 .iter()
339 .position(|e| !is_blank(e))
340 .unwrap_or(elements.len());
341 elements[..first]
342 .iter()
343 .rposition(|e| e.kind() == SyntaxKind::NEWLINE)
344 .map_or(0, |i| i + 1)
345}