1use alloc::{
2 borrow::Cow,
3 string::{String, ToString},
4 vec,
5 vec::Vec,
6};
7use core::{error::Error, fmt, ops::Range};
8
9use crate::{encode::encode_native, AssignmentState, Dialect, Document, ItemKind, Quote, Severity};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct TextEdit {
14 pub range: Range<usize>,
16 pub replacement: String,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct VerifiedEdits {
23 edits: Vec<TextEdit>,
24 output: String,
25}
26impl VerifiedEdits {
27 #[must_use]
29 pub fn edits(&self) -> &[TextEdit] {
30 &self.edits
31 }
32 #[must_use]
34 pub fn into_edits(self) -> Vec<TextEdit> {
35 self.edits
36 }
37 #[must_use]
39 pub fn into_string(self) -> String {
40 self.output
41 }
42 #[must_use]
44 pub fn as_str(&self) -> &str {
45 &self.output
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum InsertPosition<'a> {
53 End,
55 AfterKey(&'a str),
57 AfterEntry(usize),
59 Sorted,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum Edit<'a> {
67 Set {
69 entry: usize,
71 value: &'a str,
73 },
74 Remove {
76 item: usize,
78 },
79 Rename {
81 entry: usize,
83 key: &'a str,
85 },
86 Insert {
88 key: &'a str,
90 value: Option<&'a str>,
92 position: InsertPosition<'a>,
94 },
95 Comment {
97 item: usize,
99 },
100 Uncomment {
102 item: usize,
104 },
105 Export {
107 entry: usize,
109 enabled: bool,
111 },
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[non_exhaustive]
117pub enum EditError {
118 Missing,
120 Duplicate(usize),
122 InvalidTarget,
124 InvalidKey,
126 Unencodable,
128 ConflictingEdits,
130 WouldChangeMeaning,
132}
133impl fmt::Display for EditError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 Self::Missing => f.write_str("no declaration has the requested key"),
137 Self::Duplicate(count) => {
138 write!(f, "key has {count} declarations; select an occurrence")
139 }
140 Self::InvalidTarget => f.write_str("invalid snapshot-local edit target"),
141 Self::InvalidKey => f.write_str("key cannot be represented by the selected dialect"),
142 Self::Unencodable => f.write_str("value cannot be represented in the selected dialect"),
143 Self::ConflictingEdits => f.write_str("conflicting operations in edit batch"),
144 Self::WouldChangeMeaning => {
145 f.write_str("edit would change unrelated source interpretation")
146 }
147 }
148 }
149}
150impl Error for EditError {}
151
152fn key_token(key: &str, dialect: Dialect) -> Option<Cow<'_, str>> {
153 if crate::parser::valid_key_in(key, dialect) && !(dialect.quoted_keys && key.starts_with('\''))
154 {
155 Some(Cow::Borrowed(key))
156 } else if dialect.quoted_keys && !key.is_empty() && !key.contains('\'') {
157 let mut token = String::with_capacity(key.len() + 2);
158 token.push('\'');
159 token.push_str(key);
160 token.push('\'');
161 Some(Cow::Owned(token))
162 } else {
163 None
164 }
165}
166
167struct KeyLookup<'doc> {
171 document: &'doc Document<'doc>,
172 entries: Vec<crate::Entry<'doc>>,
173 minimum_starts_rev: Vec<usize>,
174}
175impl<'doc> KeyLookup<'doc> {
176 fn new(document: &'doc Document<'doc>, operations: &[Edit<'_>]) -> Self {
177 let lookups = operations
178 .iter()
179 .copied()
180 .filter(|operation| match *operation {
181 Edit::Rename { entry, key } => document
182 .entry(entry)
183 .is_some_and(|entry| entry.key() != key),
184 Edit::Insert {
185 position: InsertPosition::Sorted | InsertPosition::AfterKey(_),
186 ..
187 } => true,
188 _ => false,
189 })
190 .count();
191 let mut entries: Vec<_> = if lookups > 1 {
192 document.entries().collect()
193 } else {
194 Vec::new()
195 };
196 entries.sort_unstable_by(|a, b| a.key().cmp(b.key()));
197 let sorted_inserts = operations.iter().any(|operation| {
198 matches!(
199 *operation,
200 Edit::Insert {
201 position: InsertPosition::Sorted,
202 ..
203 }
204 )
205 });
206 let mut minimum_starts_rev = Vec::new();
207 if sorted_inserts && !entries.is_empty() {
208 minimum_starts_rev.reserve(entries.len());
209 let mut minimum = document.source().len();
210 for entry in entries.iter().rev() {
211 minimum = minimum.min(entry.line_span().start);
212 minimum_starts_rev.push(minimum);
213 }
214 }
215 Self {
216 document,
217 entries,
218 minimum_starts_rev,
219 }
220 }
221
222 fn bounds(&self, key: &str) -> Range<usize> {
223 self.entries.partition_point(|entry| entry.key() < key)
224 ..self.entries.partition_point(|entry| entry.key() <= key)
225 }
226
227 fn count(&self, key: &str) -> usize {
228 if self.entries.is_empty() {
229 self.document.get_all(key).count()
230 } else {
231 self.bounds(key).len()
232 }
233 }
234
235 fn unique(&self, key: &str) -> Result<crate::Entry<'doc>, EditError> {
236 if self.entries.is_empty() {
237 return self.document.unique(key);
238 }
239 let matches = self.bounds(key);
240 match matches.len() {
241 0 => Err(EditError::Missing),
242 1 => Ok(self.entries[matches.start]),
243 count => Err(EditError::Duplicate(count)),
244 }
245 }
246
247 fn first_greater(&self, key: &str) -> usize {
248 if self.entries.is_empty() {
249 return self
250 .document
251 .entries()
252 .find(|entry| entry.key() > key)
253 .map_or(self.document.source().len(), |entry| {
254 entry.line_span().start
255 });
256 }
257 let start = self.entries.partition_point(|entry| entry.key() <= key);
258 if start == self.entries.len() {
259 self.document.source().len()
260 } else {
261 self.minimum_starts_rev[self.entries.len() - start - 1]
262 }
263 }
264}
265
266#[derive(Clone, Copy)]
267struct Desired<'a> {
268 key: &'a str,
269 value: Option<&'a str>,
270 quote: Quote,
271 exported: bool,
272}
273impl<'a> Desired<'a> {
274 fn from_entry(entry: crate::Entry<'a>) -> Self {
275 Self {
276 key: entry.key(),
277 value: entry.value(),
278 quote: entry.quote(),
279 exported: entry.exported(),
280 }
281 }
282 fn matches(self, entry: crate::Entry<'_>) -> bool {
283 self.key == entry.key()
284 && self.value == entry.value()
285 && self.quote == entry.quote()
286 && self.exported == entry.exported()
287 && entry.state()
288 == match self.value {
289 None => AssignmentState::Bare,
290 Some("") => AssignmentState::Empty,
291 Some(_) => AssignmentState::Value,
292 }
293 }
294}
295#[derive(Clone, Copy, PartialEq, Eq)]
296enum Whole {
297 Keep,
298 Remove,
299 Comment,
300 Uncomment,
301}
302struct ItemPlan<'a> {
303 whole: Whole,
304 fields: u8,
305 desired: Option<Desired<'a>>,
306}
307impl ItemPlan<'_> {
308 fn retains(&self, item: crate::Item<'_>) -> bool {
309 match self.whole {
310 Whole::Remove => false,
311 Whole::Uncomment => {
312 item.raw().len()
313 > 1 + usize::from(
314 item.comment_text()
315 .is_some_and(|body| body.starts_with(' ')),
316 )
317 }
318 _ => true,
319 }
320 }
321}
322struct SetTarget<'a> {
323 entry: usize,
324 value: &'a str,
325 range: Range<usize>,
326 context: Range<usize>,
327}
328
329#[derive(Clone, Copy)]
330struct ContextEdits<'a, 'value> {
331 base: &'a [TextEdit],
332 targets: &'a [SetTarget<'value>],
333 sets: &'a [Option<TextEdit>],
334}
335
336fn edit_key(edit: &TextEdit) -> (usize, usize) {
337 (edit.range.start, edit.range.end)
338}
339
340struct Insertion<'a> {
341 offset: usize,
342 desired: Desired<'a>,
343 key_token: Cow<'a, str>,
344 leading: String,
345 sorted: bool,
346}
347impl<'a> Insertion<'a> {
348 fn append_to(
349 &mut self,
350 text: &mut String,
351 dialect: Dialect,
352 ending: &str,
353 ) -> Result<(Range<usize>, Desired<'a>), EditError> {
354 let start = text.len();
355 text.push_str(&self.key_token);
356 if let Some(value) = self.desired.value {
357 let (token, quote) = encode_native(
358 value,
359 Quote::None,
360 dialect,
361 &self.key_token,
362 ending,
363 true,
364 false,
365 )
366 .map_err(|_| EditError::Unencodable)?;
367 text.push('=');
368 text.push_str(&token);
369 self.desired.quote = quote;
370 }
371 text.push_str(ending);
372 Ok((start..text.len(), self.desired))
373 }
374}
375enum Expect<'a> {
376 Original(usize),
377 Entry(Desired<'a>),
378 Comments,
379 Uncommented,
380}
381struct Region<'a> {
382 range: Range<usize>,
383 expect: Expect<'a>,
384}
385fn verify_entry_region<'a>(
386 first: crate::Item<'a>,
387 desired: Desired<'_>,
388 range: &Range<usize>,
389 actual: &mut impl Iterator<Item = crate::Item<'a>>,
390) -> Result<(), EditError> {
391 if first.span().end > range.end
392 || first
393 .diagnostic()
394 .is_some_and(|d| d.severity() == Severity::Error)
395 || !first.entry().is_some_and(|entry| desired.matches(entry))
396 {
397 return Err(EditError::WouldChangeMeaning);
398 }
399 let mut end = first.span().end;
402 while end < range.end {
403 let next = actual.next().ok_or(EditError::WouldChangeMeaning)?;
404 if next.span().start != end
405 || next.span().end > range.end
406 || !matches!(next.kind(), ItemKind::Blank | ItemKind::Comment)
407 || next
408 .diagnostic()
409 .is_some_and(|d| d.severity() == Severity::Error)
410 {
411 return Err(EditError::WouldChangeMeaning);
412 }
413 end = next.span().end;
414 }
415 Ok(())
416}
417struct InsertGroup<'a> {
418 offset: usize,
419 len: usize,
420 entries: Vec<(Range<usize>, Desired<'a>)>,
421}
422fn group_at<'a, 'b>(groups: &'a [InsertGroup<'b>], offset: usize) -> Option<&'a InsertGroup<'b>> {
423 groups
424 .binary_search_by_key(&offset, |group| group.offset)
425 .ok()
426 .map(|index| &groups[index])
427}
428
429fn claim(plan: &mut ItemPlan<'_>, field: u8) -> Result<(), EditError> {
430 if plan.whole != Whole::Keep || plan.fields & field != 0 {
431 return Err(EditError::ConflictingEdits);
432 }
433 plan.fields |= field;
434 Ok(())
435}
436fn whole(plan: &mut ItemPlan<'_>, action: Whole) -> Result<(), EditError> {
437 if plan.whole != Whole::Keep || plan.fields != 0 {
438 return Err(EditError::ConflictingEdits);
439 }
440 plan.whole = action;
441 Ok(())
442}
443fn comment_text(text: &str, breaks: crate::LineBreaks) -> String {
444 let mut out = String::with_capacity(text.len() + 2);
445 let mut start = 0;
446 for (index, byte) in text.bytes().enumerate() {
447 if byte == b'\n'
448 || (breaks == crate::LineBreaks::Universal
449 && byte == b'\r'
450 && text.as_bytes().get(index + 1) != Some(&b'\n'))
451 {
452 out.push_str("# ");
453 out.push_str(&text[start..=index]);
454 start = index + 1;
455 }
456 }
457 if start < text.len() {
458 out.push_str("# ");
459 out.push_str(&text[start..]);
460 }
461 out
462}
463
464struct Boundaries<'a> {
467 edits: &'a [TextEdit],
468 removed: Vec<usize>,
469 added: Vec<usize>,
470}
471impl<'a> Boundaries<'a> {
472 fn new(edits: &'a [TextEdit]) -> Self {
473 let mut removed = vec![0];
474 let mut added = vec![0];
475 for edit in edits {
476 removed.push(removed.last().copied().unwrap_or(0) + edit.range.len());
477 added.push(added.last().copied().unwrap_or(0) + edit.replacement.len());
478 }
479 Self {
480 edits,
481 removed,
482 added,
483 }
484 }
485 fn map(&self, offset: usize) -> usize {
486 let count = self.edits.partition_point(|edit| {
487 edit.range.end < offset || (edit.range.end == offset && !edit.range.is_empty())
488 });
489 offset - self.removed[count] + self.added[count]
490 }
491}
492
493impl Document<'_> {
494 pub fn edit(&self, operations: &[Edit<'_>]) -> Result<VerifiedEdits, EditError> {
499 let keys = KeyLookup::new(self, operations);
500 let mut plans: Vec<_> = self
501 .items()
502 .map(|item| ItemPlan {
503 whole: Whole::Keep,
504 fields: 0,
505 desired: item.entry().map(Desired::from_entry),
506 })
507 .collect();
508 let mut edits = Vec::new();
509 let mut insertions = Vec::new();
510 let mut end_insertions = vec![0; self.items.len()];
511 for operation in operations {
512 self.plan_operation(*operation, &mut plans, &mut edits, &mut insertions, &keys)?;
513 }
514 self.validate_renames(&plans)?;
515 insertions.sort_by_key(|insertion| insertion.offset);
516 self.finish_sets(
517 operations,
518 &mut plans,
519 &mut edits,
520 &insertions,
521 &mut end_insertions,
522 )?;
523 let (groups, ending) =
524 self.plan_insertions(insertions, &plans, &mut edits, &mut end_insertions)?;
525 let edits = Self::normalize_edits(edits)?;
526 let output = Self::apply_edits(self.source(), &edits);
527 let regions = self.expected_regions(&plans, &end_insertions, &edits, groups)?;
528 self.verify_regions(&output, regions, ending)?;
529 Ok(VerifiedEdits { edits, output })
530 }
531
532 fn plan_operation<'a>(
533 &'a self,
534 operation: Edit<'a>,
535 plans: &mut [ItemPlan<'a>],
536 edits: &mut Vec<TextEdit>,
537 insertions: &mut Vec<Insertion<'a>>,
538 keys: &KeyLookup<'a>,
539 ) -> Result<(), EditError> {
540 match operation {
541 Edit::Set { entry, value } => {
542 self.plan_set(entry, value, plans)?;
543 }
544 Edit::Rename { entry, key } => self.plan_rename(entry, key, plans, edits, keys)?,
545 Edit::Export { entry, enabled } => self.plan_export(entry, enabled, plans, edits)?,
546 Edit::Remove { item } => {
547 let source_item = self.item(item).ok_or(EditError::InvalidTarget)?;
548 whole(&mut plans[item], Whole::Remove)?;
549 edits.push(TextEdit {
550 range: source_item.span(),
551 replacement: String::new(),
552 });
553 }
554 Edit::Comment { item } => {
555 let source_item = self.item(item).ok_or(EditError::InvalidTarget)?;
556 whole(&mut plans[item], Whole::Comment)?;
557 edits.push(TextEdit {
558 range: source_item.span(),
559 replacement: comment_text(source_item.raw(), self.dialect().line_breaks),
560 });
561 }
562 Edit::Uncomment { item } => {
563 let source_item = self.item(item).ok_or(EditError::InvalidTarget)?;
564 if source_item.kind() != ItemKind::Comment {
565 return Err(EditError::InvalidTarget);
566 }
567 whole(&mut plans[item], Whole::Uncomment)?;
568 let span = source_item.span();
569 let hash = source_item
570 .raw()
571 .find('#')
572 .ok_or(EditError::InvalidTarget)?
573 + span.start;
574 let end =
575 hash + 1 + usize::from(self.source().as_bytes().get(hash + 1) == Some(&b' '));
576 edits.push(TextEdit {
577 range: hash..end,
578 replacement: String::new(),
579 });
580 }
581 Edit::Insert {
582 key,
583 value,
584 position,
585 } => self.plan_insertion(key, value, position, insertions, keys)?,
586 }
587 Ok(())
588 }
589
590 fn plan_set<'a>(
591 &'a self,
592 id: usize,
593 value: &'a str,
594 plans: &mut [ItemPlan<'a>],
595 ) -> Result<(), EditError> {
596 let entry = self.entry(id).ok_or(EditError::InvalidTarget)?;
597 let plan = &mut plans[entry.item_id()];
598 claim(plan, 1)?;
599 if let Some(desired) = &mut plan.desired {
600 desired.value = Some(value);
601 }
602 Ok(())
603 }
604
605 fn finish_sets<'a>(
606 &'a self,
607 operations: &[Edit<'a>],
608 plans: &mut [ItemPlan<'a>],
609 edits: &mut Vec<TextEdit>,
610 insertions: &[Insertion<'_>],
611 end_insertions: &mut [usize],
612 ) -> Result<(), EditError> {
613 edits.sort_by_key(edit_key);
617 let mut targets = Vec::new();
618 for operation in operations {
619 let Edit::Set { entry: id, value } = *operation else {
620 continue;
621 };
622 let entry = self.entry(id).ok_or(EditError::InvalidTarget)?;
623 let span = entry.line_span();
624 let range = entry
625 .value_span()
626 .unwrap_or_else(|| entry.tokens().key.end..entry.tokens().key.end);
627 let context = self.value_context(&span, &range);
628 targets.push(SetTarget {
629 entry: id,
630 value,
631 range,
632 context,
633 });
634 }
635 targets.sort_by_key(|target| (target.range.start, target.range.end));
636 let mut set_edits: Vec<Option<TextEdit>> = core::iter::repeat_with(|| None)
637 .take(targets.len())
638 .collect();
639 let mut ending_items = vec![None; targets.len()];
640 let mut group_start = 0;
641 while group_start < targets.len() {
642 let mut group_end = group_start + 1;
643 while group_end < targets.len()
644 && targets[group_end].context == targets[group_start].context
645 {
646 group_end += 1;
647 }
648 let mut remaining = group_end - group_start;
649 while remaining != 0 {
650 let mut progressed = false;
651 for index in group_start..group_end {
652 if set_edits[index].is_some() {
653 continue;
654 }
655 let context_edits = ContextEdits {
656 base: edits,
657 targets: &targets,
658 sets: &set_edits,
659 };
660 match self.finish_set(&targets[index], plans, context_edits, insertions) {
661 Ok((edit, item, ending_field)) => {
662 set_edits[index] = Some(edit);
663 ending_items[index] = ending_field.then_some(item);
664 remaining -= 1;
665 progressed = true;
666 }
667 Err(EditError::Unencodable) => {}
668 Err(error) => return Err(error),
669 }
670 }
671 if !progressed {
672 return Err(EditError::Unencodable);
673 }
674 }
675 group_start = group_end;
676 }
677 Self::merge_set_edits(edits, set_edits, ending_items, end_insertions)
678 }
679
680 fn merge_set_edits(
681 edits: &mut Vec<TextEdit>,
682 set_edits: Vec<Option<TextEdit>>,
683 ending_items: Vec<Option<usize>>,
684 end_insertions: &mut [usize],
685 ) -> Result<(), EditError> {
686 if set_edits.iter().any(Option::is_none) {
687 return Err(EditError::Unencodable);
688 }
689 let base = core::mem::take(edits);
690 edits.reserve(base.len() + set_edits.len());
691 let mut base = base.into_iter().peekable();
692 let mut sets = set_edits
693 .into_iter()
694 .zip(ending_items)
695 .filter_map(|(edit, item)| edit.map(|edit| (edit, item)))
696 .peekable();
697 while base.peek().is_some() || sets.peek().is_some() {
698 let take_base = match (base.peek(), sets.peek()) {
699 (Some(left), Some((right, _))) => edit_key(left) <= edit_key(right),
700 (Some(_), None) => true,
701 (None, Some(_)) => false,
702 (None, None) => break,
703 };
704 if take_base {
705 if let Some(edit) = base.next() {
706 edits.push(edit);
707 }
708 } else if let Some((edit, ending_item)) = sets.next() {
709 edits.push(edit);
710 if let Some(item) = ending_item {
711 end_insertions[item] = edits.len();
712 }
713 }
714 }
715 Ok(())
716 }
717
718 fn finish_set<'a>(
719 &'a self,
720 target: &SetTarget<'a>,
721 plans: &mut [ItemPlan<'a>],
722 edits: ContextEdits<'_, 'a>,
723 insertions: &[Insertion<'_>],
724 ) -> Result<(TextEdit, usize, bool), EditError> {
725 let entry = self.entry(target.entry).ok_or(EditError::InvalidTarget)?;
726 let item = self.item(entry.item_id()).ok_or(EditError::InvalidTarget)?;
727 let span = entry.line_span();
728 let range = target.range.clone();
729 let context = target.context.clone();
730 let before = self.edited_context(context.start..range.start, edits);
731 let suffix = &self.source()[range.end..span.end];
732 let separate_comment = entry
733 .tokens()
734 .inline_comment
735 .as_ref()
736 .is_some_and(|comment| comment.start == range.end);
737 let encode = |ending: &str, preferred| {
738 let after = if ending.is_empty() {
739 self.edited_context(range.end..context.end, edits)
740 } else if suffix.is_empty() {
741 Cow::Borrowed(ending)
742 } else {
743 let mut combined = String::with_capacity(suffix.len() + ending.len());
744 combined.push_str(suffix);
745 combined.push_str(ending);
746 Cow::Owned(combined)
747 };
748 encode_native(
749 target.value,
750 preferred,
751 self.dialect(),
752 &before,
753 &after,
754 entry.value_span().is_none(),
755 separate_comment,
756 )
757 .map_err(|_| EditError::Unencodable)
758 };
759 let original_quote = plans[item.id()]
760 .desired
761 .ok_or(EditError::InvalidTarget)?
762 .quote;
763 let mut preferred = original_quote;
764 let mut ending = self.following_separator(item, insertions, plans);
765 let mut chosen = None;
766 for _ in 0..4 {
767 let (candidate, actual_quote) = encode(ending, preferred)?;
768 plans[item.id()]
769 .desired
770 .as_mut()
771 .ok_or(EditError::InvalidTarget)?
772 .quote = actual_quote;
773 let actual_ending = self.following_separator(item, insertions, plans);
774 plans[item.id()]
775 .desired
776 .as_mut()
777 .ok_or(EditError::InvalidTarget)?
778 .quote = original_quote;
779 if actual_ending == ending {
780 chosen = Some((candidate, actual_quote));
781 break;
782 }
783 preferred = actual_quote;
784 ending = actual_ending;
785 }
786 let (mut token, quote) = chosen.ok_or(EditError::Unencodable)?;
787 if separate_comment {
788 token.push(' ');
789 }
790 if entry.value_span().is_none() {
791 token.insert(0, '=');
792 }
793 plans[item.id()]
794 .desired
795 .as_mut()
796 .ok_or(EditError::InvalidTarget)?
797 .quote = quote;
798 let ending_field = range.is_empty() && range.start == span.end;
799 Ok((
800 TextEdit {
801 range,
802 replacement: token,
803 },
804 item.id(),
805 ending_field,
806 ))
807 }
808
809 fn value_context(&self, item: &Range<usize>, value: &Range<usize>) -> Range<usize> {
810 if self.dialect().line_byte_limit.is_none() {
811 return item.clone();
812 }
813 let breaks = |c| {
814 c == '\n' || (c == '\r' && self.dialect().line_breaks == crate::LineBreaks::Universal)
815 };
816 let start = self.source()[..item.start]
817 .rfind(breaks)
818 .map_or(0, |at| at + 1);
819 let end = self.source()[value.end..]
820 .find(breaks)
821 .map_or(self.source().len(), |at| {
822 let at = value.end + at;
823 at + if self.source().as_bytes()[at] == b'\r'
824 && self.source().as_bytes().get(at + 1) == Some(&b'\n')
825 {
826 2
827 } else {
828 1
829 }
830 });
831 start..end
832 }
833
834 fn edited_context<'a>(
835 &'a self,
836 range: Range<usize>,
837 edits: ContextEdits<'_, '_>,
838 ) -> Cow<'a, str> {
839 let base_start = edits.base.partition_point(|edit| {
840 edit.range.end < range.start
841 || (edit.range.end == range.start && !edit.range.is_empty())
842 });
843 let base_end = edits
844 .base
845 .partition_point(|edit| edit.range.start < range.end);
846 let set_start = edits.targets.partition_point(|target| {
847 target.range.end < range.start
848 || (target.range.end == range.start && !target.range.is_empty())
849 });
850 let set_end = edits
851 .targets
852 .partition_point(|target| target.range.start < range.end);
853 let mut base = edits.base[base_start..base_end].iter().peekable();
854 let mut sets = edits.sets[set_start..set_end]
855 .iter()
856 .filter_map(Option::as_ref)
857 .peekable();
858 if base.peek().is_none() && sets.peek().is_none() {
859 return Cow::Borrowed(&self.source()[range]);
860 }
861 let mut output = String::with_capacity(range.len());
862 let mut at = range.start;
863 while base.peek().is_some() || sets.peek().is_some() {
864 let take_base = match (base.peek(), sets.peek()) {
865 (Some(left), Some(right)) => edit_key(left) <= edit_key(right),
866 (Some(_), None) => true,
867 (None, Some(_)) => false,
868 (None, None) => break,
869 };
870 let edit = if take_base { base.next() } else { sets.next() };
871 let Some(edit) = edit else {
872 break;
873 };
874 output.push_str(&self.source()[at..edit.range.start]);
875 output.push_str(&edit.replacement);
876 at = edit.range.end;
877 }
878 output.push_str(&self.source()[at..range.end]);
879 Cow::Owned(output)
880 }
881
882 fn following_separator(
883 &self,
884 item: crate::Item<'_>,
885 insertions: &[Insertion<'_>],
886 plans: &[ItemPlan<'_>],
887 ) -> &'static str {
888 let next = insertions.partition_point(|insertion| insertion.offset < item.span().end);
889 let Some(insertion) = insertions.get(next) else {
890 return "";
891 };
892 let intervening = self
893 .items()
894 .skip(item.id() + 1)
895 .find(|next| plans[next.id()].retains(*next));
896 if intervening.is_some_and(|next| next.span().end <= insertion.offset) {
897 return "";
898 }
899 self.insertion_separator(Some(item), self.preferred_ending(), plans)
900 }
901
902 fn plan_rename<'a>(
903 &'a self,
904 id: usize,
905 key: &'a str,
906 plans: &mut [ItemPlan<'a>],
907 edits: &mut Vec<TextEdit>,
908 keys: &KeyLookup<'a>,
909 ) -> Result<(), EditError> {
910 let entry = self.entry(id).ok_or(EditError::InvalidTarget)?;
911 if key != entry.key() {
912 let existing = keys.count(key);
913 if existing != 0 {
914 return Err(EditError::Duplicate(existing + 1));
915 }
916 }
917 let plan = &mut plans[entry.item_id()];
918 claim(plan, 2)?;
919 let (range, replacement) =
920 if entry.key_span() != entry.tokens().key && !key.is_empty() && !key.contains('\'') {
921 (entry.key_span(), key.to_string())
922 } else {
923 (
924 entry.tokens().key.clone(),
925 key_token(key, self.dialect())
926 .ok_or(EditError::InvalidKey)?
927 .into_owned(),
928 )
929 };
930 edits.push(TextEdit { range, replacement });
931 if let Some(desired) = &mut plan.desired {
932 desired.key = key;
933 }
934 Ok(())
935 }
936
937 fn plan_export(
938 &self,
939 id: usize,
940 enabled: bool,
941 plans: &mut [ItemPlan<'_>],
942 edits: &mut Vec<TextEdit>,
943 ) -> Result<(), EditError> {
944 let entry = self.entry(id).ok_or(EditError::InvalidTarget)?;
945 let plan = &mut plans[entry.item_id()];
946 claim(plan, 4)?;
947 if enabled != entry.exported() {
948 let (range, replacement) = if enabled {
949 let start = entry.tokens().key.start;
950 (start..start, "export ".to_string())
951 } else {
952 let export = entry
953 .tokens()
954 .export
955 .as_ref()
956 .ok_or(EditError::InvalidTarget)?;
957 (
958 export.start..entry.tokens().space_after_export.end,
959 String::new(),
960 )
961 };
962 edits.push(TextEdit { range, replacement });
963 }
964 if let Some(desired) = &mut plan.desired {
965 desired.exported = enabled;
966 }
967 Ok(())
968 }
969
970 fn plan_insertion<'a>(
971 &self,
972 key: &'a str,
973 value: Option<&'a str>,
974 position: InsertPosition<'_>,
975 insertions: &mut Vec<Insertion<'a>>,
976 keys: &KeyLookup<'_>,
977 ) -> Result<(), EditError> {
978 let key_token = key_token(key, self.dialect()).ok_or(EditError::InvalidKey)?;
979 let offset = match position {
980 InsertPosition::End => self.source().len(),
981 InsertPosition::AfterKey(key) => keys.unique(key)?.line_span().end,
982 InsertPosition::AfterEntry(id) => {
983 self.entry(id)
984 .ok_or(EditError::InvalidTarget)?
985 .line_span()
986 .end
987 }
988 InsertPosition::Sorted => keys.first_greater(key),
989 };
990 insertions.push(Insertion {
991 offset,
992 desired: Desired {
993 key,
994 value,
995 quote: Quote::None,
996 exported: false,
997 },
998 key_token,
999 leading: String::new(),
1000 sorted: matches!(position, InsertPosition::Sorted),
1001 });
1002 Ok(())
1003 }
1004
1005 fn validate_renames(&self, plans: &[ItemPlan<'_>]) -> Result<(), EditError> {
1006 let mut renamed = plans.iter().enumerate().filter_map(|(index, plan)| {
1009 if plan.fields & 2 == 0 {
1010 return None;
1011 }
1012 let desired = plan.desired?;
1013 let original = self.item(index)?.entry()?;
1014 (original.key() != desired.key).then_some(desired.key)
1015 });
1016 let Some(first) = renamed.next() else {
1017 return Ok(());
1018 };
1019 let Some(second) = renamed.next() else {
1020 return Ok(());
1021 };
1022 let mut keys = Vec::from([first, second]);
1023 keys.extend(renamed);
1024 keys.sort_unstable();
1025 if keys.windows(2).any(|pair| pair[0] == pair[1]) {
1026 return Err(EditError::WouldChangeMeaning);
1027 }
1028 Ok(())
1029 }
1030
1031 fn last_retained_item(&self, plans: &[ItemPlan<'_>]) -> Option<crate::Item<'_>> {
1032 let mut retained = self
1033 .items()
1034 .rev()
1035 .filter(|item| plans[item.id()].retains(*item));
1036 let last = retained.next()?;
1037 let becomes_prelude = !self.has_bom()
1040 && plans[last.id()].whole == Whole::Uncomment
1041 && last.line_ending() == crate::LineEnding::None
1042 && last.raw().starts_with('#')
1043 && last
1044 .comment_text()
1045 .is_some_and(|body| body.strip_prefix(' ').unwrap_or(body) == "\u{feff}");
1046 if becomes_prelude && retained.next().is_none() {
1047 None
1048 } else {
1049 Some(last)
1050 }
1051 }
1052 fn insertion_separator(
1053 &self,
1054 previous: Option<crate::Item<'_>>,
1055 ending: &'static str,
1056 plans: &[ItemPlan<'_>],
1057 ) -> &'static str {
1058 previous.map_or("", |item| {
1059 let plan = &plans[item.id()];
1060 let next_statement = self.dialect().line_byte_limit.is_none()
1061 && plan.whole == Whole::Keep
1062 && self.dialect().quoted_tail == crate::QuotedTail::NextStatement
1063 && plan
1064 .desired
1065 .is_some_and(|desired| desired.quote != Quote::None);
1066 let needs_separator = (item.line_ending() == crate::LineEnding::None
1067 && !next_statement)
1068 || (self.dialect().line_breaks == crate::LineBreaks::Lf
1069 && item.line_ending() == crate::LineEnding::Cr);
1070 if needs_separator {
1071 ending
1072 } else {
1073 ""
1074 }
1075 })
1076 }
1077
1078 fn merge_boundary_fields(
1079 insertions: &mut [Insertion<'_>],
1080 edits: &mut Vec<TextEdit>,
1081 end_insertions: &mut [usize],
1082 ) {
1083 for ending in end_insertions {
1086 let Some(index) = ending.checked_sub(1) else {
1087 continue;
1088 };
1089 let edit = &mut edits[index];
1090 *ending = edit.replacement.len();
1091 let at = insertions.partition_point(|insert| insert.offset < edit.range.start);
1092 if let Some(insertion) = insertions
1093 .get_mut(at)
1094 .filter(|insert| insert.offset == edit.range.start)
1095 {
1096 insertion.leading = core::mem::take(&mut edit.replacement);
1097 }
1098 }
1099 edits.retain(|edit| !edit.range.is_empty() || !edit.replacement.is_empty());
1100 }
1101
1102 fn plan_insertions<'a>(
1103 &self,
1104 mut insertions: Vec<Insertion<'a>>,
1105 plans: &[ItemPlan<'_>],
1106 edits: &mut Vec<TextEdit>,
1107 end_insertions: &mut [usize],
1108 ) -> Result<(Vec<InsertGroup<'a>>, &'static str), EditError> {
1109 Self::merge_boundary_fields(&mut insertions, edits, end_insertions);
1110 if insertions.is_empty() {
1111 return Ok((Vec::new(), ""));
1112 }
1113 let ending = self.preferred_ending();
1114 let last_retained = self.last_retained_item(plans);
1115 let mut retained = self
1116 .items()
1117 .filter(|item| plans[item.id()].retains(*item))
1118 .peekable();
1119 let mut previous = None;
1120 let mut previous_group_item = None;
1121 let mut groups = Vec::new();
1122 let mut pending = insertions.as_mut_slice();
1123 while !pending.is_empty() {
1124 let offset = pending[0].offset;
1125 let count = pending.partition_point(|insert| insert.offset == offset);
1126 let (same, remaining) = pending.split_at_mut(count);
1127 let mut text = core::mem::take(&mut same[0].leading);
1128 if same.iter().all(|insert| insert.sorted) {
1129 same.sort_by(|a, b| a.desired.key.cmp(b.desired.key));
1130 }
1131 let at_end = offset == self.source().len();
1132 while retained
1133 .peek()
1134 .is_some_and(|item| item.span().end <= offset)
1135 {
1136 previous = retained.next();
1137 }
1138 let final_in_output = remaining.is_empty()
1139 && last_retained.map_or(true, |item| item.span().start < offset);
1140 let prior = if at_end { last_retained } else { previous };
1141 let owner = prior.map(crate::Item::id);
1142 let prefix = if !groups.is_empty() && owner == previous_group_item {
1143 ""
1144 } else {
1145 self.insertion_separator(prior, ending, plans)
1146 };
1147 if let Some(owner) = owner {
1148 end_insertions[owner] += prefix.len();
1149 }
1150 previous_group_item = owner;
1151 text.push_str(prefix);
1152 let mut entries = Vec::with_capacity(same.len());
1153 let count = same.len();
1154 for (index, insertion) in same.iter_mut().enumerate() {
1155 let suffix = if !final_in_output
1156 || self.has_final_newline()
1157 || index + 1 != count
1158 || (self.dialect().syntax == crate::Syntax::Compose
1159 && insertion.desired.value.is_none())
1160 {
1161 ending
1162 } else {
1163 ""
1164 };
1165 entries.push(insertion.append_to(&mut text, self.dialect(), suffix)?);
1166 }
1167 groups.push(InsertGroup {
1168 offset,
1169 len: text.len(),
1170 entries,
1171 });
1172 let insertion = TextEdit {
1173 range: offset..offset,
1174 replacement: text,
1175 };
1176 edits.push(insertion);
1177 pending = remaining;
1178 }
1179 edits.rotate_right(groups.len());
1182 Ok((groups, ending))
1183 }
1184
1185 fn normalize_edits(mut edits: Vec<TextEdit>) -> Result<Vec<TextEdit>, EditError> {
1186 edits.sort_by(|a, b| {
1188 a.range
1189 .start
1190 .cmp(&b.range.start)
1191 .then(a.range.end.cmp(&b.range.end))
1192 });
1193 if edits
1194 .windows(2)
1195 .any(|pair| pair[1].range.start < pair[0].range.end)
1196 {
1197 return Err(EditError::ConflictingEdits);
1198 }
1199 edits.dedup_by(|later, earlier| {
1200 if later.range.is_empty() && earlier.range == later.range {
1201 earlier.replacement.push_str(&later.replacement);
1202 true
1203 } else {
1204 false
1205 }
1206 });
1207 Ok(edits)
1208 }
1209
1210 fn apply_edits(source: &str, edits: &[TextEdit]) -> String {
1211 let removed: usize = edits.iter().map(|edit| edit.range.len()).sum();
1212 let added: usize = edits.iter().map(|edit| edit.replacement.len()).sum();
1213 let mut output = String::with_capacity(source.len() - removed + added);
1214 let mut cursor = 0;
1215 for edit in edits {
1216 output.push_str(&source[cursor..edit.range.start]);
1217 output.push_str(&edit.replacement);
1218 cursor = edit.range.end;
1219 }
1220 output.push_str(&source[cursor..]);
1221 output
1222 }
1223
1224 fn expected_regions<'a>(
1225 &self,
1226 plans: &[ItemPlan<'a>],
1227 end_insertions: &[usize],
1228 edits: &[TextEdit],
1229 groups: Vec<InsertGroup<'a>>,
1230 ) -> Result<Vec<Region<'a>>, EditError> {
1231 let boundaries = Boundaries::new(edits);
1232 let mut regions = Vec::with_capacity(self.items.len() + groups.len());
1233 let mut item = 0;
1234 while item < self.items.len() {
1235 let plan = &plans[item];
1236 if plan.whole == Whole::Remove {
1237 item += 1;
1238 continue;
1239 }
1240 let first = item;
1241 let expect = match plan.whole {
1242 Whole::Uncomment => {
1243 let mut next = item + 1;
1244 while next < plans.len()
1245 && group_at(&groups, self.items[next - 1].span.end).is_none()
1246 {
1247 match plans[next].whole {
1248 Whole::Remove => {}
1249 Whole::Uncomment => item = next,
1250 _ => break,
1251 }
1252 next += 1;
1253 }
1254 Expect::Uncommented
1255 }
1256 Whole::Comment => Expect::Comments,
1257 Whole::Keep if plan.fields != 0 => {
1258 Expect::Entry(plan.desired.ok_or(EditError::InvalidTarget)?)
1259 }
1260 Whole::Keep => Expect::Original(item),
1261 Whole::Remove => return Err(EditError::InvalidTarget),
1262 };
1263 let end = self.items[item].span.end;
1264 let start = self.items[first].span.start;
1265 let inserted_before = group_at(&groups, start).map_or(0, |group| group.len);
1266 regions.push(Region {
1267 range: boundaries.map(start) + inserted_before
1268 ..boundaries.map(end) + end_insertions[item],
1269 expect,
1270 });
1271 item += 1;
1272 }
1273 for group in groups {
1274 let start = boundaries.map(group.offset);
1275 for (range, desired) in group.entries {
1276 regions.push(Region {
1277 range: start + range.start..start + range.end,
1278 expect: Expect::Entry(desired),
1279 });
1280 }
1281 }
1282 regions.sort_by_key(|region| region.range.start);
1283 Ok(regions)
1284 }
1285
1286 fn verify_regions(
1287 &self,
1288 output: &str,
1289 mut regions: Vec<Region<'_>>,
1290 ending: &str,
1291 ) -> Result<(), EditError> {
1292 let parsed = crate::parser::parse_document(output, self.dialect());
1293 if parsed.has_bom() != self.has_bom() {
1294 let selected = regions.first_mut().filter(|region| {
1297 !self.has_bom()
1298 && parsed.has_bom()
1299 && region.range.start == 0
1300 && region.range.end >= 3
1301 && matches!(region.expect, Expect::Uncommented)
1302 });
1303 let Some(region) = selected else {
1304 return Err(EditError::WouldChangeMeaning);
1305 };
1306 region.range.start = 3;
1307 }
1308 let mut actual = parsed.items();
1309 for region in regions {
1310 if region.range.is_empty() && matches!(region.expect, Expect::Uncommented) {
1311 continue;
1312 }
1313 let first = actual.next().ok_or(EditError::WouldChangeMeaning)?;
1314 if first.span().start != region.range.start {
1315 return Err(EditError::WouldChangeMeaning);
1316 }
1317 match region.expect {
1318 Expect::Entry(desired) => {
1319 verify_entry_region(first, desired, ®ion.range, &mut actual)?;
1320 }
1321 Expect::Original(id) => {
1322 let original = self.item(id).ok_or(EditError::InvalidTarget)?;
1323 if first.span() != region.range || first.kind() != original.kind() {
1324 return Err(EditError::WouldChangeMeaning);
1325 }
1326 let original_text = original.raw();
1327 let new_text = &output[region.range.clone()];
1328 if !new_text.starts_with(original_text)
1329 || (new_text.len() != original_text.len()
1330 && &new_text[original_text.len()..] != ending)
1331 {
1332 return Err(EditError::WouldChangeMeaning);
1333 }
1334 if let Some(entry) = original.entry() {
1335 if !first
1336 .entry()
1337 .is_some_and(|after| Desired::from_entry(entry).matches(after))
1338 {
1339 return Err(EditError::WouldChangeMeaning);
1340 }
1341 }
1342 match (&self.items[id].invalid, &parsed.items[first.id()].invalid) {
1343 (None, None) => {}
1344 (Some(before), Some(after))
1345 if before.code() == after.code()
1346 && before.span().start - original.span().start
1347 == after.span().start - region.range.start
1348 && before.span().end - original.span().start
1349 == after.span().end - region.range.start => {}
1350 _ => return Err(EditError::WouldChangeMeaning),
1351 }
1352 }
1353 Expect::Comments | Expect::Uncommented => {
1354 let comments_only = matches!(region.expect, Expect::Comments);
1355 let mut current = first;
1356 loop {
1357 if current.span().end > region.range.end
1358 || current.kind() == ItemKind::Invalid
1359 || current
1360 .diagnostic()
1361 .is_some_and(|diagnostic| diagnostic.severity() == Severity::Error)
1362 || (comments_only && current.kind() != ItemKind::Comment)
1363 {
1364 return Err(EditError::WouldChangeMeaning);
1365 }
1366 if current.span().end == region.range.end {
1367 break;
1368 }
1369 let next = actual.next().ok_or(EditError::WouldChangeMeaning)?;
1370 if next.span().start != current.span().end {
1371 return Err(EditError::WouldChangeMeaning);
1372 }
1373 current = next;
1374 }
1375 }
1376 }
1377 }
1378 if actual.next().is_some() {
1379 return Err(EditError::WouldChangeMeaning);
1380 }
1381 Ok(())
1382 }
1383
1384 pub fn set(&self, key: &str, value: &str) -> Result<VerifiedEdits, EditError> {
1389 self.edit(&[Edit::Set {
1390 entry: self.unique(key)?.id(),
1391 value,
1392 }])
1393 }
1394 pub fn remove(&self, key: &str) -> Result<VerifiedEdits, EditError> {
1399 self.edit(&[Edit::Remove {
1400 item: self.unique(key)?.item_id(),
1401 }])
1402 }
1403 pub fn rename(&self, key: &str, new_key: &str) -> Result<VerifiedEdits, EditError> {
1408 self.edit(&[Edit::Rename {
1409 entry: self.unique(key)?.id(),
1410 key: new_key,
1411 }])
1412 }
1413 pub fn insert(
1418 &self,
1419 key: &str,
1420 value: Option<&str>,
1421 position: InsertPosition<'_>,
1422 ) -> Result<VerifiedEdits, EditError> {
1423 self.edit(&[Edit::Insert {
1424 key,
1425 value,
1426 position,
1427 }])
1428 }
1429 pub fn comment(&self, key: &str) -> Result<VerifiedEdits, EditError> {
1434 self.edit(&[Edit::Comment {
1435 item: self.unique(key)?.item_id(),
1436 }])
1437 }
1438 pub fn uncomment(&self, item: usize) -> Result<VerifiedEdits, EditError> {
1443 self.edit(&[Edit::Uncomment { item }])
1444 }
1445 pub fn set_export(&self, key: &str, enabled: bool) -> Result<VerifiedEdits, EditError> {
1450 self.edit(&[Edit::Export {
1451 entry: self.unique(key)?.id(),
1452 enabled,
1453 }])
1454 }
1455}
1456
1457pub fn set(text: &str, key: &str, value: &str) -> Result<String, EditError> {
1462 crate::parser::parse_document(text, Dialect::PORTABLE)
1463 .set(key, value)
1464 .map(VerifiedEdits::into_string)
1465}
1466pub fn remove(text: &str, key: &str) -> Result<String, EditError> {
1471 crate::parser::parse_document(text, Dialect::PORTABLE)
1472 .remove(key)
1473 .map(VerifiedEdits::into_string)
1474}