From 70afc06666329da3543d169df339135674d26b2b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 6 Jun 2022 16:15:11 +0200 Subject: [PATCH] Handle out-of-order edits coming from LSP --- crates/language/src/language.rs | 8 ++- crates/project/src/project.rs | 121 +++++++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 00f86a2488cd1b1a3b85623056e9a6f45777eb0e..f13088f99750c52f82ace0de7a58210bd1f81f23 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -22,6 +22,7 @@ use serde_json::Value; use std::{ any::Any, cell::RefCell, + mem, ops::Range, path::{Path, PathBuf}, str, @@ -692,5 +693,10 @@ pub fn range_to_lsp(range: Range) -> lsp::Range { } pub fn range_from_lsp(range: lsp::Range) -> Range { - point_from_lsp(range.start)..point_from_lsp(range.end) + let mut start = point_from_lsp(range.start); + let mut end = point_from_lsp(range.end); + if start > end { + mem::swap(&mut start, &mut end); + } + start..end } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3e09436b4bc80ad3df67f9c54123869f2c4e4fed..dc21e5c253e54b8f84c8e924c4230aedc02c7983 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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::>(); + 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::>(); + + 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( buffer: &Buffer, range: Range,