Skip to content

Commit

Permalink
Avoid string allocation when git diffing
Browse files Browse the repository at this point in the history
  • Loading branch information
sudormrfbin committed Mar 10, 2022
1 parent 7e1463e commit c96c98c
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 16 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions helix-vcs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
30 changes: 15 additions & 15 deletions helix-vcs/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -17,7 +18,7 @@ pub struct Git {

/// A cache mapping absolute file paths to file contents
/// in the HEAD commit.
head_cache: HashMap<PathBuf, String>,
head_cache: HashMap<PathBuf, Rope>,
}

impl std::fmt::Debug for Git {
Expand Down Expand Up @@ -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.
Expand All @@ -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<usize>, 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 {
Expand Down Expand Up @@ -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"
);
Expand Down
1 change: 1 addition & 0 deletions helix-vcs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod git;
mod rope;

use std::{
cell::RefCell,
Expand Down
20 changes: 20 additions & 0 deletions helix-vcs/src/rope.rs
Original file line number Diff line number Diff line change
@@ -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<H: std::hash::Hasher>(&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<Self> {
rope.lines().into_iter().map(RopeLine).collect()
}
}
2 changes: 1 addition & 1 deletion helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down

0 comments on commit c96c98c

Please sign in to comment.