Skip to content

Commit

Permalink
reload histories if persistent_undo is dynamically enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
kirawi committed Feb 20, 2023
1 parent 8a60b79 commit cf81ab7
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 6 deletions.
25 changes: 23 additions & 2 deletions helix-core/src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ impl Default for History {

impl Revision {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
// `timestamp` is ignored since `Instant`s can't be serialized.
write_usize(writer, self.parent)?;
self.transaction.serialize(writer)?;
self.inversion.serialize(writer)?;
Expand All @@ -117,8 +118,7 @@ impl Revision {
}
}

// Temporarily 3 for review.
const HEADER_TAG: &str = "Helix Undofile 4\n";
const HEADER_TAG: &str = "Helix Undofile 1\n";

fn get_hash<R: Read>(reader: &mut R) -> std::io::Result<[u8; 20]> {
const BUF_SIZE: usize = 8192;
Expand All @@ -144,6 +144,7 @@ impl History {
revision: usize,
last_saved_revision: usize,
) -> std::io::Result<()> {
// Header
let mtime = std::fs::metadata(path)?
.modified()?
.duration_since(std::time::UNIX_EPOCH)
Expand All @@ -164,9 +165,14 @@ impl History {
Ok(())
}

/// Returns the deserialized [`History`] and the last_saved_revision.
pub fn deserialize<R: Read>(reader: &mut R, path: &Path) -> std::io::Result<(usize, Self)> {
let (current, last_saved_revision) = Self::read_header(reader, path)?;

// Since `timestamp` can't be serialized, a new timestamp is created.
let timestamp = Instant::now();

// Read the revisions and construct the tree.
let len = read_usize(reader)?;
let mut revisions: Vec<Revision> = Vec::with_capacity(len);
for _ in 0..len {
Expand All @@ -184,6 +190,16 @@ impl History {
Ok((last_saved_revision, history))
}

/// If two histories originate from: `A -> B (B is head)` but have deviated since then such that
/// the first history is: `A -> B -> C -> D (D is head)` and the second one is:
/// `A -> B -> E -> F (F is head)`.
/// Then they are merged into
/// ```
/// A -> B -> C -> D
/// \
/// \ -> E -> F
/// ```
/// and retain their revision heads.
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();
Expand Down Expand Up @@ -823,6 +839,7 @@ mod test {

quickcheck!(
fn serde_history(original: String, changes_a: Vec<String>, changes_b: Vec<String>) -> bool {
// Constructs a set of transactions and applies them to the history.
fn create_changes(history: &mut History, doc: &mut Rope, changes: Vec<String>) {
for c in changes.into_iter().map(Rope::from) {
let transaction = crate::diff::compare_ropes(doc, &c);
Expand All @@ -843,6 +860,8 @@ mod test {
let file = tempfile::NamedTempFile::new().unwrap();
history.serialize(&mut cursor, file.path(), 0, 0).unwrap();
cursor.set_position(0);

// Check if the original and deserialized history match.
let (_, res) = History::deserialize(&mut cursor, file.path()).unwrap();
assert_eq!(history, res);

Expand All @@ -854,6 +873,8 @@ mod test {
.serialize(&mut cursor, file.path(), 0, last_saved_revision)
.unwrap();
cursor.set_position(0);

// Check if they are the same after appending new changes.
let (_, res) = History::deserialize(&mut cursor, file.path()).unwrap();
history == res
}
Expand Down
26 changes: 26 additions & 0 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,21 @@ impl Application {
// the Application can apply it.
ConfigEvent::Update(editor_config) => {
let mut app_config = (*self.config.load().clone()).clone();
if self.config.load().editor.persistent_undo == false
&& editor_config.persistent_undo == true
{
for doc in self.editor.documents_mut() {
// HAXX: Do this so all revisions in this doc are treated as new.
doc.set_last_saved_revision(0);
if let Err(e) = doc.load_history() {
log::error!(
"failed to reload history for {}: {e}",
doc.path().unwrap().to_string_lossy()
);
return;
}
}
}
app_config.editor = *editor_config;
self.config.store(Arc::new(app_config));
}
Expand Down Expand Up @@ -439,6 +454,17 @@ impl Application {
let mut refresh_config = || -> Result<(), Error> {
let default_config = Config::load_default()
.map_err(|err| anyhow::anyhow!("Failed to load config: {}", err))?;

// Merge histories of existing docs if persistent undo was enabled.
if self.config.load().editor.persistent_undo == false
&& default_config.editor.persistent_undo == true
{
for doc in self.editor.documents_mut() {
// HAXX: Do this so all revisions in this doc are treated as new.
doc.set_last_saved_revision(0);
doc.load_history()?;
}
}
self.refresh_language_config()?;
self.refresh_theme(&default_config)?;
// Store new config
Expand Down
7 changes: 3 additions & 4 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,10 +1387,8 @@ impl Display for FormatterError {

#[cfg(test)]
mod test {
use arc_swap::{access::Map, ArcSwap};
use quickcheck::Gen;

use super::*;
use arc_swap::ArcSwap;

#[test]
fn changeset_to_changes_ignore_line_endings() {
Expand Down Expand Up @@ -1561,6 +1559,7 @@ mod test {
#[tokio::test(flavor = "multi_thread")]
async fn reload_history() {
let test_fn: fn(Vec<String>) -> bool = |changes| -> bool {
// Divide the vec into 3 sets of changes.
let len = changes.len() / 3;
let mut original = Rope::new();
let mut iter = changes.into_iter();
Expand Down Expand Up @@ -1623,7 +1622,6 @@ mod test {
for c in changes_b {
doc_1.apply(&c, view_id);
}

for c in changes_c {
doc_2.apply(&c, view_id);
}
Expand All @@ -1632,6 +1630,7 @@ mod test {
doc_1.load_history().unwrap();
doc_3.load_history().unwrap();

// doc_3 had no diverging edits, so they should be the same.
assert_eq!(doc_2.history.get_mut(), doc_3.history.get_mut());

helix_lsp::block_on(doc_1.save::<PathBuf>(None, true).unwrap()).unwrap();
Expand Down

0 comments on commit cf81ab7

Please sign in to comment.