Skip to content

Commit 6793574

Browse files
committed
feat: Diff source caching and autodetection
1 parent 37d5165 commit 6793574

File tree

9 files changed

+278
-108
lines changed

9 files changed

+278
-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
@@ -79,6 +79,7 @@ use std::{
7979
future::Future,
8080
io::Read,
8181
num::NonZeroUsize,
82+
sync::Arc,
8283
};
8384

8485
use std::{
@@ -3324,15 +3325,15 @@ fn jumplist_picker(cx: &mut Context) {
33243325

33253326
fn changed_file_picker(cx: &mut Context) {
33263327
pub struct FileChangeData {
3327-
cwd: PathBuf,
3328+
cwd: Arc<Path>,
33283329
style_untracked: Style,
33293330
style_modified: Style,
33303331
style_conflict: Style,
33313332
style_deleted: Style,
33323333
style_renamed: Style,
33333334
}
33343335

3335-
let cwd = helix_stdx::env::current_working_dir();
3336+
let cwd: Arc<Path> = Arc::from(helix_stdx::env::current_working_dir().as_path());
33363337
if !cwd.exists() {
33373338
cx.editor
33383339
.set_error("Current working directory does not exist");
@@ -3403,17 +3404,24 @@ fn changed_file_picker(cx: &mut Context) {
34033404
.with_preview(|_editor, meta| Some((meta.path().into(), None)));
34043405
let injector = picker.injector();
34053406

3406-
cx.editor
3407-
.diff_providers
3408-
.clone()
3409-
.for_each_changed_file(cwd, move |change| match change {
3407+
// Helix can be launched without arguments, in which case no diff provider will be loaded since
3408+
// there is no file to provide infos for.
3409+
//
3410+
// This ensures we have one to work with for cwd (and as a bonus it means any file opened
3411+
// from this picker will have its diff provider already in cache).
3412+
cx.editor.diff_providers.add(&cwd);
3413+
cx.editor.diff_providers.clone().for_each_changed_file(
3414+
cwd.clone(),
3415+
move |change| match change {
34103416
Ok(change) => injector.push(change).is_ok(),
34113417
Err(err) => {
34123418
status::report_blocking(err);
34133419
true
34143420
}
3415-
});
3421+
},
3422+
);
34163423
cx.push_layer(Box::new(overlaid(picker)));
3424+
cx.editor.diff_providers.remove(&cwd);
34173425
}
34183426

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

14361436
let scrolloff = cx.editor.config().scrolloff;
14371437
let (view, doc) = current!(cx.editor);
1438-
doc.reload(view, &cx.editor.diff_providers).map(|_| {
1438+
doc.reload(view, &mut cx.editor.diff_providers).map(|_| {
14391439
view.ensure_cursor_in_view(doc, scrolloff);
14401440
})?;
14411441
if let Some(path) = doc.path() {
@@ -1470,6 +1470,8 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
14701470
})
14711471
.collect();
14721472

1473+
cx.editor.diff_providers.reset();
1474+
14731475
for (doc_id, view_ids) in docs_view_ids {
14741476
let doc = doc_mut!(cx.editor, &doc_id);
14751477

@@ -1479,7 +1481,7 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
14791481
// Ensure that the view is synced with the document's history.
14801482
view.sync_changes(doc);
14811483

1482-
if let Err(error) = doc.reload(view, &cx.editor.diff_providers) {
1484+
if let Err(error) = doc.reload(view, &mut cx.editor.diff_providers) {
14831485
cx.editor.set_error(format!("{}", error));
14841486
continue;
14851487
}

helix-vcs/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ 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.75.0", features = ["attributes", "status"], default-features = false, optional = true }
20+
gix = { version = "0.75.0", features = ["attributes", "parallel", "status"], default-features = false, optional = true }
2121
imara-diff = "0.2.0"
2222
anyhow = "1"
23-
2423
log = "0.4"
2524

2625
[features]

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)