Skip to content

Commit 3c1c082

Browse files
committed
feat: Diff source caching and autodetection
1 parent 9829ac0 commit 3c1c082

File tree

9 files changed

+277
-105
lines changed

9 files changed

+277
-105
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-term/src/commands.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use std::{
7171
future::Future,
7272
io::Read,
7373
num::NonZeroUsize,
74+
sync::Arc,
7475
};
7576

7677
use std::{
@@ -3208,15 +3209,15 @@ fn jumplist_picker(cx: &mut Context) {
32083209

32093210
fn changed_file_picker(cx: &mut Context) {
32103211
pub struct FileChangeData {
3211-
cwd: PathBuf,
3212+
cwd: Arc<Path>,
32123213
style_untracked: Style,
32133214
style_modified: Style,
32143215
style_conflict: Style,
32153216
style_deleted: Style,
32163217
style_renamed: Style,
32173218
}
32183219

3219-
let cwd = helix_stdx::env::current_working_dir();
3220+
let cwd: Arc<Path> = Arc::from(helix_stdx::env::current_working_dir().as_path());
32203221
if !cwd.exists() {
32213222
cx.editor
32223223
.set_error("Current working directory does not exist");
@@ -3287,17 +3288,24 @@ fn changed_file_picker(cx: &mut Context) {
32873288
.with_preview(|_editor, meta| Some((meta.path().into(), None)));
32883289
let injector = picker.injector();
32893290

3290-
cx.editor
3291-
.diff_providers
3292-
.clone()
3293-
.for_each_changed_file(cwd, move |change| match change {
3291+
// Helix can be launched without arguments, in which case no diff provider will be loaded since
3292+
// there is no file to provide infos for.
3293+
//
3294+
// This ensures we have one to work with for cwd (and as a bonus it means any file opened
3295+
// from this picker will have its diff provider already in cache).
3296+
cx.editor.diff_providers.add(&cwd);
3297+
cx.editor.diff_providers.clone().for_each_changed_file(
3298+
cwd.clone(),
3299+
move |change| match change {
32943300
Ok(change) => injector.push(change).is_ok(),
32953301
Err(err) => {
32963302
status::report_blocking(err);
32973303
true
32983304
}
3299-
});
3305+
},
3306+
);
33003307
cx.push_layer(Box::new(overlaid(picker)));
3308+
cx.editor.diff_providers.remove(&cwd);
33013309
}
33023310

33033311
pub fn command_palette(cx: &mut Context) {

helix-term/src/commands/typed.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ fn reload(
12971297

12981298
let scrolloff = cx.editor.config().scrolloff;
12991299
let (view, doc) = current!(cx.editor);
1300-
doc.reload(view, &cx.editor.diff_providers).map(|_| {
1300+
doc.reload(view, &mut cx.editor.diff_providers).map(|_| {
13011301
view.ensure_cursor_in_view(doc, scrolloff);
13021302
})?;
13031303
if let Some(path) = doc.path() {
@@ -1336,6 +1336,8 @@ fn reload_all(
13361336
})
13371337
.collect();
13381338

1339+
cx.editor.diff_providers.reset();
1340+
13391341
for (doc_id, view_ids) in docs_view_ids {
13401342
let doc = doc_mut!(cx.editor, &doc_id);
13411343

@@ -1345,7 +1347,7 @@ fn reload_all(
13451347
// Ensure that the view is synced with the document's history.
13461348
view.sync_changes(doc);
13471349

1348-
if let Err(error) = doc.reload(view, &cx.editor.diff_providers) {
1350+
if let Err(error) = doc.reload(view, &mut cx.editor.diff_providers) {
13491351
cx.editor.set_error(format!("{}", error));
13501352
continue;
13511353
}

helix-vcs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
1919
parking_lot = "0.12"
2020
arc-swap = { version = "1.7.1" }
2121

22-
gix = { version = "0.70.0", features = ["attributes", "status"], default-features = false, optional = true }
22+
gix = { version = "0.70.0", features = ["attributes", "parallel", "status"], default-features = false, optional = true }
2323
imara-diff = "0.1.7"
2424
anyhow = "1"
2525

helix-vcs/src/git.rs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,12 @@ use crate::FileChange;
2222
#[cfg(test)]
2323
mod test;
2424

25-
#[inline]
26-
fn get_repo_dir(file: &Path) -> Result<&Path> {
27-
file.parent().context("file has no parent directory")
28-
}
29-
30-
pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
25+
pub fn get_diff_base(repo: &ThreadSafeRepository, file: &Path) -> Result<Vec<u8>> {
3126
debug_assert!(!file.exists() || file.is_file());
3227
debug_assert!(file.is_absolute());
33-
let file = gix::path::realpath(file).context("resolve symlinks")?;
34-
35-
// TODO cache repository lookup
28+
let file = gix::path::realpath(file).context("resolve symlink")?;
3629

37-
let repo_dir = get_repo_dir(&file)?;
38-
let repo = open_repo(repo_dir)
39-
.context("failed to open git repo")?
40-
.to_thread_local();
30+
let repo = repo.to_thread_local();
4131
let head = repo.head_commit()?;
4232
let file_oid = find_file_in_commit(&repo, &head, &file)?;
4333

@@ -59,15 +49,8 @@ pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
5949
}
6050
}
6151

62-
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
63-
debug_assert!(!file.exists() || file.is_file());
64-
debug_assert!(file.is_absolute());
65-
let file = gix::path::realpath(file).context("resolve symlinks")?;
66-
67-
let repo_dir = get_repo_dir(&file)?;
68-
let repo = open_repo(repo_dir)
69-
.context("failed to open git repo")?
70-
.to_thread_local();
52+
pub fn get_current_head_name(repo: &ThreadSafeRepository) -> Result<Arc<ArcSwap<Box<str>>>> {
53+
let repo = repo.to_thread_local();
7154
let head_ref = repo.head_ref()?;
7255
let head_commit = repo.head_commit()?;
7356

@@ -79,11 +62,17 @@ pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
7962
Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
8063
}
8164

82-
pub fn for_each_changed_file(cwd: &Path, f: impl Fn(Result<FileChange>) -> bool) -> Result<()> {
83-
status(&open_repo(cwd)?.to_thread_local(), f)
65+
pub fn for_each_changed_file(
66+
repo: &ThreadSafeRepository,
67+
f: impl Fn(Result<FileChange>) -> bool,
68+
) -> Result<()> {
69+
status(&repo.to_thread_local(), f)
8470
}
8571

86-
fn open_repo(path: &Path) -> Result<ThreadSafeRepository> {
72+
pub(super) fn open_repo(path: &Path) -> Result<ThreadSafeRepository> {
73+
// Ensure the repo itself is an absolute real path, else we'll not match prefixes with
74+
// symlink-resolved files in `get_diff_base()` above.
75+
let path = gix::path::realpath(path)?;
8776
// custom open options
8877
let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default();
8978

helix-vcs/src/git/test.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ fn missing_file() {
5454
let file = temp_git.path().join("file.txt");
5555
File::create(&file).unwrap().write_all(b"foo").unwrap();
5656

57-
assert!(git::get_diff_base(&file).is_err());
57+
let repo = git::open_repo(temp_git.path()).unwrap();
58+
assert!(git::get_diff_base(&repo, &file).is_err());
5859
}
5960

6061
#[test]
@@ -64,7 +65,12 @@ fn unmodified_file() {
6465
let contents = b"foo".as_slice();
6566
File::create(&file).unwrap().write_all(contents).unwrap();
6667
create_commit(temp_git.path(), true);
67-
assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
68+
69+
let repo = git::open_repo(temp_git.path()).unwrap();
70+
assert_eq!(
71+
git::get_diff_base(&repo, &file).unwrap(),
72+
Vec::from(contents)
73+
);
6874
}
6975

7076
#[test]
@@ -76,7 +82,11 @@ fn modified_file() {
7682
create_commit(temp_git.path(), true);
7783
File::create(&file).unwrap().write_all(b"bar").unwrap();
7884

79-
assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
85+
let repo = git::open_repo(temp_git.path()).unwrap();
86+
assert_eq!(
87+
git::get_diff_base(&repo, &file).unwrap(),
88+
Vec::from(contents)
89+
);
8090
}
8191

8292
/// Test that `get_file_head` does not return content for a directory.
@@ -95,7 +105,9 @@ fn directory() {
95105

96106
std::fs::remove_dir_all(&dir).unwrap();
97107
File::create(&dir).unwrap().write_all(b"bar").unwrap();
98-
assert!(git::get_diff_base(&dir).is_err());
108+
109+
let repo = git::open_repo(temp_git.path()).unwrap();
110+
assert!(git::get_diff_base(&repo, &dir).is_err());
99111
}
100112

101113
/// Test that `get_diff_base` resolves symlinks so that the same diff base is
@@ -122,8 +134,9 @@ fn symlink() {
122134
symlink("file.txt", &file_link).unwrap();
123135
create_commit(temp_git.path(), true);
124136

125-
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
126-
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
137+
let repo = git::open_repo(temp_git.path()).unwrap();
138+
assert_eq!(git::get_diff_base(&repo, &file_link).unwrap(), contents);
139+
assert_eq!(git::get_diff_base(&repo, &file).unwrap(), contents);
127140
}
128141

129142
/// Test that `get_diff_base` returns content when the file is a symlink to
@@ -147,6 +160,7 @@ fn symlink_to_git_repo() {
147160
let file_link = temp_dir.path().join("file_link.txt");
148161
symlink(&file, &file_link).unwrap();
149162

150-
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
151-
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
163+
let repo = git::open_repo(temp_git.path()).unwrap();
164+
assert_eq!(git::get_diff_base(&repo, &file_link).unwrap(), contents);
165+
assert_eq!(git::get_diff_base(&repo, &file).unwrap(), contents);
152166
}

0 commit comments

Comments
 (0)