From 6dafc13f5f776980cd2560fb79d3d4790811c96d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 12 Aug 2020 17:03:06 +0200 Subject: Rename ra_text_edit -> text_edit --- crates/text_edit/Cargo.toml | 12 +++ crates/text_edit/src/lib.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 crates/text_edit/Cargo.toml create mode 100644 crates/text_edit/src/lib.rs (limited to 'crates/text_edit') diff --git a/crates/text_edit/Cargo.toml b/crates/text_edit/Cargo.toml new file mode 100644 index 000000000..a69b1ef2b --- /dev/null +++ b/crates/text_edit/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "text_edit" +version = "0.0.0" +license = "MIT OR Apache-2.0" +authors = ["rust-analyzer developers"] +edition = "2018" + +[lib] +doctest = false + +[dependencies] +text-size = "1.0.0" diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs new file mode 100644 index 000000000..ab8cd7fd1 --- /dev/null +++ b/crates/text_edit/src/lib.rs @@ -0,0 +1,186 @@ +//! 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. +pub use text_size::{TextRange, TextSize}; + +/// `InsertDelete` -- a single "atomic" change to text +/// +/// Must not overlap with other `InDel`s +#[derive(Debug, Clone)] +pub struct Indel { + pub insert: String, + /// Refers to offsets in the original text + pub delete: TextRange, +} + +#[derive(Default, Debug, Clone)] +pub struct TextEdit { + indels: Vec, +} + +#[derive(Debug, Default, Clone)] +pub struct TextEditBuilder { + indels: Vec, +} + +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, text: &mut String) { + let start: usize = self.delete.start().into(); + let end: usize = self.delete.end().into(); + text.replace_range(start..end, &self.insert); + } +} + +impl TextEdit { + pub fn builder() -> TextEditBuilder { + TextEditBuilder::default() + } + + pub fn insert(offset: TextSize, text: String) -> TextEdit { + let mut builder = TextEdit::builder(); + builder.insert(offset, text); + builder.finish() + } + + pub fn delete(range: TextRange) -> TextEdit { + let mut builder = TextEdit::builder(); + builder.delete(range); + builder.finish() + } + + pub fn replace(range: TextRange, replace_with: String) -> TextEdit { + let mut builder = TextEdit::builder(); + builder.replace(range, replace_with); + builder.finish() + } + + pub fn len(&self) -> usize { + self.indels.len() + } + + pub fn is_empty(&self) -> bool { + self.indels.is_empty() + } + + pub fn iter(&self) -> std::slice::Iter<'_, Indel> { + self.into_iter() + } + + pub fn apply(&self, text: &mut String) { + match self.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(); + } + 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); + + // FIXME: figure out a way to mutate the text in-place or reuse the + // memory in some other way + *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() { + 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 IntoIterator for TextEdit { + type Item = Indel; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.indels.into_iter() + } +} + +impl<'a> IntoIterator for &'a TextEdit { + type Item = &'a Indel; + type IntoIter = std::slice::Iter<'a, Indel>; + + fn into_iter(self) -> Self::IntoIter { + self.indels.iter() + } +} + +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 { + 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