agent: Render diffs for the edit file tool (#29234)

This PR implements the `ToolCard` for the edit file tool, which allow us
to display an editor with a diff in the thread view with the changes
performed by the model.

- [x] Fix buffer sometimes displaying empty
- [x] Stop buffer from scrolling together with the thread
- [x] Fix multibuffer header sometimes appearing
- [x] Fix buffer height issue
- [x] Implement "full height" expand button
- [x] Add "Jump To File" functionality
- [x] Polish and refine styles

Release Notes:

- agent: Added diff preview cards in the thread view for edits performed
by the agent.

---------

Co-authored-by: João Marcos <marcospb19@hotmail.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Danilo Leal
2025-04-23 15:43:33 -03:00
committed by GitHub
co-authored by João Marcos Richard Feldman Agus Zubiaga Conrad Irwin
parent f6774ae60d
commit 8366cd0b52
40 changed files with 1003 additions and 316 deletions
+33 -1
View File
@@ -15,7 +15,7 @@ use std::{
num::NonZeroU64,
sync::{
Arc, Weak,
atomic::{AtomicUsize, Ordering::SeqCst},
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
},
thread::panicking,
};
@@ -572,6 +572,30 @@ impl AnyWeakEntity {
)
}
}
/// Creates a weak entity that can never be upgraded.
pub fn new_invalid() -> Self {
/// To hold the invariant that all ids are unique, and considering that slotmap
/// increases their IDs from `0`, we can decrease ours from `u64::MAX` so these
/// two will never conflict (u64 is way too large).
static UNIQUE_NON_CONFLICTING_ID_GENERATOR: AtomicU64 = AtomicU64::new(u64::MAX);
let entity_id = UNIQUE_NON_CONFLICTING_ID_GENERATOR.fetch_sub(1, SeqCst);
Self {
// Safety:
// Docs say this is safe but can be unspecified if slotmap changes the representation
// after `1.0.7`, that said, providing a valid entity_id here is not necessary as long
// as we guarantee that that `entity_id` is never used if `entity_ref_counts` equals
// to `Weak::new()` (that is, it's unable to upgrade), that is the invariant that
// actually needs to be hold true.
//
// And there is no sane reason to read an entity slot if `entity_ref_counts` can't be
// read in the first place, so we're good!
entity_id: entity_id.into(),
entity_type: TypeId::of::<()>(),
entity_ref_counts: Weak::new(),
}
}
}
impl std::fmt::Debug for AnyWeakEntity {
@@ -707,6 +731,14 @@ impl<T: 'static> WeakEntity<T> {
.map(|this| cx.read_entity(&this, read)),
)
}
/// Create a new weak entity that can never be upgraded.
pub fn new_invalid() -> Self {
Self {
any_entity: AnyWeakEntity::new_invalid(),
entity_type: PhantomData,
}
}
}
impl<T> Hash for WeakEntity<T> {