Skip to content

Commit e6e1285

Browse files
committed
feat: Diff source caching and autodetection
1 parent 4418e33 commit e6e1285

File tree

9 files changed

+279
-108
lines changed

9 files changed

+279
-108
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
@@ -76,6 +76,7 @@ use std::{
7676
future::Future,
7777
io::Read,
7878
num::NonZeroUsize,
79+
sync::Arc,
7980
};
8081

8182
use std::{
@@ -3304,15 +3305,15 @@ fn jumplist_picker(cx: &mut Context) {
33043305

33053306
fn changed_file_picker(cx: &mut Context) {
33063307
pub struct FileChangeData {
3307-
cwd: PathBuf,
3308+
cwd: Arc<Path>,
33083309
style_untracked: Style,
33093310
style_modified: Style,
33103311
style_conflict: Style,
33113312
style_deleted: Style,
33123313
style_renamed: Style,
33133314
}
33143315

3315-
let cwd = helix_stdx::env::current_working_dir();
3316+
let cwd: Arc<Path> = Arc::from(helix_stdx::env::current_working_dir().as_path());
33163317
if !cwd.exists() {
33173318
cx.editor
33183319
.set_error("Current working directory does not exist");
@@ -3383,17 +3384,24 @@ fn changed_file_picker(cx: &mut Context) {
33833384
.with_preview(|_editor, meta| Some((meta.path().into(), None)));
33843385
let injector = picker.injector();
33853386

3386-
cx.editor
3387-
.diff_providers
3388-
.clone()
3389-
.for_each_changed_file(cwd, move |change| match change {
3387+
// Helix can be launched without arguments, in which case no diff provider will be loaded since
3388+
// there is no file to provide infos for.
3389+
//
3390+
// This ensures we have one to work with for cwd (and as a bonus it means any file opened
3391+
// from this picker will have its diff provider already in cache).
3392+
cx.editor.diff_providers.add(&cwd);
3393+
cx.editor.diff_providers.clone().for_each_changed_file(
3394+
cwd.clone(),
3395+
move |change| match change {
33903396
Ok(change) => injector.push(change).is_ok(),
33913397
Err(err) => {
33923398
status::report_blocking(err);
33933399
true
33943400
}
3395-
});
3401+
},
3402+
);
33963403
cx.push_layer(Box::new(overlaid(picker)));
3404+
cx.editor.diff_providers.remove(&cwd);
33973405
}
33983406

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

13951395
let scrolloff = cx.editor.config().scrolloff;
13961396
let (view, doc) = current!(cx.editor);
1397-
doc.reload(view, &cx.editor.diff_providers).map(|_| {
1397+
doc.reload(view, &mut cx.editor.diff_providers).map(|_| {
13981398
view.ensure_cursor_in_view(doc, scrolloff);
13991399
})?;
14001400
if let Some(path) = doc.path() {
@@ -1429,6 +1429,8 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
14291429
})
14301430
.collect();
14311431

1432+
cx.editor.diff_providers.reset();
1433+
14321434
for (doc_id, view_ids) in docs_view_ids {
14331435
let doc = doc_mut!(cx.editor, &doc_id);
14341436

@@ -1438,7 +1440,7 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
14381440
// Ensure that the view is synced with the document's history.
14391441
view.sync_changes(doc);
14401442

1441-
if let Err(error) = doc.reload(view, &cx.editor.diff_providers) {
1443+
if let Err(error) = doc.reload(view, &mut cx.editor.diff_providers) {
14421444
cx.editor.set_error(format!("{}", error));
14431445
continue;
14441446
}

helix-vcs/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
1717
parking_lot.workspace = true
1818
arc-swap = { version = "1.7.1" }
1919

20-
gix = { version = "0.72.1", features = ["attributes", "status"], default-features = false, optional = true }
21-
imara-diff = "0.2.0"
20+
gix = { version = "0.72.1", features = ["attributes", "parallel", "status"], default-features = false, optional = true }
21+
imara-diff = "0.2.0"
2222
anyhow = "1"
2323

2424
log = "0.4"

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)