feat: show repository changes in normal file pickers
This commit is contained in:
+99
-11
@@ -3,7 +3,9 @@ use crate::{
|
||||
blame::{git, git_without_literal_pathspecs, run, run_status},
|
||||
diff::line_hunks,
|
||||
editor::normalize_newlines,
|
||||
repository::{jj_command, jj_file_pattern, safe_path, store_for_jj},
|
||||
repository::{
|
||||
WorkingFileChange, WorkingFileReader, jj_command, jj_file_pattern, safe_path, store_for_jj,
|
||||
},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@@ -23,7 +25,13 @@ const FILE_COUNT_LIMIT: usize = 1024;
|
||||
type Files = Vec<(usize, PathBuf, Arc<str>)>;
|
||||
type FileResult = Result<Vec<Option<Change>>, String>;
|
||||
pub(crate) type ResultBatch = Vec<(usize, FileResult)>;
|
||||
type Request = (u64, u64, Files);
|
||||
type PickerRequest = (PathBuf, Vec<(PathBuf, Arc<str>)>);
|
||||
type Request = (u64, u64, Files, Option<PickerRequest>);
|
||||
|
||||
pub(crate) struct PickerChanges {
|
||||
pub(crate) root: PathBuf,
|
||||
pub(crate) result: Result<Vec<WorkingFileChange>, String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -45,7 +53,7 @@ pub(crate) enum Change {
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
request: Option<Request>,
|
||||
result: Option<(u64, ResultBatch)>,
|
||||
result: Option<(u64, ResultBatch, Option<PickerChanges>)>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct Shared {
|
||||
@@ -65,6 +73,7 @@ impl Worker {
|
||||
let thread = thread::Builder::new()
|
||||
.name("vamp-gutter".into())
|
||||
.spawn(move || {
|
||||
let mut picker_reader = WorkingFileReader::default();
|
||||
loop {
|
||||
let Ok(mut state) = worker.state.lock() else {
|
||||
return;
|
||||
@@ -78,7 +87,7 @@ impl Worker {
|
||||
if worker.stopped.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
let (generation, id, files) = state.request.take().unwrap();
|
||||
let (generation, id, files, picker) = state.request.take().unwrap();
|
||||
drop(state);
|
||||
let cancelled = || {
|
||||
worker.stopped.load(Ordering::Acquire)
|
||||
@@ -91,11 +100,15 @@ impl Worker {
|
||||
}
|
||||
results.push((buffer, read_file(&path, &contents, &cancelled)));
|
||||
}
|
||||
let picker = picker.map(|(root, buffers)| PickerChanges {
|
||||
result: picker_reader.read(&root, &buffers, &cancelled),
|
||||
root,
|
||||
});
|
||||
let Ok(mut state) = worker.state.lock() else {
|
||||
return;
|
||||
};
|
||||
if !cancelled() {
|
||||
state.result = Some((id, results));
|
||||
state.result = Some((id, results, picker));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
@@ -105,12 +118,31 @@ impl Worker {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn request(&mut self, id: u64, files: Files) -> io::Result<()> {
|
||||
pub(crate) fn request(
|
||||
&mut self,
|
||||
id: u64,
|
||||
files: Files,
|
||||
picker: Option<PickerRequest>,
|
||||
) -> io::Result<()> {
|
||||
let size = files.iter().try_fold(0usize, |size, (_, path, text)| {
|
||||
size.checked_add(text.len())?
|
||||
.checked_add(path.as_os_str().len())
|
||||
});
|
||||
if files.len() > FILE_COUNT_LIMIT || size.is_none_or(|size| size > BATCH_LIMIT) {
|
||||
let picker_size = picker.as_ref().map_or(Some(0), |(root, buffers)| {
|
||||
buffers
|
||||
.iter()
|
||||
.try_fold(root.as_os_str().len(), |size, (path, text)| {
|
||||
size.checked_add(text.len())?
|
||||
.checked_add(path.as_os_str().len())
|
||||
})
|
||||
});
|
||||
let count = files.len() + picker.as_ref().map_or(0, |(_, buffers)| buffers.len());
|
||||
if count > FILE_COUNT_LIMIT
|
||||
|| size
|
||||
.zip(picker_size)
|
||||
.and_then(|(files, picker)| files.checked_add(picker))
|
||||
.is_none_or(|size| size > BATCH_LIMIT)
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Gutter batch exceeds 64 MiB or 1024 files",
|
||||
@@ -126,13 +158,13 @@ impl Worker {
|
||||
.generation
|
||||
.fetch_add(1, Ordering::AcqRel)
|
||||
.wrapping_add(1);
|
||||
state.request = Some((generation, id, files));
|
||||
state.request = Some((generation, id, files, picker));
|
||||
state.result = None;
|
||||
self.shared.wake.notify_one();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn poll(&mut self) -> Option<(u64, ResultBatch)> {
|
||||
pub(crate) fn poll(&mut self) -> Option<(u64, ResultBatch, Option<PickerChanges>)> {
|
||||
self.shared.state.lock().ok()?.result.take()
|
||||
}
|
||||
}
|
||||
@@ -766,11 +798,13 @@ mod tests {
|
||||
(5, path.clone(), "first\nchanged\nthird\n".into()),
|
||||
(8, path.clone(), Arc::clone(&oversized)),
|
||||
],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
loop {
|
||||
if let Some((id, result)) = worker.poll() {
|
||||
if let Some((id, result, picker)) = worker.poll() {
|
||||
assert!(picker.is_none());
|
||||
assert_eq!(id, 19);
|
||||
assert_eq!(result[0].0, 5);
|
||||
assert_eq!(
|
||||
@@ -788,10 +822,64 @@ mod tests {
|
||||
}
|
||||
assert_eq!(
|
||||
worker
|
||||
.request(20, vec![(0, path, "x".repeat(BATCH_LIMIT + 1).into())])
|
||||
.request(
|
||||
20,
|
||||
vec![(0, path, "x".repeat(BATCH_LIMIT + 1).into())],
|
||||
None
|
||||
)
|
||||
.unwrap_err()
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidInput
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picker_status_is_optional_and_errors_do_not_discard_line_results() {
|
||||
let (temp, path) = git_fixture();
|
||||
let mut worker = Worker::start().unwrap();
|
||||
worker
|
||||
.request(
|
||||
1,
|
||||
vec![],
|
||||
Some((
|
||||
temp.0.clone(),
|
||||
vec![(path.clone(), "dirty buffer\n".into())],
|
||||
)),
|
||||
)
|
||||
.unwrap();
|
||||
loop {
|
||||
if let Some((id, lines, picker)) = worker.poll() {
|
||||
assert_eq!(id, 1);
|
||||
assert!(lines.is_empty());
|
||||
let picker = picker.unwrap();
|
||||
assert_eq!(picker.root, temp.0);
|
||||
let changes = picker.result.unwrap();
|
||||
assert_eq!(changes.len(), 1);
|
||||
assert_eq!(changes[0].path, path.strip_prefix(&temp.0).unwrap());
|
||||
assert_eq!(changes[0].status, crate::diff::FileStatus::Modified);
|
||||
break;
|
||||
}
|
||||
thread::yield_now();
|
||||
}
|
||||
worker
|
||||
.request(
|
||||
2,
|
||||
vec![(4, path.clone(), "first\nchanged\nthird\n".into())],
|
||||
Some((
|
||||
temp.0.clone(),
|
||||
vec![(path, "x".repeat(FILE_LIMIT + 1).into())],
|
||||
)),
|
||||
)
|
||||
.unwrap();
|
||||
loop {
|
||||
if let Some((id, lines, picker)) = worker.poll() {
|
||||
assert_eq!(id, 2);
|
||||
assert_eq!(lines[0].0, 4);
|
||||
assert!(lines[0].1.is_ok());
|
||||
assert!(picker.unwrap().result.unwrap_err().contains("16 MiB"));
|
||||
break;
|
||||
}
|
||||
thread::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user