Allow viewing past commits in Zed (#27636)
This PR adds functionality for loading the diff for an arbitrary git commit, and displaying it in a tab. To retrieve the diff for the commit, I'm using a single `git cat-file --batch` invocation to efficiently load both the old and new versions of each file that was changed in the commit. Todo * Features * [x] Open the commit view when clicking the most recent commit message in the commit panel * [x] Open the commit view when clicking a SHA in a git blame column * [x] Open the commit view when clicking a SHA in a commit tooltip * [x] Make it work over RPC * [x] Allow buffer search in commit view * [x] Command palette action to open the commit for the current blame line * Styling * [x] Add a header that shows the author, timestamp, and the full commit message * [x] Remove stage/unstage buttons in commit view * [x] Truncate the commit message in the tab * Bugs * [x] Dedup commit tabs within a pane * [x] Add a tooltip to the tab Release Notes: - Added the ability to show past commits in Zed. You can view the most recent commit by clicking its message in the commit panel. And when viewing a git blame, you can show any commit by clicking its sha.
This commit is contained in:
parent
33912011b7
commit
8546dc101d
28 changed files with 1742 additions and 603 deletions
|
@ -40,6 +40,7 @@ pub struct BlockMap {
|
|||
buffer_header_height: u32,
|
||||
excerpt_header_height: u32,
|
||||
pub(super) folded_buffers: HashSet<BufferId>,
|
||||
buffers_with_disabled_headers: HashSet<BufferId>,
|
||||
}
|
||||
|
||||
pub struct BlockMapReader<'a> {
|
||||
|
@ -422,6 +423,7 @@ impl BlockMap {
|
|||
custom_blocks: Vec::new(),
|
||||
custom_blocks_by_id: TreeMap::default(),
|
||||
folded_buffers: HashSet::default(),
|
||||
buffers_with_disabled_headers: HashSet::default(),
|
||||
transforms: RefCell::new(transforms),
|
||||
wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
|
||||
buffer_header_height,
|
||||
|
@ -642,11 +644,8 @@ impl BlockMap {
|
|||
);
|
||||
|
||||
if buffer.show_headers() {
|
||||
blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
|
||||
self.buffer_header_height,
|
||||
self.excerpt_header_height,
|
||||
blocks_in_edit.extend(self.header_and_footer_blocks(
|
||||
buffer,
|
||||
&self.folded_buffers,
|
||||
(start_bound, end_bound),
|
||||
wrap_snapshot,
|
||||
));
|
||||
|
@ -714,10 +713,8 @@ impl BlockMap {
|
|||
}
|
||||
|
||||
fn header_and_footer_blocks<'a, R, T>(
|
||||
buffer_header_height: u32,
|
||||
excerpt_header_height: u32,
|
||||
&'a self,
|
||||
buffer: &'a multi_buffer::MultiBufferSnapshot,
|
||||
folded_buffers: &'a HashSet<BufferId>,
|
||||
range: R,
|
||||
wrap_snapshot: &'a WrapSnapshot,
|
||||
) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
|
||||
|
@ -728,73 +725,78 @@ impl BlockMap {
|
|||
let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
let excerpt_boundary = boundaries.next()?;
|
||||
let wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.row();
|
||||
loop {
|
||||
let excerpt_boundary = boundaries.next()?;
|
||||
let wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.row();
|
||||
|
||||
let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
|
||||
(None, next) => Some(next.buffer_id),
|
||||
(Some(prev), next) => {
|
||||
if prev.buffer_id != next.buffer_id {
|
||||
Some(next.buffer_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut height = 0;
|
||||
|
||||
if let Some(new_buffer_id) = new_buffer_id {
|
||||
let first_excerpt = excerpt_boundary.next.clone();
|
||||
if folded_buffers.contains(&new_buffer_id) {
|
||||
let mut last_excerpt_end_row = first_excerpt.end_row;
|
||||
|
||||
while let Some(next_boundary) = boundaries.peek() {
|
||||
if next_boundary.next.buffer_id == new_buffer_id {
|
||||
last_excerpt_end_row = next_boundary.next.end_row;
|
||||
let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
|
||||
(None, next) => Some(next.buffer_id),
|
||||
(Some(prev), next) => {
|
||||
if prev.buffer_id != next.buffer_id {
|
||||
Some(next.buffer_id)
|
||||
} else {
|
||||
break;
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut height = 0;
|
||||
|
||||
if let Some(new_buffer_id) = new_buffer_id {
|
||||
let first_excerpt = excerpt_boundary.next.clone();
|
||||
if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
|
||||
continue;
|
||||
}
|
||||
if self.folded_buffers.contains(&new_buffer_id) {
|
||||
let mut last_excerpt_end_row = first_excerpt.end_row;
|
||||
|
||||
while let Some(next_boundary) = boundaries.peek() {
|
||||
if next_boundary.next.buffer_id == new_buffer_id {
|
||||
last_excerpt_end_row = next_boundary.next.end_row;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
boundaries.next();
|
||||
}
|
||||
|
||||
boundaries.next();
|
||||
let wrap_end_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
last_excerpt_end_row.0,
|
||||
buffer.line_len(last_excerpt_end_row),
|
||||
),
|
||||
Bias::Right,
|
||||
)
|
||||
.row();
|
||||
|
||||
return Some((
|
||||
BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
|
||||
Block::FoldedBuffer {
|
||||
height: height + self.buffer_header_height,
|
||||
first_excerpt,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let wrap_end_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
last_excerpt_end_row.0,
|
||||
buffer.line_len(last_excerpt_end_row),
|
||||
),
|
||||
Bias::Right,
|
||||
)
|
||||
.row();
|
||||
|
||||
return Some((
|
||||
BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
|
||||
Block::FoldedBuffer {
|
||||
height: height + buffer_header_height,
|
||||
first_excerpt,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if new_buffer_id.is_some() {
|
||||
height += buffer_header_height;
|
||||
} else {
|
||||
height += excerpt_header_height;
|
||||
}
|
||||
if new_buffer_id.is_some() {
|
||||
height += self.buffer_header_height;
|
||||
} else {
|
||||
height += self.excerpt_header_height;
|
||||
}
|
||||
|
||||
Some((
|
||||
BlockPlacement::Above(WrapRow(wrap_row)),
|
||||
Block::ExcerptBoundary {
|
||||
excerpt: excerpt_boundary.next,
|
||||
height,
|
||||
starts_new_buffer: new_buffer_id.is_some(),
|
||||
},
|
||||
))
|
||||
return Some((
|
||||
BlockPlacement::Above(WrapRow(wrap_row)),
|
||||
Block::ExcerptBoundary {
|
||||
excerpt: excerpt_boundary.next,
|
||||
height,
|
||||
starts_new_buffer: new_buffer_id.is_some(),
|
||||
},
|
||||
));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1168,6 +1170,10 @@ impl BlockMapWriter<'_> {
|
|||
self.remove(blocks_to_remove);
|
||||
}
|
||||
|
||||
pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
|
||||
self.0.buffers_with_disabled_headers.insert(buffer_id);
|
||||
}
|
||||
|
||||
pub fn fold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
|
@ -3159,11 +3165,8 @@ mod tests {
|
|||
}));
|
||||
|
||||
// Note that this needs to be synced with the related section in BlockMap::sync
|
||||
expected_blocks.extend(BlockMap::header_and_footer_blocks(
|
||||
buffer_start_header_height,
|
||||
excerpt_header_height,
|
||||
expected_blocks.extend(block_map.header_and_footer_blocks(
|
||||
&buffer_snapshot,
|
||||
&block_map.folded_buffers,
|
||||
0..,
|
||||
&wraps_snapshot,
|
||||
));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue