From 4a6fa8f0dfcebbb4ea80394e5e4ca21f076f58f2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 5 May 2020 23:15:49 +0200 Subject: Rename AtomTextEdit -> Indel --- crates/ra_text_edit/src/lib.rs | 131 +++++++++++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 17 deletions(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index e656260c7..c41bf324b 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -1,30 +1,40 @@ -//! FIXME: write short doc here - -mod text_edit; +//! Representation of a `TextEdit`. +//! +//! `rust-analyzer` never mutates text itself and only sends diffs to clients, +//! so `TextEdit` is the ultimate representation of the work done by +//! rust-analyzer. use text_size::{TextRange, TextSize}; -pub use crate::text_edit::{TextEdit, TextEditBuilder}; - -/// Must not overlap with other `AtomTextEdit`s +/// `InsertDelete` -- a single "atomic" change to text +/// +/// Must not overlap with other `InDel`s #[derive(Debug, Clone)] -pub struct AtomTextEdit { +pub struct Indel { + pub insert: String, /// Refers to offsets in the original text pub delete: TextRange, - pub insert: String, } -impl AtomTextEdit { - pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit { - AtomTextEdit { delete: range, insert: replace_with } - } +#[derive(Debug, Clone)] +pub struct TextEdit { + indels: Vec, +} - pub fn delete(range: TextRange) -> AtomTextEdit { - AtomTextEdit::replace(range, String::new()) - } +#[derive(Debug, Default)] +pub struct TextEditBuilder { + indels: Vec, +} - pub fn insert(offset: TextSize, text: String) -> AtomTextEdit { - AtomTextEdit::replace(TextRange::empty(offset), text) +impl Indel { + pub fn insert(offset: TextSize, text: String) -> Indel { + Indel::replace(TextRange::empty(offset), text) + } + pub fn delete(range: TextRange) -> Indel { + Indel::replace(range, String::new()) + } + pub fn replace(range: TextRange, replace_with: String) -> Indel { + Indel { delete: range, insert: replace_with } } pub fn apply(&self, mut text: String) -> String { @@ -34,3 +44,90 @@ impl AtomTextEdit { text } } + +impl TextEdit { + pub fn insert(offset: TextSize, text: String) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.insert(offset, text); + builder.finish() + } + + pub fn delete(range: TextRange) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.delete(range); + builder.finish() + } + + pub fn replace(range: TextRange, replace_with: String) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.replace(range, replace_with); + builder.finish() + } + + pub(crate) fn from_indels(mut indels: Vec) -> TextEdit { + indels.sort_by_key(|a| (a.delete.start(), a.delete.end())); + for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) { + assert!(a1.delete.end() <= a2.delete.start()) + } + TextEdit { indels } + } + + pub fn as_indels(&self) -> &[Indel] { + &self.indels + } + + pub fn apply(&self, text: &str) -> String { + let mut total_len = TextSize::of(text); + for indel in self.indels.iter() { + total_len += TextSize::of(&indel.insert); + total_len -= indel.delete.end() - indel.delete.start(); + } + let mut buf = String::with_capacity(total_len.into()); + let mut prev = 0; + for indel in self.indels.iter() { + let start: usize = indel.delete.start().into(); + let end: usize = indel.delete.end().into(); + if start > prev { + buf.push_str(&text[prev..start]); + } + buf.push_str(&indel.insert); + prev = end; + } + buf.push_str(&text[prev..text.len()]); + assert_eq!(TextSize::of(&buf), total_len); + buf + } + + pub fn apply_to_offset(&self, offset: TextSize) -> Option { + let mut res = offset; + for indel in self.indels.iter() { + if indel.delete.start() >= offset { + break; + } + if offset < indel.delete.end() { + return None; + } + res += TextSize::of(&indel.insert); + res -= indel.delete.len(); + } + Some(res) + } +} + +impl TextEditBuilder { + pub fn replace(&mut self, range: TextRange, replace_with: String) { + self.indels.push(Indel::replace(range, replace_with)) + } + pub fn delete(&mut self, range: TextRange) { + self.indels.push(Indel::delete(range)) + } + pub fn insert(&mut self, offset: TextSize, text: String) { + self.indels.push(Indel::insert(offset, text)) + } + pub fn finish(self) -> TextEdit { + TextEdit::from_indels(self.indels) + } + pub fn invalidates_offset(&self, offset: TextSize) -> bool { + self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset)) + } +} -- cgit v1.2.3 From 27c7ef6d65ffa6a642768377d3f0ba85ac8564bf Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 5 May 2020 23:23:29 +0200 Subject: Use more natural signature for Edit::apply --- crates/ra_text_edit/src/lib.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index c41bf324b..7138bbc65 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -37,11 +37,10 @@ impl Indel { Indel { delete: range, insert: replace_with } } - pub fn apply(&self, mut text: String) -> String { + pub fn apply(&self, text: &mut String) { let start: usize = self.delete.start().into(); let end: usize = self.delete.end().into(); text.replace_range(start..end, &self.insert); - text } } @@ -76,8 +75,17 @@ impl TextEdit { &self.indels } - pub fn apply(&self, text: &str) -> String { - let mut total_len = TextSize::of(text); + pub fn apply(&self, text: &mut String) { + match self.indels.len() { + 0 => return, + 1 => { + self.indels[0].apply(text); + return; + } + _ => (), + } + + let mut total_len = TextSize::of(&*text); for indel in self.indels.iter() { total_len += TextSize::of(&indel.insert); total_len -= indel.delete.end() - indel.delete.start(); @@ -95,7 +103,10 @@ impl TextEdit { } buf.push_str(&text[prev..text.len()]); assert_eq!(TextSize::of(&buf), total_len); - buf + + // FIXME: figure out a way to mutate the text in-place or reuse the + // memory in some other way + *text = buf } pub fn apply_to_offset(&self, offset: TextSize) -> Option { -- cgit v1.2.3 From 3850b1c0860a075f1fd569577c2a2fecd1fc2f0c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 6 May 2020 11:31:26 +0200 Subject: Lift SourceChange to the ra_ide_db --- crates/ra_text_edit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 7138bbc65..64b67f2ad 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -4,7 +4,7 @@ //! so `TextEdit` is the ultimate representation of the work done by //! rust-analyzer. -use text_size::{TextRange, TextSize}; +pub use text_size::{TextRange, TextSize}; /// `InsertDelete` -- a single "atomic" change to text /// -- cgit v1.2.3 From 1116c9a0e9992d0dea8dac87de95c8a74c093cff Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 6 May 2020 13:08:37 +0200 Subject: Fix TODO --- crates/ra_text_edit/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 64b67f2ad..3409713ff 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -71,6 +71,10 @@ impl TextEdit { TextEdit { indels } } + pub fn is_empty(&self) -> bool { + self.indels.is_empty() + } + pub fn as_indels(&self) -> &[Indel] { &self.indels } -- cgit v1.2.3 From 1586bab0b97bef411e6187dfc389557edbc5a16e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 10 May 2020 13:55:24 +0200 Subject: Simplify proto conversion Trait based infra in conv.rs is significantly more complicated than what we actually need here. --- crates/ra_text_edit/src/lib.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 3409713ff..37f77cc47 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -75,6 +75,7 @@ impl TextEdit { self.indels.is_empty() } + // FXME: impl IntoIter instead pub fn as_indels(&self) -> &[Indel] { &self.indels } -- cgit v1.2.3 From 9f0a7eb97b4e047cebbe51ffd6f9e2092dd63e00 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Fri, 24 Apr 2020 21:57:10 +0200 Subject: Make some stuff public so that they can be reused by other tools --- crates/ra_text_edit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 37f77cc47..c4f945101 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -21,7 +21,7 @@ pub struct TextEdit { indels: Vec, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct TextEditBuilder { indels: Vec, } -- cgit v1.2.3 From 5f57491c981530103e1e26dcd280e1a2df10f853 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 21 May 2020 15:56:18 +0200 Subject: Cleanup TextEdit --- crates/ra_text_edit/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index c4f945101..199fd1096 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -3,6 +3,7 @@ //! `rust-analyzer` never mutates text itself and only sends diffs to clients, //! so `TextEdit` is the ultimate representation of the work done by //! rust-analyzer. +use std::{slice, vec}; pub use text_size::{TextRange, TextSize}; @@ -71,17 +72,24 @@ impl TextEdit { TextEdit { indels } } + pub fn len(&self) -> usize { + self.indels.len() + } + pub fn is_empty(&self) -> bool { self.indels.is_empty() } - // FXME: impl IntoIter instead - pub fn as_indels(&self) -> &[Indel] { - &self.indels + pub fn iter(&self) -> slice::Iter<'_, Indel> { + self.indels.iter() + } + + pub fn into_iter(self) -> vec::IntoIter { + self.indels.into_iter() } pub fn apply(&self, text: &mut String) { - match self.indels.len() { + match self.len() { 0 => return, 1 => { self.indels[0].apply(text); -- cgit v1.2.3 From 5b5ebec440841ee98a0aa70b71a135d94f5ca077 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 21 May 2020 19:50:23 +0200 Subject: Formalize JoinLines protocol extension --- crates/ra_text_edit/src/lib.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) (limited to 'crates/ra_text_edit/src/lib.rs') diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 199fd1096..25554f583 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs @@ -17,7 +17,7 @@ pub struct Indel { pub delete: TextRange, } -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone)] pub struct TextEdit { indels: Vec, } @@ -64,14 +64,6 @@ impl TextEdit { builder.finish() } - pub(crate) fn from_indels(mut indels: Vec) -> TextEdit { - indels.sort_by_key(|a| (a.delete.start(), a.delete.end())); - for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) { - assert!(a1.delete.end() <= a2.delete.start()) - } - TextEdit { indels } - } - pub fn len(&self) -> usize { self.indels.len() } @@ -122,6 +114,17 @@ impl TextEdit { *text = buf } + pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> { + // FIXME: can be done without allocating intermediate vector + let mut all = self.iter().chain(other.iter()).collect::>(); + if !check_disjoint(&mut all) { + return Err(other); + } + self.indels.extend(other.indels); + assert!(check_disjoint(&mut self.indels)); + Ok(()) + } + pub fn apply_to_offset(&self, offset: TextSize) -> Option { let mut res = offset; for indel in self.indels.iter() { @@ -149,9 +152,19 @@ impl TextEditBuilder { self.indels.push(Indel::insert(offset, text)) } pub fn finish(self) -> TextEdit { - TextEdit::from_indels(self.indels) + let mut indels = self.indels; + assert!(check_disjoint(&mut indels)); + TextEdit { indels } } pub fn invalidates_offset(&self, offset: TextSize) -> bool { self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset)) } } + +fn check_disjoint(indels: &mut [impl std::borrow::Borrow]) -> bool { + indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end())); + indels + .iter() + .zip(indels.iter().skip(1)) + .all(|(l, r)| l.borrow().delete.end() <= r.borrow().delete.start()) +} -- cgit v1.2.3