Skip to content

Commit 0cd310b

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

File tree

6 files changed

+47
-16
lines changed

6 files changed

+47
-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

+23-15
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use std::{
22
collections::HashMap,
33
ops::Range,
44
path::{Path, PathBuf},
5+
time::{Duration, Instant},
56
};
67

78
use git2::{Oid, Repository};
9+
use ropey::Rope;
810
use similar::DiffTag;
911

10-
use crate::{LineDiff, LineDiffs, RepoRoot};
12+
use crate::{rope::RopeLine, LineDiff, LineDiffs, RepoRoot};
1113

1214
pub struct Git {
1315
repo: Repository,
@@ -17,7 +19,7 @@ pub struct Git {
1719

1820
/// A cache mapping absolute file paths to file contents
1921
/// in the HEAD commit.
20-
head_cache: HashMap<PathBuf, String>,
22+
head_cache: HashMap<PathBuf, Rope>,
2123
}
2224

2325
impl std::fmt::Debug for Git {
@@ -54,7 +56,7 @@ impl Git {
5456
path.strip_prefix(&self.root).ok()
5557
}
5658

57-
pub fn read_file_from_head(&mut self, file: &Path) -> Option<&str> {
59+
pub fn read_file_from_head(&mut self, file: &Path) -> Option<&Rope> {
5860
let current_head = Self::head_commit_id(&self.repo)?;
5961
// TODO: Check cache validity on events like WindowChange
6062
// instead of on every keypress ? Will require hooks.
@@ -70,30 +72,35 @@ impl Git {
7072
let blob = object.peel_to_blob().ok()?;
7173
let contents = std::str::from_utf8(blob.content()).ok()?;
7274
self.head_cache
73-
.insert(file.to_path_buf(), contents.to_string());
75+
.insert(file.to_path_buf(), Rope::from_str(contents));
7476
}
7577

76-
self.head_cache.get(file).map(|s| s.as_str())
78+
self.head_cache.get(file)
7779
}
7880

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,
81+
pub fn line_diff_with_head(&mut self, file: &Path, contents: &Rope) -> LineDiffs {
82+
let old = match self.read_file_from_head(file) {
83+
Some(rope) => RopeLine::from_rope(rope),
8284
None => return LineDiffs::new(),
8385
};
84-
let mut config = similar::TextDiff::configure();
85-
config.timeout(std::time::Duration::from_millis(250));
86+
let new = RopeLine::from_rope(contents);
8687

8788
let mut line_diffs: LineDiffs = HashMap::new();
88-
8989
let mut mark_lines = |range: Range<usize>, change: LineDiff| {
9090
for line in range {
9191
line_diffs.insert(line, change);
9292
}
9393
};
9494

95-
let diff = config.diff_lines(base, contents);
96-
for op in diff.ops() {
95+
let timeout = Duration::from_millis(250);
96+
let diff = similar::capture_diff_slices_deadline(
97+
similar::Algorithm::Myers,
98+
&old,
99+
&new,
100+
Some(Instant::now() + timeout),
101+
);
102+
103+
for op in diff {
97104
let (tag, _, line_range) = op.as_tag_tuple();
98105
let start = line_range.start;
99106
match tag {
@@ -173,15 +180,16 @@ mod test {
173180
exec_git_cmd("commit -m message", git_dir);
174181

175182
let mut git = Git::discover_from_path(&file).unwrap();
183+
let rope = Rope::from_str(contents);
176184
assert_eq!(
177-
Some(contents),
185+
Some(&rope),
178186
git.read_file_from_head(&file),
179187
"Wrong blob contents from HEAD on clean index"
180188
);
181189

182190
fs::write(&file, "new text").expect("Could not write to file");
183191
assert_eq!(
184-
Some(contents),
192+
Some(&rope),
185193
git.read_file_from_head(&file),
186194
"Wrong blob contents from HEAD when index is dirty"
187195
);

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)