Handle out-of-order edits coming from LSP

This commit is contained in:
Antonio Scandurra 2022-06-06 16:15:11 +02:00
parent 8826ad5ddd
commit 70afc06666
2 changed files with 126 additions and 3 deletions

View file

@ -36,7 +36,7 @@ use sha2::{Digest, Sha256};
use similar::{ChangeTag, TextDiff};
use std::{
cell::RefCell,
cmp::{self, Ordering},
cmp::{self, Ordering, Reverse},
convert::TryInto,
ffi::OsString,
hash::Hash,
@ -5164,8 +5164,10 @@ impl Project {
let mut lsp_edits = lsp_edits
.into_iter()
.map(|edit| (range_from_lsp(edit.range), edit.new_text))
.peekable();
.collect::<Vec<_>>();
lsp_edits.sort_by_key(|(range, _)| range.start);
let mut lsp_edits = lsp_edits.into_iter().peekable();
let mut edits = Vec::new();
while let Some((mut range, mut new_text)) = lsp_edits.next() {
// Combine any LSP edits that are adjacent.
@ -6959,6 +6961,121 @@ mod tests {
});
}
#[gpui::test]
async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
cx.foreground().forbid_parking();
let text = "
use a::b;
use a::c;
fn f() {
b();
c();
}
"
.unindent();
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/dir",
json!({
"a.rs": text.clone(),
}),
)
.await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
.await
.unwrap();
// Simulate the language server sending us edits in a non-ordered fashion,
// with ranges sometimes being inverted.
let edits = project
.update(cx, |project, cx| {
project.edits_from_lsp(
&buffer,
[
lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 9),
lsp::Position::new(0, 9),
),
new_text: "\n\n".into(),
},
lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 8),
lsp::Position::new(0, 4),
),
new_text: "a::{b, c}".into(),
},
lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(1, 0),
lsp::Position::new(7, 0),
),
new_text: "".into(),
},
lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 9),
lsp::Position::new(0, 9),
),
new_text: "
fn f() {
b();
c();
}"
.unindent(),
},
],
None,
cx,
)
})
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
let edits = edits
.into_iter()
.map(|(range, text)| {
(
range.start.to_point(&buffer)..range.end.to_point(&buffer),
text,
)
})
.collect::<Vec<_>>();
assert_eq!(
edits,
[
(Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
(Point::new(1, 0)..Point::new(2, 0), "".into())
]
);
for (range, new_text) in edits {
buffer.edit([(range, new_text)], cx);
}
assert_eq!(
buffer.text(),
"
use a::{b, c};
fn f() {
b();
c();
}
"
.unindent()
);
});
}
fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
buffer: &Buffer,
range: Range<T>,