Skip to content

Commit c96c98c

Browse files
committed
Avoid string allocation when git diffing
1 parent 7e1463e commit c96c98c

File tree

6 files changed

+39
-16
lines changed

6 files changed

+39
-16
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-vcs/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ homepage = "https://helix-editor.com"
1313
[dependencies]
1414
git2 = { version = "0.13", default-features = false }
1515
similar = "2.1"
16+
ropey = "1.3"
1617

1718
[dev-dependencies]
1819
tempfile = "3.3"

helix-vcs/src/git.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::{
55
};
66

77
use git2::{Oid, Repository};
8+
use ropey::Rope;
89
use similar::DiffTag;
910

10-
use crate::{LineDiff, LineDiffs, RepoRoot};
11+
use crate::{rope::RopeLine, LineDiff, LineDiffs, RepoRoot};
1112

1213
pub struct Git {
1314
repo: Repository,
@@ -17,7 +18,7 @@ pub struct Git {
1718

1819
/// A cache mapping absolute file paths to file contents
1920
/// in the HEAD commit.
20-
head_cache: HashMap<PathBuf, String>,
21+
head_cache: HashMap<PathBuf, Rope>,
2122
}
2223

2324
impl std::fmt::Debug for Git {
@@ -54,7 +55,7 @@ impl Git {
5455
path.strip_prefix(&self.root).ok()
5556
}
5657

57-
pub fn read_file_from_head(&mut self, file: &Path) -> Option<&str> {
58+
pub fn read_file_from_head(&mut self, file: &Path) -> Option<&Rope> {
5859
let current_head = Self::head_commit_id(&self.repo)?;
5960
// TODO: Check cache validity on events like WindowChange
6061
// instead of on every keypress ? Will require hooks.
@@ -70,30 +71,28 @@ impl Git {
7071
let blob = object.peel_to_blob().ok()?;
7172
let contents = std::str::from_utf8(blob.content()).ok()?;
7273
self.head_cache
73-
.insert(file.to_path_buf(), contents.to_string());
74+
.insert(file.to_path_buf(), Rope::from_str(contents));
7475
}
7576

76-
self.head_cache.get(file).map(|s| s.as_str())
77+
self.head_cache.get(file)
7778
}
7879

79-
pub fn line_diff_with_head(&mut self, file: &Path, contents: &str) -> LineDiffs {
80-
let base = match self.read_file_from_head(file) {
81-
Some(b) => b,
80+
pub fn line_diff_with_head(&mut self, file: &Path, contents: &Rope) -> LineDiffs {
81+
let old = match self.read_file_from_head(file) {
82+
Some(rope) => RopeLine::from_rope(rope),
8283
None => return LineDiffs::new(),
8384
};
84-
let mut config = similar::TextDiff::configure();
85-
config.timeout(std::time::Duration::from_millis(250));
85+
let new = RopeLine::from_rope(contents);
8686

8787
let mut line_diffs: LineDiffs = HashMap::new();
88-
8988
let mut mark_lines = |range: Range<usize>, change: LineDiff| {
9089
for line in range {
9190
line_diffs.insert(line, change);
9291
}
9392
};
9493

95-
let diff = config.diff_lines(base, contents);
96-
for op in diff.ops() {
94+
let diff = similar::capture_diff_slices(similar::Algorithm::Myers, &old, &new);
95+
for op in diff {
9796
let (tag, _, line_range) = op.as_tag_tuple();
9897
let start = line_range.start;
9998
match tag {
@@ -173,15 +172,16 @@ mod test {
173172
exec_git_cmd("commit -m message", git_dir);
174173

175174
let mut git = Git::discover_from_path(&file).unwrap();
175+
let rope = Rope::from_str(contents);
176176
assert_eq!(
177-
Some(contents),
177+
Some(&rope),
178178
git.read_file_from_head(&file),
179179
"Wrong blob contents from HEAD on clean index"
180180
);
181181

182182
fs::write(&file, "new text").expect("Could not write to file");
183183
assert_eq!(
184-
Some(contents),
184+
Some(&rope),
185185
git.read_file_from_head(&file),
186186
"Wrong blob contents from HEAD when index is dirty"
187187
);

helix-vcs/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod git;
2+
mod rope;
23

34
use std::{
45
cell::RefCell,

helix-vcs/src/rope.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::hash::Hash;
2+
3+
use ropey::Rope;
4+
5+
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
6+
pub struct RopeLine<'a>(pub ropey::RopeSlice<'a>);
7+
8+
impl Hash for RopeLine<'_> {
9+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
10+
for chunk in self.0.chunks() {
11+
chunk.hash(state);
12+
}
13+
}
14+
}
15+
16+
impl<'a> RopeLine<'a> {
17+
pub fn from_rope(rope: &'a Rope) -> Vec<Self> {
18+
rope.lines().into_iter().map(RopeLine).collect()
19+
}
20+
}

helix-view/src/document.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ impl Document {
624624
.as_ref()
625625
.and_then(|v| v.try_borrow_mut().ok());
626626
if let Some((mut vcs, path)) = vcs.zip(self.path()) {
627-
self.line_diffs = vcs.line_diff_with_head(path, &self.text().to_string());
627+
self.line_diffs = vcs.line_diff_with_head(path, self.text());
628628
}
629629
}
630630

0 commit comments

Comments
 (0)