Skip to content

Commit d993441

Browse files
committed
feat: Diff source caching and autodetection
1 parent 1c32fb2 commit d993441

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
@@ -74,6 +74,7 @@ use std::{
7474
future::Future,
7575
io::Read,
7676
num::NonZeroUsize,
77+
sync::Arc,
7778
};
7879

7980
use std::{
@@ -3292,15 +3293,15 @@ fn jumplist_picker(cx: &mut Context) {
32923293

32933294
fn changed_file_picker(cx: &mut Context) {
32943295
pub struct FileChangeData {
3295-
cwd: PathBuf,
3296+
cwd: Arc<Path>,
32963297
style_untracked: Style,
32973298
style_modified: Style,
32983299
style_conflict: Style,
32993300
style_deleted: Style,
33003301
style_renamed: Style,
33013302
}
33023303

3303-
let cwd = helix_stdx::env::current_working_dir();
3304+
let cwd: Arc<Path> = Arc::from(helix_stdx::env::current_working_dir().as_path());
33043305
if !cwd.exists() {
33053306
cx.editor
33063307
.set_error("Current working directory does not exist");
@@ -3371,17 +3372,24 @@ fn changed_file_picker(cx: &mut Context) {
33713372
.with_preview(|_editor, meta| Some((meta.path().into(), None)));
33723373
let injector = picker.injector();
33733374

3374-
cx.editor
3375-
.diff_providers
3376-
.clone()
3377-
.for_each_changed_file(cwd, move |change| match change {
3375+
// Helix can be launched without arguments, in which case no diff provider will be loaded since
3376+
// there is no file to provide infos for.
3377+
//
3378+
// This ensures we have one to work with for cwd (and as a bonus it means any file opened
3379+
// from this picker will have its diff provider already in cache).
3380+
cx.editor.diff_providers.add(&cwd);
3381+
cx.editor.diff_providers.clone().for_each_changed_file(
3382+
cwd.clone(),
3383+
move |change| match change {
33783384
Ok(change) => injector.push(change).is_ok(),
33793385
Err(err) => {
33803386
status::report_blocking(err);
33813387
true
33823388
}
3383-
});
3389+
},
3390+
);
33843391
cx.push_layer(Box::new(overlaid(picker)));
3392+
cx.editor.diff_providers.remove(&cwd);
33853393
}
33863394

33873395
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
@@ -1329,7 +1329,7 @@ fn reload(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh
13291329

13301330
let scrolloff = cx.editor.config().scrolloff;
13311331
let (view, doc) = current!(cx.editor);
1332-
doc.reload(view, &cx.editor.diff_providers).map(|_| {
1332+
doc.reload(view, &mut cx.editor.diff_providers).map(|_| {
13331333
view.ensure_cursor_in_view(doc, scrolloff);
13341334
})?;
13351335
if let Some(path) = doc.path() {
@@ -1364,6 +1364,8 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
13641364
})
13651365
.collect();
13661366

1367+
cx.editor.diff_providers.reset();
1368+
13671369
for (doc_id, view_ids) in docs_view_ids {
13681370
let doc = doc_mut!(cx.editor, &doc_id);
13691371

@@ -1373,7 +1375,7 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
13731375
// Ensure that the view is synced with the document's history.
13741376
view.sync_changes(doc);
13751377

1376-
if let Err(error) = doc.reload(view, &cx.editor.diff_providers) {
1378+
if let Err(error) = doc.reload(view, &mut cx.editor.diff_providers) {
13771379
cx.editor.set_error(format!("{}", error));
13781380
continue;
13791381
}

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.workspace = true
2020
arc-swap = { version = "1.7.1" }
2121

22-
gix = { version = "0.72.1", features = ["attributes", "status"], default-features = false, optional = true }
22+
gix = { version = "0.72.1", features = ["attributes", "parallel", "status"], default-features = false, optional = true }
2323
imara-diff = "0.1.8"
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)