internal: Improve reporting of intersecting changes

This commit is contained in:
Giga Bowser 2025-01-10 14:33:21 -06:00
parent 8925544998
commit b4f2d62952
3 changed files with 142 additions and 8 deletions

View file

@ -1,9 +1,14 @@
//! Implementation of applying changes to a syntax tree.
use std::{cmp::Ordering, collections::VecDeque, ops::RangeInclusive};
use std::{
cmp::Ordering,
collections::VecDeque,
ops::{Range, RangeInclusive},
};
use rowan::TextRange;
use rustc_hash::FxHashMap;
use stdx::format_to;
use crate::{
syntax_editor::{mapping::MissingMapping, Change, ChangeKind, PositionRepr},
@ -76,11 +81,9 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
|| (l.target_range().end() <= r.target_range().start())
});
if stdx::never!(
!disjoint_replaces_ranges,
"some replace change ranges intersect: {:?}",
changes
) {
if !disjoint_replaces_ranges {
report_intersecting_changes(&changes, get_node_depth, &root);
return SyntaxEdit {
old_root: root.clone(),
new_root: root,
@ -293,6 +296,78 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
}
}
fn report_intersecting_changes(
changes: &[Change],
mut get_node_depth: impl FnMut(rowan::SyntaxNode<crate::RustLanguage>) -> usize,
root: &rowan::SyntaxNode<crate::RustLanguage>,
) {
let intersecting_changes = changes
.iter()
.zip(changes.iter().skip(1))
.filter(|(l, r)| {
// We only care about checking for disjoint replace ranges.
matches!(
(l.change_kind(), r.change_kind()),
(
ChangeKind::Replace | ChangeKind::ReplaceRange,
ChangeKind::Replace | ChangeKind::ReplaceRange
)
)
})
.filter(|(l, r)| {
get_node_depth(l.target_parent()) == get_node_depth(r.target_parent())
&& (l.target_range().end() > r.target_range().start())
});
let mut error_msg = String::from("some replace change ranges intersect!\n");
let parent_str = root.to_string();
for (l, r) in intersecting_changes {
let mut highlighted_str = parent_str.clone();
let l_range = l.target_range();
let r_range = r.target_range();
let i_range = l_range.intersect(r_range).unwrap();
let i_str = format!("\x1b[46m{}", &parent_str[i_range]);
let pre_range: Range<usize> = l_range.start().into()..i_range.start().into();
let pre_str = format!("\x1b[44m{}", &parent_str[pre_range]);
let (highlight_range, highlight_str) = if l_range == r_range {
format_to!(error_msg, "\x1b[46mleft change:\x1b[0m {l:?} {l}\n");
format_to!(error_msg, "\x1b[46mequals\x1b[0m\n");
format_to!(error_msg, "\x1b[46mright change:\x1b[0m {r:?} {r}\n");
let i_highlighted = format!("{i_str}\x1b[0m\x1b[K");
let total_range: Range<usize> = i_range.into();
(total_range, i_highlighted)
} else {
format_to!(error_msg, "\x1b[44mleft change:\x1b[0m {l:?} {l}\n");
let range_end = if l_range.contains_range(r_range) {
format_to!(error_msg, "\x1b[46mcovers\x1b[0m\n");
format_to!(error_msg, "\x1b[46mright change:\x1b[0m {r:?} {r}\n");
l_range.end()
} else {
format_to!(error_msg, "\x1b[46mintersects\x1b[0m\n");
format_to!(error_msg, "\x1b[42mright change:\x1b[0m {r:?} {r}\n");
r_range.end()
};
let post_range: Range<usize> = i_range.end().into()..range_end.into();
let post_str = format!("\x1b[42m{}", &parent_str[post_range]);
let result = format!("{pre_str}{i_str}{post_str}\x1b[0m\x1b[K");
let total_range: Range<usize> = l_range.start().into()..range_end.into();
(total_range, result)
};
highlighted_str.replace_range(highlight_range, &highlight_str);
format_to!(error_msg, "{highlighted_str}\n");
}
stdx::always!(false, "{}", error_msg);
}
fn to_owning_node(element: &SyntaxElement) -> SyntaxNode {
match element {
SyntaxElement::Node(node) => node.clone(),