feat: add local and remote repository diff views

This commit is contained in:
2026-10-10 23:01:34 +02:00
parent f6da7922a1
commit 81a2bf9a02
15 changed files with 4947 additions and 5 deletions
+154
View File
@@ -0,0 +1,154 @@
use std::{
fs,
path::{Path, PathBuf},
process::Command,
sync::atomic::{AtomicU64, Ordering},
thread,
time::{Duration, Instant},
};
use vamp::{diff::Phase, editor::Input, session::EditorSession};
static NEXT: AtomicU64 = AtomicU64::new(0);
struct Fixture(PathBuf);
impl Fixture {
fn new() -> Self {
let root = std::env::temp_dir().join(format!(
"vamp-diff-session-{}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed),
));
fs::create_dir_all(root.join("repo")).unwrap();
Self(root)
}
fn git(&self, args: &[&str]) -> String {
let output = Command::new("git")
.args(args)
.current_dir(self.0.join("repo"))
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_AUTHOR_NAME", "Diff Fixture")
.env("GIT_AUTHOR_EMAIL", "fixture@example.invalid")
.env("GIT_COMMITTER_NAME", "Diff Fixture")
.env("GIT_COMMITTER_EMAIL", "fixture@example.invalid")
.env("GIT_AUTHOR_DATE", "2000-01-01T00:00:00Z")
.env("GIT_COMMITTER_DATE", "2000-01-01T00:00:00Z")
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn wait_until(session: &mut EditorSession, ready: impl Fn(&EditorSession) -> bool) {
let deadline = Instant::now() + Duration::from_secs(10);
while !ready(session) {
session.poll();
assert!(
Instant::now() < deadline,
"diff state: {:?}",
session.editor().diff()
);
if let Some(diff) = session.editor().diff() {
assert!(diff.error.is_none(), "{:?}", diff.error);
}
thread::sleep(Duration::from_millis(1));
}
}
fn type_query(session: &mut EditorSession, text: &str) {
for ch in text.chars() {
session.handle(Input::Char(ch));
}
}
#[test]
fn typed_diff_compares_disk_not_unsaved_buffers_and_preserves_undo() {
let fixture = Fixture::new();
fixture.git(&["init", "-q"]);
let source = fixture.0.join("repo/buffer.rs");
fs::write(&source, "before\n").unwrap();
fixture.git(&["add", "buffer.rs"]);
fixture.git(&["commit", "-qm", "base"]);
let base = fixture.git(&["rev-parse", "HEAD"]);
fs::write(&source, "after\n").unwrap();
let config = fixture.0.join("config.rb");
fs::write(
&config,
"Vamp.configure do |config|\n config.clipboard_mode = 'separate'\n config.bind 'leader d', config.core.diff\nend\n",
)
.unwrap();
let mut session =
EditorSession::open(Some(source.clone()), vec!["text".into()], Some(&config)).unwrap();
assert!(session.editor().startup_diagnostic().is_none());
for input in [Input::Char('i'), Input::Char('u'), Input::Escape] {
session.handle(input);
}
let cursor = session.editor().cursor();
session.handle(Input::Char(' '));
session.handle(Input::Char('d'));
wait_until(&mut session, |session| {
session
.editor()
.diff()
.is_some_and(|diff| diff.catalog.is_some() && !diff.loading)
});
type_query(&mut session, &base);
session.handle(Input::Down);
session.handle(Input::Enter);
assert_eq!(session.editor().diff().unwrap().phase, Phase::After);
session.handle(Input::Enter);
wait_until(&mut session, |session| {
session
.editor()
.diff()
.is_some_and(|diff| diff.phase == Phase::View)
});
let comparison = session
.editor()
.diff()
.unwrap()
.comparison
.as_ref()
.unwrap();
assert_eq!(comparison.files.len(), 1);
let file = &comparison.files[0];
assert_eq!(file.path, Path::new("buffer.rs"));
assert_eq!(file.before, ["before"]);
assert_eq!(file.after, ["after"]);
for input in [
Input::Char('i'),
Input::Char('x'),
Input::Paste("overwrite".into()),
] {
session.handle(input);
}
session.handle(Input::Escape);
assert!(session.editor().diff().is_none());
assert_eq!(session.editor().context().text, "uafter\n");
assert!(session.editor().dirty());
assert_eq!(session.editor().cursor(), cursor);
assert_eq!(fs::read_to_string(&source).unwrap(), "after\n");
session.handle(Input::Char('u'));
assert_eq!(session.editor().context().text, "after\n");
session.execute(":diff");
assert!(session.editor().diff().is_some());
session.handle(Input::Escape);
session.poll();
assert!(session.editor().diff().is_none());
assert_eq!(fixture.git(&["rev-parse", "HEAD"]), base);
assert_eq!(fixture.git(&["status", "--porcelain"]), "M buffer.rs");
}
+1022
View File
File diff suppressed because it is too large Load Diff