From c20086dca23cb98fe31451e427c0a54880d1381b Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 24 Jun 2021 18:57:57 -0400 Subject: [PATCH 01/24] reloading functionality --- Cargo.lock | 7 +++ helix-view/Cargo.toml | 1 + helix-view/src/document.rs | 104 +++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2a90b539cc15..8d9d5d64e91c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,7 @@ dependencies = [ "log", "once_cell", "serde", + "similar", "slotmap", "tokio", "toml", @@ -849,6 +850,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" + [[package]] name = "slab" version = "0.4.3" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index cadbbdbd70ea..66fff3a1d5ad 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -33,6 +33,7 @@ slotmap = "1" encoding_rs = "0.8" chardetng = "0.1" +similar = "1.3" serde = { version = "1.0", features = ["derive"] } toml = "0.5" diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index f26e9c1a0303..ced0e0edd247 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -550,6 +550,110 @@ impl Document { } } + /// Reload buffer + /// + /// If `force` is true, then it will attempt to reload from the source file, + /// otherwise it will reload the buffer as-is, which is helpful when you need + /// to preserve changes. + pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> { + use helix_core::Change; + use similar::DiffableStr; + + let encoding = &self.encoding; + let path = match self.path() { + Some(path) => path, + None => return Err(anyhow::anyhow!("can't find file to reload from")), + }; + + if path.exists() { + let mut file = std::fs::File::open(path.clone())?; + let (rope, ..) = from_reader(&mut file, Some(encoding))?; + + let old = self.text().to_string(); + let new = rope.to_string(); + + // `similar` diffs don't return byte or char offsets, so we need + // to map it back. Instead, we're tokenizing it ahead of time and + // then comparing the slices to make it more readable and allocate + // less. + let old_words = old.tokenize_lines(); + let new_words = new.tokenize_lines(); + + let diff = similar::TextDiff::from_slices(&old_words, &new_words); + let changes: Vec = diff + .ops() + .iter() + .filter_map(|op| { + // `range_old` and `range_old` are ranges that map to `old_words` + // and `new_words`. We need to map them back to the source `String`. + let (tag, range_old, range_new) = op.as_tag_tuple(); + let (start, end) = { + let origin = old.as_ptr(); + let words = &old_words[range_old]; + let start = words[0]; + let end = *words.last().unwrap(); + + // Always safe because the slices were already created safely. + let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; + let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } + as usize + + end.len(); + + // We need to convert from byte indices to char indices since that's what + // `ChangeSet` operates on. + ( + self.text().byte_to_char(start_offset), + self.text().byte_to_char(start_offset + between_offset), + ) + }; + + match tag { + similar::DiffTag::Insert | similar::DiffTag::Replace => { + let words = &new_words[range_new]; + // Because `words` is a slice of slices, we need to concat them + // back into one `&str`. We're using `unsafe` to avoid an allocation + // that would happen if we were using the `std::slice::concat()`. + let text: &str = { + let origin = new.as_ptr(); + let start = words[0]; + let end = *words.last().unwrap(); + + // Always safe because the slices were already created safely. + let start_offset = + unsafe { start.as_ptr().offset_from(origin) } as usize; + let between_offset = + unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize + + end.len(); + &new[start_offset..start_offset + between_offset] + }; + + Some((start, end, Some(text.into()))) + } + similar::DiffTag::Delete => Some((start, end, None)), + similar::DiffTag::Equal => None, + } + }) + .collect(); + + let transaction = Transaction::change(self.text(), changes.into_iter()); + self.apply(&transaction, view_id); + self.append_changes_to_history(view_id); + } else { + return Err(anyhow::anyhow!("can't find file to reload from")); + } + + Ok(()) + } + + pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> { + match encoding_rs::Encoding::for_label(label.as_bytes()) { + Some(encoding) => self.encoding = encoding, + None => return Err(anyhow::anyhow!("unknown encoding")), + } + + Ok(()) + } + fn detect_indent_style(&mut self) { // Build a histogram of the indentation *increases* between // subsequent lines, ignoring lines that are all whitespace. From 75a7d66e8157485c19b3510d4500a22dac5a9359 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 24 Jun 2021 19:09:57 -0400 Subject: [PATCH 02/24] fn with_newline_eof() --- helix-view/src/document.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index ced0e0edd247..70d78a2c4164 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -306,6 +306,18 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( Ok(()) } +pub fn with_newline_eof(rope: &mut Rope) -> LineEnding { + // search for line endings + let line_ending = auto_detect_line_ending(&rope).unwrap_or(DEFAULT_LINE_ENDING); + + // add missing newline at the end of file + if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) { + rope.insert(rope.len_chars(), line_ending.as_str()); + } + + line_ending +} + /// Like std::mem::replace() except it allows the replacement value to be mapped from the /// original value. fn take_with(mut_ref: &mut T, closure: F) @@ -448,15 +460,8 @@ impl Document { let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; let (mut rope, encoding) = from_reader(&mut file, encoding)?; - - // search for line endings - let line_ending = auto_detect_line_ending(&rope).unwrap_or(DEFAULT_LINE_ENDING); - - // add missing newline at the end of file - if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) { - rope.insert(rope.len_chars(), line_ending.as_str()); - } - + let line_ending = with_newline_eof(&mut rope); + let mut doc = Self::from(rope, Some(encoding)); // set the path and try detecting the language @@ -565,12 +570,14 @@ impl Document { None => return Err(anyhow::anyhow!("can't find file to reload from")), }; - if path.exists() { - let mut file = std::fs::File::open(path.clone())?; - let (rope, ..) = from_reader(&mut file, Some(encoding))?; - + if path.exists() { let old = self.text().to_string(); - let new = rope.to_string(); + let new = { + let mut file = std::fs::File::open(path.clone())?; + let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; + with_newline_eof(&mut rope); + rope.to_string() + }; // `similar` diffs don't return byte or char offsets, so we need // to map it back. Instead, we're tokenizing it ahead of time and From 457b151721887b35e8d632ebcac821408112cae7 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 24 Jun 2021 19:12:31 -0400 Subject: [PATCH 03/24] fmt --- helix-view/src/document.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 70d78a2c4164..fdb8af64c53c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -461,7 +461,7 @@ impl Document { let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; let (mut rope, encoding) = from_reader(&mut file, encoding)?; let line_ending = with_newline_eof(&mut rope); - + let mut doc = Self::from(rope, Some(encoding)); // set the path and try detecting the language @@ -570,7 +570,7 @@ impl Document { None => return Err(anyhow::anyhow!("can't find file to reload from")), }; - if path.exists() { + if path.exists() { let old = self.text().to_string(); let new = { let mut file = std::fs::File::open(path.clone())?; From 116b397bdb49fe5cb99f9bcd4f6b3d556de90c58 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 25 Jun 2021 20:32:01 -0400 Subject: [PATCH 04/24] wip --- helix-term/src/commands.rs | 33 +++++++++++++++++++++++++++++++++ helix-view/src/document.rs | 9 ++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d48a209956d5..011b77a01c03 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1459,6 +1459,25 @@ mod cmd { } } + fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + let (view, doc) = current!(cx.editor); + if let Some(label) = args.first() { + doc.set_encoding(label) + .and_then(|_| doc.reload(view.id)) + .unwrap_or_else(|e| { + cx.editor.set_error(e.to_string()); + }); + } else { + let encoding = doc.encoding().name().to_string(); + cx.editor.set_status(encoding) + } + } + + fn reload(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + let (view, doc) = current!(cx.editor); + doc.reload(view.id).unwrap(); + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -1642,6 +1661,20 @@ mod cmd { fun: show_current_directory, completer: None, }, + TypableCommand { + name: "encoding", + alias: None, + doc: "Set encoding based on `https://encoding.spec.whatwg.org`", + fun: set_encoding, + completer: None, + }, + TypableCommand { + name: "reload", + alias: None, + doc: "Discard changes and reload from the source file.", + fun: reload, + completer: None, + } ]; pub static COMMANDS: Lazy> = Lazy::new(|| { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index fdb8af64c53c..65706e4b15f5 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -586,7 +586,10 @@ impl Document { let old_words = old.tokenize_lines(); let new_words = new.tokenize_lines(); - let diff = similar::TextDiff::from_slices(&old_words, &new_words); + let mut config = similar::TextDiff::configure(); + config.timeout(std::time::Duration::new(10, 0)); + + let diff = config.diff_slices(&old_words, &new_words); let changes: Vec = diff .ops() .iter() @@ -661,6 +664,10 @@ impl Document { Ok(()) } + pub fn encoding(&self) -> &'static encoding_rs::Encoding { + self.encoding + } + fn detect_indent_style(&mut self) { // Build a histogram of the indentation *increases* between // subsequent lines, ignoring lines that are all whitespace. From 802b800e2c5f517348a7ada5c9dd178068f66714 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 25 Jun 2021 21:06:48 -0400 Subject: [PATCH 05/24] wip --- Cargo.lock | 1 + helix-view/Cargo.toml | 1 + helix-view/src/document.rs | 159 ++++++++++++++++++------------------- 3 files changed, 80 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d9d5d64e91c..cb00de6b3bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,7 @@ dependencies = [ "helix-tui", "log", "once_cell", + "ropey", "serde", "similar", "slotmap", diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 66fff3a1d5ad..b07e62b2423e 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -34,6 +34,7 @@ slotmap = "1" encoding_rs = "0.8" chardetng = "0.1" similar = "1.3" +ropey = "1.3" serde = { version = "1.0", features = ["derive"] } toml = "0.5" diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 65706e4b15f5..b5181616162b 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -565,92 +565,89 @@ impl Document { use similar::DiffableStr; let encoding = &self.encoding; - let path = match self.path() { - Some(path) => path, - None => return Err(anyhow::anyhow!("can't find file to reload from")), + let path = self.path().filter(|path| path.exists()); + + if path.is_none() { + return Err(anyhow!("can't find file to reload from")); + } + + let from = self.text().to_string(); + let into = { + let mut file = std::fs::File::open(path.unwrap())?; + let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; + with_newline_eof(&mut rope); + rope.to_string() }; - if path.exists() { - let old = self.text().to_string(); - let new = { - let mut file = std::fs::File::open(path.clone())?; - let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; - with_newline_eof(&mut rope); - rope.to_string() - }; + let old = from.tokenize_lines(); + let new = into.tokenize_lines(); - // `similar` diffs don't return byte or char offsets, so we need - // to map it back. Instead, we're tokenizing it ahead of time and - // then comparing the slices to make it more readable and allocate - // less. - let old_words = old.tokenize_lines(); - let new_words = new.tokenize_lines(); - - let mut config = similar::TextDiff::configure(); - config.timeout(std::time::Duration::new(10, 0)); - - let diff = config.diff_slices(&old_words, &new_words); - let changes: Vec = diff - .ops() - .iter() - .filter_map(|op| { - // `range_old` and `range_old` are ranges that map to `old_words` - // and `new_words`. We need to map them back to the source `String`. - let (tag, range_old, range_new) = op.as_tag_tuple(); - let (start, end) = { - let origin = old.as_ptr(); - let words = &old_words[range_old]; - let start = words[0]; - let end = *words.last().unwrap(); - - // Always safe because the slices were already created safely. - let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; - let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } - as usize - + end.len(); - - // We need to convert from byte indices to char indices since that's what - // `ChangeSet` operates on. - ( - self.text().byte_to_char(start_offset), - self.text().byte_to_char(start_offset + between_offset), - ) - }; - - match tag { - similar::DiffTag::Insert | similar::DiffTag::Replace => { - let words = &new_words[range_new]; - // Because `words` is a slice of slices, we need to concat them - // back into one `&str`. We're using `unsafe` to avoid an allocation - // that would happen if we were using the `std::slice::concat()`. - let text: &str = { - let origin = new.as_ptr(); - let start = words[0]; - let end = *words.last().unwrap(); - - // Always safe because the slices were already created safely. - let start_offset = - unsafe { start.as_ptr().offset_from(origin) } as usize; - let between_offset = - unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize - + end.len(); - &new[start_offset..start_offset + between_offset] - }; - - Some((start, end, Some(text.into()))) - } - similar::DiffTag::Delete => Some((start, end, None)), - similar::DiffTag::Equal => None, + // `similar` diffs don't return byte or char offsets, so we need + // to map it back. Instead, we're tokenizing it ahead of time and + // then comparing the slices to make it more readable and allocate + // less. + + let mut config = similar::TextDiff::configure(); + config.timeout(std::time::Duration::new(10, 0)); + + let diff = config.diff_slices(&old, &new); + let changes: Vec = diff + .ops() + .iter() + .filter_map(|op| { + // `range_old` and `range_old` are ranges that map to `old_words` + // and `new_words`. We need to map them back to the source `String`. + let (tag, from_range, into_range) = op.as_tag_tuple(); + let (start, end) = { + let origin = from.as_ptr(); + let slices = &old[from_range]; + let start = slices[0]; + let end = *slices.last().unwrap(); + + // Always safe because the slices were already created safely. + let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; + let between_offset = + unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize + end.len(); + + // We need to convert from byte indices to char indices since that's what + // `ChangeSet` operates on. + ( + ropey::str_utils::byte_to_char_idx(&from, start_offset), + ropey::str_utils::byte_to_char_idx(&from, start_offset + between_offset), + ) + }; + + match tag { + similar::DiffTag::Insert | similar::DiffTag::Replace => { + // Because `words` is a slice of slices, we need to concat them + // back into one `&str`. We're using `unsafe` to avoid an allocation + // that would happen if we were using the `std::slice::concat()`. + let text = { + let origin = into.as_ptr(); + let slices = &new[into_range]; + let start = slices[0]; + let end = *slices.last().unwrap(); + + // Always safe because the slices were already created safely. + let start_offset = + unsafe { start.as_ptr().offset_from(origin) } as usize; + let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } + as usize + + end.len(); + &into[start_offset..start_offset + between_offset] + }; + + Some((start, end, Some(text.into()))) } - }) - .collect(); + similar::DiffTag::Delete => Some((start, end, None)), + similar::DiffTag::Equal => None, + } + }) + .collect(); - let transaction = Transaction::change(self.text(), changes.into_iter()); - self.apply(&transaction, view_id); - self.append_changes_to_history(view_id); - } else { - return Err(anyhow::anyhow!("can't find file to reload from")); - } + let transaction = Transaction::change(self.text(), changes.into_iter()); + self.apply(&transaction, view_id); + self.append_changes_to_history(view_id); Ok(()) } From e92df34b8a94f7615379dafba181583c95639ecd Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 25 Jun 2021 23:08:35 -0400 Subject: [PATCH 06/24] wip --- helix-view/src/document.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index b5181616162b..53c2816a3495 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -571,13 +571,12 @@ impl Document { return Err(anyhow!("can't find file to reload from")); } + let mut file = std::fs::File::open(path.unwrap())?; + let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; + with_newline_eof(&mut rope); + let from = self.text().to_string(); - let into = { - let mut file = std::fs::File::open(path.unwrap())?; - let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; - with_newline_eof(&mut rope); - rope.to_string() - }; + let into = rope.to_string(); let old = from.tokenize_lines(); let new = into.tokenize_lines(); @@ -590,7 +589,7 @@ impl Document { let mut config = similar::TextDiff::configure(); config.timeout(std::time::Duration::new(10, 0)); - let diff = config.diff_slices(&old, &new); + let diff = config.diff_lines(&from, &into); let changes: Vec = diff .ops() .iter() @@ -605,6 +604,8 @@ impl Document { let end = *slices.last().unwrap(); // Always safe because the slices were already created safely. + // let start_offset = ropey::str_utils::line_to_byte_idx(&from, from_range.start); + // let between_offset = ropey::str_utils::line_to_byte_idx(&from, from_range.end); let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize + end.len(); @@ -619,6 +620,7 @@ impl Document { match tag { similar::DiffTag::Insert | similar::DiffTag::Replace => { + // let text: String = into.lines().skip(into_range.start).take(into_range.end - into_range.start).collect(); // Because `words` is a slice of slices, we need to concat them // back into one `&str`. We're using `unsafe` to avoid an allocation // that would happen if we were using the `std::slice::concat()`. @@ -636,7 +638,6 @@ impl Document { + end.len(); &into[start_offset..start_offset + between_offset] }; - Some((start, end, Some(text.into()))) } similar::DiffTag::Delete => Some((start, end, None)), From 004fb71c7b086e1b5e8910e23a6f1619b65a7ed6 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 25 Jun 2021 23:09:55 -0400 Subject: [PATCH 07/24] wip --- helix-view/src/document.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 53c2816a3495..3500982ad2f1 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -604,8 +604,6 @@ impl Document { let end = *slices.last().unwrap(); // Always safe because the slices were already created safely. - // let start_offset = ropey::str_utils::line_to_byte_idx(&from, from_range.start); - // let between_offset = ropey::str_utils::line_to_byte_idx(&from, from_range.end); let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize + end.len(); @@ -620,7 +618,6 @@ impl Document { match tag { similar::DiffTag::Insert | similar::DiffTag::Replace => { - // let text: String = into.lines().skip(into_range.start).take(into_range.end - into_range.start).collect(); // Because `words` is a slice of slices, we need to concat them // back into one `&str`. We're using `unsafe` to avoid an allocation // that would happen if we were using the `std::slice::concat()`. From afea39dbba8e70840b53ebf9f44807bf6f249820 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Sun, 27 Jun 2021 16:50:01 -0400 Subject: [PATCH 08/24] moved to core, added simd feature for encoding_rs --- Cargo.lock | 20 +++++++++- helix-core/Cargo.toml | 2 + helix-core/src/diff.rs | 34 ++++++++++++++++ helix-core/src/lib.rs | 1 + helix-view/Cargo.toml | 3 +- helix-view/src/document.rs | 81 +++----------------------------------- 6 files changed, 61 insertions(+), 80 deletions(-) create mode 100644 helix-core/src/diff.rs diff --git a/Cargo.lock b/Cargo.lock index cb00de6b3bbc..14fcac24a286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ "cfg-if 1.0.0", + "packed_simd_2", ] [[package]] @@ -300,6 +301,7 @@ dependencies = [ "ropey", "rust-embed", "serde", + "similar", "smallvec", "tendril", "toml", @@ -390,9 +392,7 @@ dependencies = [ "helix-tui", "log", "once_cell", - "ropey", "serde", - "similar", "slotmap", "tokio", "toml", @@ -487,6 +487,12 @@ version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "lock_api" version = "0.4.4" @@ -608,6 +614,16 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +[[package]] +name = "packed_simd_2" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e64858a2d3733fdd61adfdd6da89aa202f7ff0e741d2fc7ed1e452ba9dc99d7" +dependencies = [ + "cfg-if 0.1.10", + "libm", +] + [[package]] name = "parking_lot" version = "0.11.1" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index bab062e13f3b..b24ef3486ff6 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -31,5 +31,7 @@ regex = "1" serde = { version = "1.0", features = ["derive"] } toml = "0.5" +similar = "1.3" + etcetera = "0.3" rust-embed = { version = "5.9.0", optional = true } diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs new file mode 100644 index 000000000000..d6eafdc8f67b --- /dev/null +++ b/helix-core/src/diff.rs @@ -0,0 +1,34 @@ +use ropey::Rope; + +use crate::{Change, Transaction}; + +pub fn diff_ropes(old: &Rope, new: &Rope) -> Transaction { + let old_into_string = old.to_string(); + let new_into_string = new.to_string(); + + let mut config = similar::TextDiff::configure(); + config.timeout(std::time::Duration::new(2, 0)); + + let diff = config.diff_chars(&old_into_string, &new_into_string); + let changes: Vec = diff + .ops() + .iter() + .filter_map(|op| { + let (tag, old_range, new_range) = op.as_tag_tuple(); + match tag { + similar::DiffTag::Insert | similar::DiffTag::Replace => { + let text: &str = { + let start = new.char_to_byte(new_range.start); + let end = new.char_to_byte(new_range.end); + &new_into_string[start..end] + }; + Some((old_range.start, old_range.end, Some(text.into()))) + } + similar::DiffTag::Delete => Some((old_range.start, old_range.end, None)), + similar::DiffTag::Equal => None, + } + }) + .collect(); + + Transaction::change(old, changes.into_iter()) +} diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index ae135b00faf8..024f18ad2fc8 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -3,6 +3,7 @@ pub mod auto_pairs; pub mod chars; pub mod comment; pub mod diagnostic; +pub mod diff; pub mod graphemes; pub mod history; pub mod indent; diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index b07e62b2423e..c747d3a6d1b8 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -13,6 +13,7 @@ homepage = "https://helix-editor.com" [features] term = ["tui", "crossterm"] +simd = ["encoding_rs/simd-accel"] default = ["term"] [dependencies] @@ -33,8 +34,6 @@ slotmap = "1" encoding_rs = "0.8" chardetng = "0.1" -similar = "1.3" -ropey = "1.3" serde = { version = "1.0", features = ["derive"] } toml = "0.5" diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 3500982ad2f1..742accb18b82 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -306,9 +306,9 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( Ok(()) } -pub fn with_newline_eof(rope: &mut Rope) -> LineEnding { +pub fn with_line_ending(rope: &mut Rope) -> LineEnding { // search for line endings - let line_ending = auto_detect_line_ending(&rope).unwrap_or(DEFAULT_LINE_ENDING); + let line_ending = auto_detect_line_ending(rope).unwrap_or(DEFAULT_LINE_ENDING); // add missing newline at the end of file if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) { @@ -460,7 +460,7 @@ impl Document { let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; let (mut rope, encoding) = from_reader(&mut file, encoding)?; - let line_ending = with_newline_eof(&mut rope); + let line_ending = with_line_ending(&mut rope); let mut doc = Self::from(rope, Some(encoding)); @@ -561,9 +561,6 @@ impl Document { /// otherwise it will reload the buffer as-is, which is helpful when you need /// to preserve changes. pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> { - use helix_core::Change; - use similar::DiffableStr; - let encoding = &self.encoding; let path = self.path().filter(|path| path.exists()); @@ -573,77 +570,9 @@ impl Document { let mut file = std::fs::File::open(path.unwrap())?; let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; - with_newline_eof(&mut rope); - - let from = self.text().to_string(); - let into = rope.to_string(); - - let old = from.tokenize_lines(); - let new = into.tokenize_lines(); - - // `similar` diffs don't return byte or char offsets, so we need - // to map it back. Instead, we're tokenizing it ahead of time and - // then comparing the slices to make it more readable and allocate - // less. - - let mut config = similar::TextDiff::configure(); - config.timeout(std::time::Duration::new(10, 0)); - - let diff = config.diff_lines(&from, &into); - let changes: Vec = diff - .ops() - .iter() - .filter_map(|op| { - // `range_old` and `range_old` are ranges that map to `old_words` - // and `new_words`. We need to map them back to the source `String`. - let (tag, from_range, into_range) = op.as_tag_tuple(); - let (start, end) = { - let origin = from.as_ptr(); - let slices = &old[from_range]; - let start = slices[0]; - let end = *slices.last().unwrap(); - - // Always safe because the slices were already created safely. - let start_offset = unsafe { start.as_ptr().offset_from(origin) } as usize; - let between_offset = - unsafe { end.as_ptr().offset_from(start.as_ptr()) } as usize + end.len(); - - // We need to convert from byte indices to char indices since that's what - // `ChangeSet` operates on. - ( - ropey::str_utils::byte_to_char_idx(&from, start_offset), - ropey::str_utils::byte_to_char_idx(&from, start_offset + between_offset), - ) - }; - - match tag { - similar::DiffTag::Insert | similar::DiffTag::Replace => { - // Because `words` is a slice of slices, we need to concat them - // back into one `&str`. We're using `unsafe` to avoid an allocation - // that would happen if we were using the `std::slice::concat()`. - let text = { - let origin = into.as_ptr(); - let slices = &new[into_range]; - let start = slices[0]; - let end = *slices.last().unwrap(); - - // Always safe because the slices were already created safely. - let start_offset = - unsafe { start.as_ptr().offset_from(origin) } as usize; - let between_offset = unsafe { end.as_ptr().offset_from(start.as_ptr()) } - as usize - + end.len(); - &into[start_offset..start_offset + between_offset] - }; - Some((start, end, Some(text.into()))) - } - similar::DiffTag::Delete => Some((start, end, None)), - similar::DiffTag::Equal => None, - } - }) - .collect(); + with_line_ending(&mut rope); - let transaction = Transaction::change(self.text(), changes.into_iter()); + let transaction = helix_core::diff::diff_ropes(self.text(), &rope); self.apply(&transaction, view_id); self.append_changes_to_history(view_id); From e109c9caf2cb5624b36f0a40c7600c0aed3d7375 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Sun, 27 Jun 2021 19:23:56 -0400 Subject: [PATCH 09/24] wip --- Cargo.lock | 153 +++++++++++++++++++++++ bool | 0 helix-core/Cargo.toml | 3 + helix-core/proptest-regressions/diff.txt | 7 ++ helix-core/src/diff.rs | 40 +++++- helix-term/src/commands.rs | 4 +- helix-view/src/document.rs | 2 +- 7 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 bool create mode 100644 helix-core/proptest-regressions/diff.txt diff --git a/Cargo.lock b/Cargo.lock index e4717f95efc0..64e2f7252e80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.2.1" @@ -44,6 +59,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.0.1" @@ -297,6 +318,7 @@ dependencies = [ "etcetera", "helix-syntax", "once_cell", + "proptest", "regex", "ropey", "rust-embed", @@ -668,6 +690,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.27" @@ -677,6 +705,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + [[package]] name = "pulldown-cmark" version = "0.8.0" @@ -688,6 +736,18 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.9" @@ -697,6 +757,55 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.9" @@ -733,6 +842,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ropey" version = "1.3.1" @@ -775,6 +893,18 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.5" @@ -906,6 +1036,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "tendril" version = "0.4.2" @@ -1107,6 +1251,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" diff --git a/bool b/bool new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index ad9d4c56bd65..98192a111aa7 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -35,3 +35,6 @@ similar = "1.3" etcetera = "0.3" rust-embed = { version = "5.9.0", optional = true } + +[dev-dependencies] +proptest = "1" diff --git a/helix-core/proptest-regressions/diff.txt b/helix-core/proptest-regressions/diff.txt new file mode 100644 index 000000000000..1e3ee9a4b38a --- /dev/null +++ b/helix-core/proptest-regressions/diff.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc d7e238338c08e8398f9c17513a665e3f266486bdb41e2a49963ab2c3cc4a2eca # shrinks to a = "®A¥꣎ AaAaaa ꯰ Ⱥ`𞴁", b = "00𑴀ᚠ𔐀ꀀa¯𞹡ਵ0!𐇐Σ\u{1d17b}¥ȺȺ`૦" diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index d6eafdc8f67b..61b1367a9436 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -2,25 +2,40 @@ use ropey::Rope; use crate::{Change, Transaction}; -pub fn diff_ropes(old: &Rope, new: &Rope) -> Transaction { - let old_into_string = old.to_string(); - let new_into_string = new.to_string(); +/// Compares `old` and `new` to generate a [`Transaction`] describing +/// the steps required to get from `old` to `new`. +pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { + // `similar` only works on contiguous data, so a `Rope` has + // to be temporarily converted into a `String` until the diff + // is created. + let old_as_string = old.to_string(); + let new_as_string = new.to_string(); + // A timeout is set so after 2 seconds, the algorithm will start + // approximating. This is especially important for big `Rope`s or + // `Rope`s that are extremely dissimilar so the diff will be + // created in a reasonable amount of time. let mut config = similar::TextDiff::configure(); config.timeout(std::time::Duration::new(2, 0)); - let diff = config.diff_chars(&old_into_string, &new_into_string); + // Note: Ignore the clippy warning, as the trait bounds of + // `Transaction::change()` require an iterator implementing + // `ExactIterator`. + let diff = config.diff_chars(&old_as_string, &new_as_string); let changes: Vec = diff .ops() .iter() .filter_map(|op| { let (tag, old_range, new_range) = op.as_tag_tuple(); match tag { + // Semantically, inserts and replacements are the same thing. similar::DiffTag::Insert | similar::DiffTag::Replace => { + // This is the text from the `new` rope that should be + // inserted into `old`. let text: &str = { let start = new.char_to_byte(new_range.start); let end = new.char_to_byte(new_range.end); - &new_into_string[start..end] + &new_as_string[start..end] }; Some((old_range.start, old_range.end, Some(text.into()))) } @@ -32,3 +47,18 @@ pub fn diff_ropes(old: &Rope, new: &Rope) -> Transaction { Transaction::change(old, changes.into_iter()) } + +#[cfg(test)] +mod tests{ + use super::*; + + proptest::proptest! { + #[test] + fn test_compare_ropes(a: String, b: String) { + let mut old = Rope::from(a); + let new = Rope::from(b); + compare_ropes(&old, &new).apply(&mut old); + proptest::prop_assert_eq!(old.to_string(), new.to_string()) + } + } +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a817a8ba2e45..428c7de69f2f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1469,9 +1469,7 @@ mod cmd { if let Some(label) = args.first() { doc.set_encoding(label) .and_then(|_| doc.reload(view.id)) - .unwrap_or_else(|e| { - cx.editor.set_error(e.to_string()); - }); + .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); } else { let encoding = doc.encoding().name().to_string(); cx.editor.set_status(encoding) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 55b2a00d8b8e..c0768aa11dea 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -573,7 +573,7 @@ impl Document { let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; with_line_ending(&mut rope); - let transaction = helix_core::diff::diff_ropes(self.text(), &rope); + let transaction = helix_core::diff::compare_ropes(self.text(), &rope); self.apply(&transaction, view_id); self.append_changes_to_history(view_id); From 259d8047b20c66fc5ae3ba4b90f1860f75c62af5 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Sun, 27 Jun 2021 19:26:07 -0400 Subject: [PATCH 10/24] rm --- .gitignore | 1 + bool | 0 helix-core/proptest-regressions/diff.txt | 7 ------- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 bool delete mode 100644 helix-core/proptest-regressions/diff.txt diff --git a/.gitignore b/.gitignore index 1a42b4409240..0551e0e0b5f3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target helix-term/rustfmt.toml helix-syntax/languages/ result + diff --git a/bool b/bool deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/helix-core/proptest-regressions/diff.txt b/helix-core/proptest-regressions/diff.txt deleted file mode 100644 index 1e3ee9a4b38a..000000000000 --- a/helix-core/proptest-regressions/diff.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc d7e238338c08e8398f9c17513a665e3f266486bdb41e2a49963ab2c3cc4a2eca # shrinks to a = "®A¥꣎ AaAaaa ꯰ Ⱥ`𞴁", b = "00𑴀ᚠ𔐀ꀀa¯𞹡ਵ0!𐇐Σ\u{1d17b}¥ȺȺ`૦" From f28bfed5f2a904e0c773eb213d8fc2173525e7de Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Sun, 27 Jun 2021 19:26:21 -0400 Subject: [PATCH 11/24] .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0551e0e0b5f3..09ec843cfa71 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ target helix-term/rustfmt.toml helix-syntax/languages/ result - +bool +helix-core/proptest-regressions From e35ccd2f503660c3419a6990c33e41c605b4b0b1 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Sun, 27 Jun 2021 20:14:11 -0400 Subject: [PATCH 12/24] wip --- helix-core/src/diff.rs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index 61b1367a9436..e7de34bd08f8 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -8,22 +8,28 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { // `similar` only works on contiguous data, so a `Rope` has // to be temporarily converted into a `String` until the diff // is created. - let old_as_string = old.to_string(); - let new_as_string = new.to_string(); + let old_as_chars: Vec<_> = old.chars().collect(); + let new_as_chars: Vec<_> = new.chars().collect(); // A timeout is set so after 2 seconds, the algorithm will start // approximating. This is especially important for big `Rope`s or // `Rope`s that are extremely dissimilar so the diff will be // created in a reasonable amount of time. - let mut config = similar::TextDiff::configure(); - config.timeout(std::time::Duration::new(2, 0)); + // let mut config = similar::TextDiff::configure(); + // config.timeout(std::time::Duration::new(2, 0)); // Note: Ignore the clippy warning, as the trait bounds of // `Transaction::change()` require an iterator implementing // `ExactIterator`. - let diff = config.diff_chars(&old_as_string, &new_as_string); + + let time = std::time::Instant::now() + std::time::Duration::new(10, 0); + let diff = similar::capture_diff_slices_deadline( + similar::Algorithm::Myers, + &old_as_chars, + &new_as_chars, + Some(time), + ); let changes: Vec = diff - .ops() .iter() .filter_map(|op| { let (tag, old_range, new_range) = op.as_tag_tuple(); @@ -32,11 +38,7 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { similar::DiffTag::Insert | similar::DiffTag::Replace => { // This is the text from the `new` rope that should be // inserted into `old`. - let text: &str = { - let start = new.char_to_byte(new_range.start); - let end = new.char_to_byte(new_range.end); - &new_as_string[start..end] - }; + let text: String = new_as_chars[new_range].iter().collect(); Some((old_range.start, old_range.end, Some(text.into()))) } similar::DiffTag::Delete => Some((old_range.start, old_range.end, None)), @@ -49,16 +51,20 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { } #[cfg(test)] -mod tests{ +mod tests { use super::*; + use proptest::prelude::*; - proptest::proptest! { + proptest! { + #![proptest_config(ProptestConfig { + cases: 10000, .. ProptestConfig::default() + })] #[test] fn test_compare_ropes(a: String, b: String) { let mut old = Rope::from(a); let new = Rope::from(b); compare_ropes(&old, &new).apply(&mut old); - proptest::prop_assert_eq!(old.to_string(), new.to_string()) + prop_assert_eq!(old.to_string(), new.to_string()) } } } From e9c894ad5508eda6c3317cf86b9da66ded975724 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Tue, 29 Jun 2021 23:41:22 -0400 Subject: [PATCH 13/24] local wip --- helix-core/derp.txt | 28 ++++++++++++++++++++++++++++ helix-core/src/diff.rs | 4 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 helix-core/derp.txt diff --git a/helix-core/derp.txt b/helix-core/derp.txt new file mode 100644 index 000000000000..fd884e9bdb29 --- /dev/null +++ b/helix-core/derp.txt @@ -0,0 +1,28 @@ +[ + Replace { + old_index: 0, + old_len: 16, + new_index: 0, + new_len: 7, + }, + Equal { + old_index: 16, + new_index: 7, + len: 1, + }, + Delete { + old_index: 17, + old_len: 1, + new_index: 12, + }, + Equal { + old_index: 18, + new_index: 8, + len: 1, + }, + Insert { + old_index: 18, + new_index: 9, + new_len: 8, + }, +] \ No newline at end of file diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index e7de34bd08f8..0c2bc353ba09 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -46,7 +46,7 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { } }) .collect(); - + std::fs::write("derp.txt", format!("{:#?}", diff)).unwrap(); Transaction::change(old, changes.into_iter()) } @@ -64,7 +64,7 @@ mod tests { let mut old = Rope::from(a); let new = Rope::from(b); compare_ropes(&old, &new).apply(&mut old); - prop_assert_eq!(old.to_string(), new.to_string()) + prop_assert_eq!(old.to_string(), new.to_string()); } } } From 39c65f1dac5a689c18bec26c0e40ca913ce9f69f Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Wed, 30 Jun 2021 23:10:09 -0400 Subject: [PATCH 14/24] wip --- helix-core/Cargo.toml | 2 +- helix-core/derp.txt | 28 ---------------------------- helix-core/src/diff.rs | 36 +++++++++++++++++------------------- 3 files changed, 18 insertions(+), 48 deletions(-) delete mode 100644 helix-core/derp.txt diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 98192a111aa7..ccaa2f133c9d 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -37,4 +37,4 @@ etcetera = "0.3" rust-embed = { version = "5.9.0", optional = true } [dev-dependencies] -proptest = "1" +quickcheck = "1" diff --git a/helix-core/derp.txt b/helix-core/derp.txt deleted file mode 100644 index fd884e9bdb29..000000000000 --- a/helix-core/derp.txt +++ /dev/null @@ -1,28 +0,0 @@ -[ - Replace { - old_index: 0, - old_len: 16, - new_index: 0, - new_len: 7, - }, - Equal { - old_index: 16, - new_index: 7, - len: 1, - }, - Delete { - old_index: 17, - old_len: 1, - new_index: 12, - }, - Equal { - old_index: 18, - new_index: 8, - len: 1, - }, - Insert { - old_index: 18, - new_index: 9, - new_len: 8, - }, -] \ No newline at end of file diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index 0c2bc353ba09..46b78cce9b95 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -11,60 +11,58 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { let old_as_chars: Vec<_> = old.chars().collect(); let new_as_chars: Vec<_> = new.chars().collect(); - // A timeout is set so after 2 seconds, the algorithm will start + // A timeout is set so after 5 seconds, the algorithm will start // approximating. This is especially important for big `Rope`s or // `Rope`s that are extremely dissimilar so the diff will be // created in a reasonable amount of time. - // let mut config = similar::TextDiff::configure(); - // config.timeout(std::time::Duration::new(2, 0)); - + // // Note: Ignore the clippy warning, as the trait bounds of // `Transaction::change()` require an iterator implementing // `ExactIterator`. - - let time = std::time::Instant::now() + std::time::Duration::new(10, 0); + let time = std::time::Instant::now() + std::time::Duration::from_secs(5); let diff = similar::capture_diff_slices_deadline( similar::Algorithm::Myers, &old_as_chars, &new_as_chars, Some(time), ); + + // The current position of the change needs to be tracked to + // construct the `Change`s. + let mut pos = 0; let changes: Vec = diff .iter() - .filter_map(|op| { - let (tag, old_range, new_range) = op.as_tag_tuple(); + .map(|op| op.as_tag_tuple()) + .filter_map(|(tag, old_range, new_range)| { + let old_pos = pos; + pos += old_range.end - old_range.start; + match tag { // Semantically, inserts and replacements are the same thing. similar::DiffTag::Insert | similar::DiffTag::Replace => { // This is the text from the `new` rope that should be // inserted into `old`. let text: String = new_as_chars[new_range].iter().collect(); - Some((old_range.start, old_range.end, Some(text.into()))) + Some((old_pos, pos, Some(text.into()))) } - similar::DiffTag::Delete => Some((old_range.start, old_range.end, None)), + similar::DiffTag::Delete => Some((old_pos, pos, None)), similar::DiffTag::Equal => None, } }) .collect(); - std::fs::write("derp.txt", format!("{:#?}", diff)).unwrap(); Transaction::change(old, changes.into_iter()) } #[cfg(test)] mod tests { use super::*; - use proptest::prelude::*; - proptest! { - #![proptest_config(ProptestConfig { - cases: 10000, .. ProptestConfig::default() - })] - #[test] - fn test_compare_ropes(a: String, b: String) { + quickcheck::quickcheck! { + fn test_compare_ropes(a: String, b: String) -> bool { let mut old = Rope::from(a); let new = Rope::from(b); compare_ropes(&old, &new).apply(&mut old); - prop_assert_eq!(old.to_string(), new.to_string()); + old.to_string() == new.to_string() } } } From 1c7b43403f08c14420535195eff8b98dfd7b7f50 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Wed, 30 Jun 2021 23:14:56 -0400 Subject: [PATCH 15/24] wip --- .gitignore | 2 - Cargo.lock | 151 +++++++---------------------------------------------- 2 files changed, 19 insertions(+), 134 deletions(-) diff --git a/.gitignore b/.gitignore index 09ec843cfa71..1a42b4409240 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,3 @@ target helix-term/rustfmt.toml helix-syntax/languages/ result -bool -helix-core/proptest-regressions diff --git a/Cargo.lock b/Cargo.lock index 7dd4e0d27615..bfc03c33eef8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,21 +29,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" version = "1.2.1" @@ -59,12 +44,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "bytes" version = "1.0.1" @@ -205,6 +184,16 @@ dependencies = [ "packed_simd_2", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "error-code" version = "2.3.0" @@ -339,7 +328,7 @@ dependencies = [ "etcetera", "helix-syntax", "once_cell", - "proptest", + "quickcheck", "regex", "ropey", "rust-embed", @@ -712,12 +701,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - [[package]] name = "proc-macro2" version = "1.0.27" @@ -727,26 +710,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "proptest" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" -dependencies = [ - "bit-set", - "bitflags", - "byteorder", - "lazy_static", - "num-traits", - "quick-error 2.0.1", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", -] - [[package]] name = "pulldown-cmark" version = "0.8.0" @@ -759,16 +722,15 @@ dependencies = [ ] [[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-error" -version = "2.0.1" +name = "quickcheck" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] [[package]] name = "quote" @@ -785,19 +747,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", "rand_core", ] @@ -810,24 +759,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.2.9" @@ -864,15 +795,6 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "ropey" version = "1.3.1" @@ -915,18 +837,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error 1.2.3", - "tempfile", - "wait-timeout", -] - [[package]] name = "ryu" version = "1.0.5" @@ -1064,20 +974,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi", -] - [[package]] name = "tendril" version = "0.4.2" @@ -1279,15 +1175,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.3.2" From b6a72bf7062687ad7050adf9fdb8376da595326d Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 1 Jul 2021 00:18:13 -0400 Subject: [PATCH 16/24] no features --- Cargo.lock | 12 ------------ helix-core/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfc03c33eef8..6e299f05644c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,16 +184,6 @@ dependencies = [ "packed_simd_2", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log", - "regex", -] - [[package]] name = "error-code" version = "2.3.0" @@ -727,8 +717,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "env_logger", - "log", "rand", ] diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index ccaa2f133c9d..80d559a9503a 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -37,4 +37,4 @@ etcetera = "0.3" rust-embed = { version = "5.9.0", optional = true } [dev-dependencies] -quickcheck = "1" +quickcheck = { version = "1", default-features = false } From 569443a6dcd1e7fb0f3551555f35d2f1e1413da7 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 1 Jul 2021 18:33:52 -0400 Subject: [PATCH 17/24] wip --- helix-core/src/diff.rs | 18 +++++++++++------- helix-term/src/commands.rs | 6 +++--- helix-view/src/document.rs | 4 ++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index 46b78cce9b95..48f011c7246d 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -5,11 +5,13 @@ use crate::{Change, Transaction}; /// Compares `old` and `new` to generate a [`Transaction`] describing /// the steps required to get from `old` to `new`. pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { + use std::borrow::Cow; + // `similar` only works on contiguous data, so a `Rope` has - // to be temporarily converted into a `String` until the diff + // to be temporarily converted into a vec until the diff // is created. - let old_as_chars: Vec<_> = old.chars().collect(); - let new_as_chars: Vec<_> = new.chars().collect(); + let old_vec: Vec<_> = old.lines().map(Cow::from).collect(); + let new_vec: Vec<_> = new.lines().map(Cow::from).collect(); // A timeout is set so after 5 seconds, the algorithm will start // approximating. This is especially important for big `Rope`s or @@ -22,8 +24,8 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { let time = std::time::Instant::now() + std::time::Duration::from_secs(5); let diff = similar::capture_diff_slices_deadline( similar::Algorithm::Myers, - &old_as_chars, - &new_as_chars, + &old_vec, + &new_vec, Some(time), ); @@ -34,15 +36,17 @@ pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { .iter() .map(|op| op.as_tag_tuple()) .filter_map(|(tag, old_range, new_range)| { + // `old_pos..pos` is equivalent to `start..end` for where + // the change should be applied. let old_pos = pos; - pos += old_range.end - old_range.start; + pos += old.line_to_char(old_range.end - old_range.start); match tag { // Semantically, inserts and replacements are the same thing. similar::DiffTag::Insert | similar::DiffTag::Replace => { // This is the text from the `new` rope that should be // inserted into `old`. - let text: String = new_as_chars[new_range].iter().collect(); + let text: String = new_vec[new_range].concat(); Some((old_pos, pos, Some(text.into()))) } similar::DiffTag::Delete => Some((old_pos, pos, None)), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 26fd3220d5b0..20456bfa49ae 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1514,18 +1514,18 @@ mod cmd { } } + /// Sets the [`Document`]'s encoding and [reloads](`reload()`) if possible. fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { let (view, doc) = current!(cx.editor); if let Some(label) = args.first() { - doc.set_encoding(label) - .and_then(|_| doc.reload(view.id)) - .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); + doc.set_encoding(label).and_then(|_| doc.reload(view.id)); } else { let encoding = doc.encoding().name().to_string(); cx.editor.set_status(encoding) } } + /// Reload the [`Document`] from its source file. fn reload(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { let (view, doc) = current!(cx.editor); doc.reload(view.id).unwrap(); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 20dc64c08e0d..8eb9dfb31211 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -307,6 +307,7 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( Ok(()) } +/// Inserts the final line ending into `rope` if it's missing. [Why?](https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline) pub fn with_line_ending(rope: &mut Rope) -> LineEnding { // search for line endings let line_ending = auto_detect_line_ending(rope).unwrap_or(DEFAULT_LINE_ENDING); @@ -600,6 +601,7 @@ impl Document { let encoding = &self.encoding; let path = self.path().filter(|path| path.exists()); + // If there is no path or the path no longer exists. if path.is_none() { return Err(anyhow!("can't find file to reload from")); } @@ -615,6 +617,7 @@ impl Document { Ok(()) } + /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`. pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> { match encoding_rs::Encoding::for_label(label.as_bytes()) { Some(encoding) => self.encoding = encoding, @@ -624,6 +627,7 @@ impl Document { Ok(()) } + /// Returns the [`Document`]'s current encoding. pub fn encoding(&self) -> &'static encoding_rs::Encoding { self.encoding } From 36ef5ec965d1e7c19a03fd071ecd5b06887a25ea Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 1 Jul 2021 18:35:56 -0400 Subject: [PATCH 18/24] nit --- helix-view/src/document.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 8eb9dfb31211..d914c587f62f 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -623,7 +623,6 @@ impl Document { Some(encoding) => self.encoding = encoding, None => return Err(anyhow::anyhow!("unknown encoding")), } - Ok(()) } From c43d91bd9fffc919e0160d770b0928a4a3e4140f Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Thu, 1 Jul 2021 20:04:25 -0400 Subject: [PATCH 19/24] remove simd --- Cargo.lock | 17 ----------------- helix-view/Cargo.toml | 1 - 2 files changed, 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e299f05644c..a377e2f40142 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ "cfg-if 1.0.0", - "packed_simd_2", ] [[package]] @@ -511,12 +510,6 @@ version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" -[[package]] -name = "libm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" - [[package]] name = "lock_api" version = "0.4.4" @@ -638,16 +631,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" -[[package]] -name = "packed_simd_2" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64858a2d3733fdd61adfdd6da89aa202f7ff0e741d2fc7ed1e452ba9dc99d7" -dependencies = [ - "cfg-if 0.1.10", - "libm", -] - [[package]] name = "parking_lot" version = "0.11.1" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index d379e153f6f9..cb2032de5457 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -11,7 +11,6 @@ homepage = "https://helix-editor.com" [features] default = [] -simd = ["encoding_rs/simd-accel"] term = ["crossterm"] [dependencies] From eb7692c8ec4aecb6cfc88b7eefa4a52c05943720 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 2 Jul 2021 00:01:43 -0400 Subject: [PATCH 20/24] doc --- helix-view/src/document.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index d914c587f62f..a40abeeac272 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -592,11 +592,7 @@ impl Document { } } - /// Reload buffer - /// - /// If `force` is true, then it will attempt to reload from the source file, - /// otherwise it will reload the buffer as-is, which is helpful when you need - /// to preserve changes. + /// Reload the document from its path. pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> { let encoding = &self.encoding; let path = self.path().filter(|path| path.exists()); From 81fa729d7ac57eb00b65783179c13e62560089a2 Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 2 Jul 2021 00:07:14 -0400 Subject: [PATCH 21/24] clippy --- helix-term/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 20456bfa49ae..d0410eb11ff6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1526,7 +1526,7 @@ mod cmd { } /// Reload the [`Document`] from its source file. - fn reload(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) { let (view, doc) = current!(cx.editor); doc.reload(view.id).unwrap(); } From f088ac8e46b324bd086925058cc320de8f7cdcfb Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 2 Jul 2021 00:20:01 -0400 Subject: [PATCH 22/24] clippy --- helix-term/src/commands.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 44f286987830..860d8e224cd2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1521,11 +1521,12 @@ mod cmd { } } - /// Sets the [`Document`]'s encoding and [reloads](`reload()`) if possible. + /// Sets the [`Document`]'s encoding.. fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { - let (view, doc) = current!(cx.editor); + let (_, doc) = current!(cx.editor); if let Some(label) = args.first() { - doc.set_encoding(label).and_then(|_| doc.reload(view.id)); + doc.set_encoding(label) + .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); } else { let encoding = doc.encoding().name().to_string(); cx.editor.set_status(encoding) From a2a5ee07e6456fb6deed0505dc687a7d70c369ce Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 2 Jul 2021 04:52:17 -0400 Subject: [PATCH 23/24] address comments --- helix-core/src/diff.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index 48f011c7246d..9c1fc999b6fc 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -5,48 +5,46 @@ use crate::{Change, Transaction}; /// Compares `old` and `new` to generate a [`Transaction`] describing /// the steps required to get from `old` to `new`. pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction { - use std::borrow::Cow; - // `similar` only works on contiguous data, so a `Rope` has - // to be temporarily converted into a vec until the diff - // is created. - let old_vec: Vec<_> = old.lines().map(Cow::from).collect(); - let new_vec: Vec<_> = new.lines().map(Cow::from).collect(); + // to be temporarily converted into a `String`. + let old_converted = old.to_string(); + let new_converted = new.to_string(); - // A timeout is set so after 5 seconds, the algorithm will start + // A timeout is set so after 1 seconds, the algorithm will start // approximating. This is especially important for big `Rope`s or - // `Rope`s that are extremely dissimilar so the diff will be - // created in a reasonable amount of time. + // `Rope`s that are extremely dissimilar to each other. // // Note: Ignore the clippy warning, as the trait bounds of // `Transaction::change()` require an iterator implementing // `ExactIterator`. - let time = std::time::Instant::now() + std::time::Duration::from_secs(5); - let diff = similar::capture_diff_slices_deadline( - similar::Algorithm::Myers, - &old_vec, - &new_vec, - Some(time), - ); + let mut config = similar::TextDiff::configure(); + config.timeout(std::time::Duration::from_secs(1)); + + let diff = config.diff_chars(&old_converted, &new_converted); // The current position of the change needs to be tracked to // construct the `Change`s. let mut pos = 0; let changes: Vec = diff + .ops() .iter() .map(|op| op.as_tag_tuple()) .filter_map(|(tag, old_range, new_range)| { // `old_pos..pos` is equivalent to `start..end` for where // the change should be applied. let old_pos = pos; - pos += old.line_to_char(old_range.end - old_range.start); + pos += old_range.end - old_range.start; match tag { // Semantically, inserts and replacements are the same thing. similar::DiffTag::Insert | similar::DiffTag::Replace => { // This is the text from the `new` rope that should be // inserted into `old`. - let text: String = new_vec[new_range].concat(); + let text: &str = { + let start = new.char_to_byte(new_range.start); + let end = new.char_to_byte(new_range.end); + &new_converted[start..end] + }; Some((old_pos, pos, Some(text.into()))) } similar::DiffTag::Delete => Some((old_pos, pos, None)), From ac2cacdb97d31968e42450544becd74c0b5da66c Mon Sep 17 00:00:00 2001 From: Shafkath Shuhan Date: Fri, 2 Jul 2021 05:08:23 -0400 Subject: [PATCH 24/24] add indentation & line ending change --- helix-view/src/document.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a40abeeac272..f85ded11699c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -604,12 +604,16 @@ impl Document { let mut file = std::fs::File::open(path.unwrap())?; let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; - with_line_ending(&mut rope); + let line_ending = with_line_ending(&mut rope); let transaction = helix_core::diff::compare_ropes(self.text(), &rope); self.apply(&transaction, view_id); self.append_changes_to_history(view_id); + // Detect indentation style and set line ending. + self.detect_indent_style(); + self.line_ending = line_ending; + Ok(()) }