Skip to content

Commit 7cf4a8a

Browse files
committed
fix: Place snippets correctly in multi-edit assists
1 parent 0d6024c commit 7cf4a8a

File tree

1 file changed

+33
-5
lines changed

1 file changed

+33
-5
lines changed

crates/rust-analyzer/src/lsp/to_proto.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,16 @@ fn merge_text_and_snippet_edits(
930930
let mut edits: Vec<SnippetTextEdit> = vec![];
931931
let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
932932
let text_edits = edit.into_iter();
933+
// offset to go from the final source location to the original source location
934+
let mut source_text_offset = 0i32;
935+
936+
let offset_range = |range: TextRange, offset: i32| -> TextRange {
937+
// map the snippet range from the target location into the original source location
938+
let start = u32::from(range.start()).checked_add_signed(offset).unwrap_or(0);
939+
let end = u32::from(range.end()).checked_add_signed(offset).unwrap_or(0);
940+
941+
TextRange::new(start.into(), end.into())
942+
};
933943

934944
for current_indel in text_edits {
935945
let new_range = {
@@ -938,10 +948,17 @@ fn merge_text_and_snippet_edits(
938948
TextRange::at(current_indel.delete.start(), insert_len)
939949
};
940950

951+
// figure out how much this Indel will shift future ranges from the initial source
952+
let offset_adjustment =
953+
u32::from(current_indel.delete.len()) as i32 - u32::from(new_range.len()) as i32;
954+
941955
// insert any snippets before the text edit
942-
for (snippet_index, snippet_range) in
943-
snippets.take_while_ref(|(_, range)| range.end() < new_range.start())
944-
{
956+
for (snippet_index, snippet_range) in snippets.peeking_take_while(|(_, range)| {
957+
offset_range(*range, source_text_offset).end() < new_range.start()
958+
}) {
959+
// adjust the snippet range into the corresponding initial source location
960+
let snippet_range = offset_range(snippet_range, source_text_offset);
961+
945962
let snippet_range = if !stdx::always!(
946963
snippet_range.is_empty(),
947964
"placeholder range {:?} is before current text edit range {:?}",
@@ -965,11 +982,16 @@ fn merge_text_and_snippet_edits(
965982
})
966983
}
967984

968-
if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) {
985+
if snippets.peek().is_some_and(|(_, range)| {
986+
new_range.intersect(offset_range(*range, source_text_offset)).is_some()
987+
}) {
969988
// at least one snippet edit intersects this text edit,
970989
// so gather all of the edits that intersect this text edit
971990
let mut all_snippets = snippets
972-
.take_while_ref(|(_, range)| new_range.intersect(*range).is_some())
991+
.peeking_take_while(|(_, range)| {
992+
new_range.intersect(offset_range(*range, source_text_offset)).is_some()
993+
})
994+
.map(|(tabstop, range)| (tabstop, offset_range(range, source_text_offset)))
973995
.collect_vec();
974996

975997
// ensure all of the ranges are wholly contained inside of the new range
@@ -1010,10 +1032,16 @@ fn merge_text_and_snippet_edits(
10101032
// since it wasn't consumed, it's available for the next pass
10111033
edits.push(snippet_text_edit(line_index, false, current_indel));
10121034
}
1035+
1036+
// update the final source -> initial source mapping offset
1037+
source_text_offset += offset_adjustment;
10131038
}
10141039

10151040
// insert any remaining tabstops
10161041
edits.extend(snippets.map(|(snippet_index, snippet_range)| {
1042+
// adjust the snippet range into the corresponding initial source location
1043+
let snippet_range = offset_range(snippet_range, source_text_offset);
1044+
10171045
let snippet_range = if !stdx::always!(
10181046
snippet_range.is_empty(),
10191047
"found placeholder snippet {:?} without a text edit",

0 commit comments

Comments
 (0)