diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 4a666fbd4160e..862ce1bb5bad8 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -189,6 +189,23 @@ impl History { Ok((last_saved_revision, history)) } + pub fn merge(&mut self, mut other: History, offset: usize) -> std::io::Result<()> { + let revisions = self.revisions.split_off(offset); + let len = other.revisions.len(); + for r in revisions { + let parent = if r.parent < offset { + r.parent + } else { + len + (r.parent - offset) + }; + other.revisions.get_mut(parent).unwrap().last_child = + NonZeroUsize::new(other.revisions.len()); + other.revisions.push(r); + } + self.revisions = other.revisions; + Ok(()) + } + pub fn read_header(reader: &mut R, path: &Path) -> std::io::Result<(usize, usize)> { let header = read_string(reader)?; if HEADER_TAG != header { @@ -259,6 +276,11 @@ impl History { self.current == 0 } + #[inline] + pub fn is_empty(&self) -> bool { + self.revisions.len() <= 1 + } + /// Returns the changes since the given revision composed into a transaction. /// Returns None if there are no changes between the current and given revisions. pub fn changes_since(&self, revision: usize) -> Option { diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index f7964162127b0..1c88cba3cd95e 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -515,10 +515,6 @@ impl Application { } }; - if doc_save_event.serialize_error { - self.editor.set_error("failed to serialize history"); - } - let doc = match self.editor.document_mut(doc_save_event.doc_id) { None => { warn!( @@ -570,6 +566,9 @@ impl Application { lines, bytes )); + if doc_save_event.serialize_error { + self.editor.set_error("failed to serialize history"); + } } #[inline(always)] diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 44f6978a893eb..2cfe3ac4053e9 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -486,6 +486,7 @@ async fn test_persistent_undo() -> anyhow::Result<()> { .with_file(file.path(), None) .build()?; + // TODO: Test if the history file is valid. test_key_sequence( &mut app, Some(&format!( diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 9861ad9e6fb41..4b248ad5e4180 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -20,6 +20,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use std::time::SystemTime; +use tokio::fs::OpenOptions; use helix_core::{ encoding, @@ -591,7 +592,7 @@ impl Document { .load() .persistent_undo .then(|| self.history.get_mut().clone()); - let undo_file = self.undo_file(Some(&path))?.unwrap(); + let undofile_path = self.undo_file(Some(&path))?.unwrap(); // We encode the file according to the `Document`'s encoding. let future = async move { use tokio::{fs, fs::File}; @@ -624,16 +625,22 @@ impl Document { if let Some(history) = history { let res = { let path = path.clone(); + let mut undofile = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(&undofile_path) + .await? + .into_std() + .await; tokio::task::spawn_blocking(move || -> anyhow::Result<()> { - use std::fs; let append = - History::read_header(&mut fs::File::open(&undo_file)?, &path).is_ok(); - let mut undo_file = std::fs::OpenOptions::new() - .write(true) - .truncate(!append) - .read(true) - .open(&undo_file)?; - history.serialize(&mut undo_file, &path, current_rev, append)?; + History::deserialize(&mut std::fs::File::open(&undofile_path)?, &path) + .is_ok(); + if !append { + undofile.set_len(0)?; + } + history.serialize(&mut undofile, &path, current_rev, append)?; Ok(()) }) .await @@ -717,14 +724,16 @@ impl Document { let mut file = std::fs::File::open(&path)?; let (rope, ..) = from_reader(&mut file, Some(encoding))?; - // Calculate the difference between the buffer and source text, and apply it. - // This is not considered a modification of the contents of the file regardless - // of the encoding. - let transaction = helix_core::diff::compare_ropes(self.text(), &rope); - self.apply(&transaction, view.id); - self.append_changes_to_history(view); - self.reset_modified(); - + if let Err(e) = self.load_history() { + log::error!("{}", e); + // Calculate the difference between the buffer and source text, and apply it. + // This is not considered a modification of the contents of the file regardless + // of the encoding. + let transaction = helix_core::diff::compare_ropes(self.text(), &rope); + self.apply(&transaction, view.id); + self.append_changes_to_history(view); + self.reset_modified(); + } self.last_saved_time = SystemTime::now(); self.detect_indent_and_line_ending(); @@ -761,7 +770,13 @@ impl Document { &mut undo_file, self.path().unwrap(), )?; - self.history.set(history); + + if self.history.get_mut().is_empty() { + self.history.set(history); + } else { + let offset = self.get_last_saved_revision() + 1; + self.history.get_mut().merge(history, offset)?; + } self.set_last_saved_revision(last_saved_revision); } }