From c96c98c6c545378d87f59d0da847ed3a8bd69e79 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Thu, 10 Mar 2022 19:30:25 +0530 Subject: [PATCH] Avoid string allocation when git diffing --- Cargo.lock | 1 + helix-vcs/Cargo.toml | 1 + helix-vcs/src/git.rs | 30 +++++++++++++++--------------- helix-vcs/src/lib.rs | 1 + helix-vcs/src/rope.rs | 20 ++++++++++++++++++++ helix-view/src/document.rs | 2 +- 6 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 helix-vcs/src/rope.rs diff --git a/Cargo.lock b/Cargo.lock index 4a9dfc7d84a9b..11eb9a9a1cc28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,7 @@ name = "helix-vcs" version = "0.6.0" dependencies = [ "git2", + "ropey", "similar", "tempfile", ] diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml index 8ea36b40297ef..bb24d1f1e1c9b 100644 --- a/helix-vcs/Cargo.toml +++ b/helix-vcs/Cargo.toml @@ -13,6 +13,7 @@ homepage = "https://helix-editor.com" [dependencies] git2 = { version = "0.13", default-features = false } similar = "2.1" +ropey = "1.3" [dev-dependencies] tempfile = "3.3" diff --git a/helix-vcs/src/git.rs b/helix-vcs/src/git.rs index 748dee40c74ad..63eeb5bd9b35b 100644 --- a/helix-vcs/src/git.rs +++ b/helix-vcs/src/git.rs @@ -5,9 +5,10 @@ use std::{ }; use git2::{Oid, Repository}; +use ropey::Rope; use similar::DiffTag; -use crate::{LineDiff, LineDiffs, RepoRoot}; +use crate::{rope::RopeLine, LineDiff, LineDiffs, RepoRoot}; pub struct Git { repo: Repository, @@ -17,7 +18,7 @@ pub struct Git { /// A cache mapping absolute file paths to file contents /// in the HEAD commit. - head_cache: HashMap, + head_cache: HashMap, } impl std::fmt::Debug for Git { @@ -54,7 +55,7 @@ impl Git { path.strip_prefix(&self.root).ok() } - pub fn read_file_from_head(&mut self, file: &Path) -> Option<&str> { + pub fn read_file_from_head(&mut self, file: &Path) -> Option<&Rope> { let current_head = Self::head_commit_id(&self.repo)?; // TODO: Check cache validity on events like WindowChange // instead of on every keypress ? Will require hooks. @@ -70,30 +71,28 @@ impl Git { let blob = object.peel_to_blob().ok()?; let contents = std::str::from_utf8(blob.content()).ok()?; self.head_cache - .insert(file.to_path_buf(), contents.to_string()); + .insert(file.to_path_buf(), Rope::from_str(contents)); } - self.head_cache.get(file).map(|s| s.as_str()) + self.head_cache.get(file) } - pub fn line_diff_with_head(&mut self, file: &Path, contents: &str) -> LineDiffs { - let base = match self.read_file_from_head(file) { - Some(b) => b, + pub fn line_diff_with_head(&mut self, file: &Path, contents: &Rope) -> LineDiffs { + let old = match self.read_file_from_head(file) { + Some(rope) => RopeLine::from_rope(rope), None => return LineDiffs::new(), }; - let mut config = similar::TextDiff::configure(); - config.timeout(std::time::Duration::from_millis(250)); + let new = RopeLine::from_rope(contents); let mut line_diffs: LineDiffs = HashMap::new(); - let mut mark_lines = |range: Range, change: LineDiff| { for line in range { line_diffs.insert(line, change); } }; - let diff = config.diff_lines(base, contents); - for op in diff.ops() { + let diff = similar::capture_diff_slices(similar::Algorithm::Myers, &old, &new); + for op in diff { let (tag, _, line_range) = op.as_tag_tuple(); let start = line_range.start; match tag { @@ -173,15 +172,16 @@ mod test { exec_git_cmd("commit -m message", git_dir); let mut git = Git::discover_from_path(&file).unwrap(); + let rope = Rope::from_str(contents); assert_eq!( - Some(contents), + Some(&rope), git.read_file_from_head(&file), "Wrong blob contents from HEAD on clean index" ); fs::write(&file, "new text").expect("Could not write to file"); assert_eq!( - Some(contents), + Some(&rope), git.read_file_from_head(&file), "Wrong blob contents from HEAD when index is dirty" ); diff --git a/helix-vcs/src/lib.rs b/helix-vcs/src/lib.rs index a7abc9e39b154..4e3e4f4d32910 100644 --- a/helix-vcs/src/lib.rs +++ b/helix-vcs/src/lib.rs @@ -1,4 +1,5 @@ mod git; +mod rope; use std::{ cell::RefCell, diff --git a/helix-vcs/src/rope.rs b/helix-vcs/src/rope.rs new file mode 100644 index 0000000000000..53488fa2839b7 --- /dev/null +++ b/helix-vcs/src/rope.rs @@ -0,0 +1,20 @@ +use std::hash::Hash; + +use ropey::Rope; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct RopeLine<'a>(pub ropey::RopeSlice<'a>); + +impl Hash for RopeLine<'_> { + fn hash(&self, state: &mut H) { + for chunk in self.0.chunks() { + chunk.hash(state); + } + } +} + +impl<'a> RopeLine<'a> { + pub fn from_rope(rope: &'a Rope) -> Vec { + rope.lines().into_iter().map(RopeLine).collect() + } +} diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 5c4aeae6b9cba..f2ce5d220ff27 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -624,7 +624,7 @@ impl Document { .as_ref() .and_then(|v| v.try_borrow_mut().ok()); if let Some((mut vcs, path)) = vcs.zip(self.path()) { - self.line_diffs = vcs.line_diff_with_head(path, &self.text().to_string()); + self.line_diffs = vcs.line_diff_with_head(path, self.text()); } }