Temporarily revert git panel diff editor feature (#22733)

The existing code was causing us to constantly re-scan files when
anything changed in the project. Temporarily revert this as we're about
to rework this entire UI with the new primitives.

follow up to https://github.com/zed-industries/zed/pull/22329

Release Notes:

- N/A

---------

Co-authored-by: Cole <cole@zed.dev>
This commit is contained in:
Mikayla Maki 2025-01-06 14:09:05 -08:00 committed by GitHub
parent 5ec924828e
commit 3c430af31a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 268 additions and 296 deletions

3
Cargo.lock generated
View file

@ -5178,11 +5178,8 @@ dependencies = [
"anyhow",
"collections",
"db",
"editor",
"futures 0.3.31",
"git",
"gpui",
"language",
"menu",
"project",
"schemars",

View file

@ -16,11 +16,8 @@ path = "src/git_ui.rs"
anyhow.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
futures.workspace = true
git.workspace = true
gpui.workspace = true
language.workspace = true
menu.workspace = true
project.workspace = true
schemars.workspace = true

View file

@ -1,11 +1,7 @@
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
use anyhow::{Context as _, Result};
use anyhow::Result;
use db::kvp::KEY_VALUE_STORE;
use editor::{
scroll::{Autoscroll, AutoscrollStrategy},
Editor, MultiBuffer, DEFAULT_MULTIBUFFER_CONTEXT,
};
use git::{
diff::DiffHunk,
repository::{GitFileStatus, RepoPath},
@ -17,9 +13,8 @@ use gpui::{
ListHorizontalSizingBehavior, ListSizingBehavior, Model, Modifiers, ModifiersChangedEvent,
MouseButton, ScrollStrategy, Stateful, Task, UniformListScrollHandle, View, WeakView,
};
use language::{Buffer, BufferRow, OffsetRangeExt};
use menu::{SelectNext, SelectPrev};
use project::{EntryKind, Fs, Project, ProjectEntryId, ProjectPath, WorktreeId};
use project::{EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use std::{
@ -40,7 +35,7 @@ use ui::{
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
ItemHandle, Workspace,
Workspace,
};
use worktree::StatusEntry;
@ -93,7 +88,7 @@ struct SerializedGitPanel {
}
pub struct GitPanel {
workspace: WeakView<Workspace>,
// workspace: WeakView<Workspace>,
current_modifiers: Modifiers,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
@ -111,8 +106,8 @@ pub struct GitPanel {
// not hidden by folding or such
visible_entries: Vec<WorktreeEntries>,
width: Option<Pixels>,
git_diff_editor: Option<View<Editor>>,
git_diff_editor_updates: Task<()>,
// git_diff_editor: Option<View<Editor>>,
// git_diff_editor_updates: Task<()>,
reveal_in_editor: Task<()>,
}
@ -120,7 +115,7 @@ pub struct GitPanel {
struct WorktreeEntries {
worktree_id: WorktreeId,
// TODO support multiple repositories per worktree
work_directory: worktree::WorkDirectory,
// work_directory: worktree::WorkDirectory,
visible_entries: Vec<GitPanelEntry>,
paths: Rc<OnceCell<HashSet<RepoPath>>>,
}
@ -160,7 +155,7 @@ impl GitPanel {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let fs = workspace.app_state().fs.clone();
let weak_workspace = workspace.weak_handle();
// let weak_workspace = workspace.weak_handle();
let project = workspace.project().clone();
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
@ -171,9 +166,6 @@ impl GitPanel {
})
.detach();
cx.subscribe(&project, |this, _, event, cx| match event {
project::Event::GitRepositoryUpdated => {
this.update_visible_entries(None, None, cx);
}
project::Event::WorktreeRemoved(_id) => {
// this.expanded_dir_ids.remove(id);
this.update_visible_entries(None, None, cx);
@ -190,11 +182,11 @@ impl GitPanel {
cx.notify();
}
project::Event::Closed => {
this.git_diff_editor_updates = Task::ready(());
// this.git_diff_editor_updates = Task::ready(());
this.reveal_in_editor = Task::ready(());
// this.expanded_dir_ids.clear();
this.visible_entries.clear();
this.git_diff_editor = None;
// this.git_diff_editor = None;
}
_ => {}
})
@ -203,7 +195,7 @@ impl GitPanel {
let scroll_handle = UniformListScrollHandle::new();
let mut git_panel = Self {
workspace: weak_workspace,
// workspace: weak_workspace,
focus_handle: cx.focus_handle(),
fs,
pending_serialization: Task::ready(None),
@ -216,8 +208,8 @@ impl GitPanel {
selected_item: None,
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
git_diff_editor: Some(diff_display_editor(cx)),
git_diff_editor_updates: Task::ready(()),
// git_diff_editor: Some(diff_display_editor(cx)),
// git_diff_editor_updates: Task::ready(()),
reveal_in_editor: Task::ready(()),
project,
};
@ -527,6 +519,7 @@ impl GitPanel {
// TODO: Update expanded directory state
// TODO: Updates happen in the main loop, could be long for large workspaces
#[track_caller]
fn update_visible_entries(
&mut self,
for_worktree: Option<WorktreeId>,
@ -562,10 +555,10 @@ impl GitPanel {
let mut visible_worktree_entries = Vec::new();
// Only use the first repository for now
let repositories = snapshot.repositories().take(1);
let mut work_directory = None;
// let mut work_directory = None;
for repository in repositories {
visible_worktree_entries.extend(repository.status());
work_directory = Some(worktree::WorkDirectory::clone(repository));
// work_directory = Some(worktree::WorkDirectory::clone(repository));
}
// TODO use the GitTraversal
@ -581,7 +574,7 @@ impl GitPanel {
if !visible_worktree_entries.is_empty() {
self.visible_entries.push(WorktreeEntries {
worktree_id,
work_directory: work_directory.unwrap(),
// work_directory: work_directory.unwrap(),
visible_entries: visible_worktree_entries
.into_iter()
.map(|entry| GitPanelEntry {
@ -615,166 +608,166 @@ impl GitPanel {
// );
// }
let project = self.project.downgrade();
self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
cx.background_executor()
.timer(UPDATE_DEBOUNCE)
.await;
let Some(project_buffers) = git_panel
.update(&mut cx, |git_panel, cx| {
futures::future::join_all(git_panel.visible_entries.iter_mut().flat_map(
|worktree_entries| {
worktree_entries
.visible_entries
.iter()
.filter_map(|entry| {
let git_status = entry.status;
let entry_hunks = entry.hunks.clone();
let (entry_path, unstaged_changes_task) =
project.update(cx, |project, cx| {
let entry_path = ProjectPath {
worktree_id: worktree_entries.worktree_id,
path: worktree_entries.work_directory.unrelativize(&entry.repo_path)?,
};
let open_task =
project.open_path(entry_path.clone(), cx);
let unstaged_changes_task =
cx.spawn(|project, mut cx| async move {
let (_, opened_model) = open_task
.await
.context("opening buffer")?;
let buffer = opened_model
.downcast::<Buffer>()
.map_err(|_| {
anyhow::anyhow!(
"accessing buffer for entry"
)
})?;
// TODO added files have noop changes and those are not expanded properly in the multi buffer
let unstaged_changes = project
.update(&mut cx, |project, cx| {
project.open_unstaged_changes(
buffer.clone(),
cx,
)
})?
.await
.context("opening unstaged changes")?;
// let project = self.project.downgrade();
// self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
// cx.background_executor()
// .timer(UPDATE_DEBOUNCE)
// .await;
// let Some(project_buffers) = git_panel
// .update(&mut cx, |git_panel, cx| {
// futures::future::join_all(git_panel.visible_entries.iter_mut().flat_map(
// |worktree_entries| {
// worktree_entries
// .visible_entries
// .iter()
// .filter_map(|entry| {
// let git_status = entry.status;
// let entry_hunks = entry.hunks.clone();
// let (entry_path, unstaged_changes_task) =
// project.update(cx, |project, cx| {
// let entry_path = ProjectPath {
// worktree_id: worktree_entries.worktree_id,
// path: worktree_entries.work_directory.unrelativize(&entry.repo_path)?,
// };
// let open_task =
// project.open_path(entry_path.clone(), cx);
// let unstaged_changes_task =
// cx.spawn(|project, mut cx| async move {
// let (_, opened_model) = open_task
// .await
// .context("opening buffer")?;
// let buffer = opened_model
// .downcast::<Buffer>()
// .map_err(|_| {
// anyhow::anyhow!(
// "accessing buffer for entry"
// )
// })?;
// // TODO added files have noop changes and those are not expanded properly in the multi buffer
// let unstaged_changes = project
// .update(&mut cx, |project, cx| {
// project.open_unstaged_changes(
// buffer.clone(),
// cx,
// )
// })?
// .await
// .context("opening unstaged changes")?;
let hunks = cx.update(|cx| {
entry_hunks
.get_or_init(|| {
match git_status {
GitFileStatus::Added => {
let buffer_snapshot = buffer.read(cx).snapshot();
let entire_buffer_range =
buffer_snapshot.anchor_after(0)
..buffer_snapshot
.anchor_before(
buffer_snapshot.len(),
);
let entire_buffer_point_range =
entire_buffer_range
.clone()
.to_point(&buffer_snapshot);
// let hunks = cx.update(|cx| {
// entry_hunks
// .get_or_init(|| {
// match git_status {
// GitFileStatus::Added => {
// let buffer_snapshot = buffer.read(cx).snapshot();
// let entire_buffer_range =
// buffer_snapshot.anchor_after(0)
// ..buffer_snapshot
// .anchor_before(
// buffer_snapshot.len(),
// );
// let entire_buffer_point_range =
// entire_buffer_range
// .clone()
// .to_point(&buffer_snapshot);
vec![DiffHunk {
row_range: entire_buffer_point_range
.start
.row
..entire_buffer_point_range
.end
.row,
buffer_range: entire_buffer_range,
diff_base_byte_range: 0..0,
}]
}
GitFileStatus::Modified => {
let buffer_snapshot =
buffer.read(cx).snapshot();
unstaged_changes.read(cx)
.diff_to_buffer
.hunks_in_row_range(
0..BufferRow::MAX,
&buffer_snapshot,
)
.collect()
}
// TODO support these
GitFileStatus::Conflict | GitFileStatus::Deleted | GitFileStatus::Untracked => Vec::new(),
}
}).clone()
})?;
// vec![DiffHunk {
// row_range: entire_buffer_point_range
// .start
// .row
// ..entire_buffer_point_range
// .end
// .row,
// buffer_range: entire_buffer_range,
// diff_base_byte_range: 0..0,
// }]
// }
// GitFileStatus::Modified => {
// let buffer_snapshot =
// buffer.read(cx).snapshot();
// unstaged_changes.read(cx)
// .diff_to_buffer
// .hunks_in_row_range(
// 0..BufferRow::MAX,
// &buffer_snapshot,
// )
// .collect()
// }
// // TODO support these
// GitFileStatus::Conflict | GitFileStatus::Deleted | GitFileStatus::Untracked => Vec::new(),
// }
// }).clone()
// })?;
anyhow::Ok((buffer, unstaged_changes, hunks))
});
Some((entry_path, unstaged_changes_task))
}).ok()??;
Some((entry_path, unstaged_changes_task))
})
.map(|(entry_path, open_task)| async move {
(entry_path, open_task.await)
})
.collect::<Vec<_>>()
},
))
})
.ok()
else {
return;
};
// anyhow::Ok((buffer, unstaged_changes, hunks))
// });
// Some((entry_path, unstaged_changes_task))
// }).ok()??;
// Some((entry_path, unstaged_changes_task))
// })
// .map(|(entry_path, open_task)| async move {
// (entry_path, open_task.await)
// })
// .collect::<Vec<_>>()
// },
// ))
// })
// .ok()
// else {
// return;
// };
let project_buffers = project_buffers.await;
if project_buffers.is_empty() {
return;
}
let mut change_sets = Vec::with_capacity(project_buffers.len());
if let Some(buffer_update_task) = git_panel
.update(&mut cx, |git_panel, cx| {
let editor = git_panel.git_diff_editor.clone()?;
let multi_buffer = editor.read(cx).buffer().clone();
let mut buffers_with_ranges = Vec::with_capacity(project_buffers.len());
for (buffer_path, open_result) in project_buffers {
if let Some((buffer, unstaged_changes, diff_hunks)) = open_result
.with_context(|| format!("opening buffer {buffer_path:?}"))
.log_err()
{
change_sets.push(unstaged_changes);
buffers_with_ranges.push((
buffer,
diff_hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect(),
));
}
}
// let project_buffers = project_buffers.await;
// if project_buffers.is_empty() {
// return;
// }
// let mut change_sets = Vec::with_capacity(project_buffers.len());
// if let Some(buffer_update_task) = git_panel
// .update(&mut cx, |git_panel, cx| {
// let editor = git_panel.git_diff_editor.clone()?;
// let multi_buffer = editor.read(cx).buffer().clone();
// let mut buffers_with_ranges = Vec::with_capacity(project_buffers.len());
// for (buffer_path, open_result) in project_buffers {
// if let Some((buffer, unstaged_changes, diff_hunks)) = open_result
// .with_context(|| format!("opening buffer {buffer_path:?}"))
// .log_err()
// {
// change_sets.push(unstaged_changes);
// buffers_with_ranges.push((
// buffer,
// diff_hunks
// .into_iter()
// .map(|hunk| hunk.buffer_range)
// .collect(),
// ));
// }
// }
Some(multi_buffer.update(cx, |multi_buffer, cx| {
multi_buffer.clear(cx);
multi_buffer.push_multiple_excerpts_with_context_lines(
buffers_with_ranges,
DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
}))
})
.ok().flatten()
{
buffer_update_task.await;
git_panel
.update(&mut cx, |git_panel, cx| {
if let Some(diff_editor) = git_panel.git_diff_editor.as_ref() {
diff_editor.update(cx, |editor, cx| {
for change_set in change_sets {
editor.add_change_set(change_set, cx);
}
});
}
})
.ok();
}
});
// Some(multi_buffer.update(cx, |multi_buffer, cx| {
// multi_buffer.clear(cx);
// multi_buffer.push_multiple_excerpts_with_context_lines(
// buffers_with_ranges,
// DEFAULT_MULTIBUFFER_CONTEXT,
// cx,
// )
// }))
// })
// .ok().flatten()
// {
// buffer_update_task.await;
// git_panel
// .update(&mut cx, |git_panel, cx| {
// if let Some(diff_editor) = git_panel.git_diff_editor.as_ref() {
// diff_editor.update(cx, |editor, cx| {
// for change_set in change_sets {
// editor.add_change_set(change_set, cx);
// }
// });
// }
// })
// .ok();
// }
// });
cx.notify();
}
@ -1058,94 +1051,94 @@ impl GitPanel {
fn reveal_entry_in_git_editor(
&mut self,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
change_focus: bool,
debounce: Option<Duration>,
cx: &mut ViewContext<Self>,
_hunks: Rc<OnceCell<Vec<DiffHunk>>>,
_change_focus: bool,
_debounce: Option<Duration>,
_cx: &mut ViewContext<Self>,
) {
let workspace = self.workspace.clone();
let Some(diff_editor) = self.git_diff_editor.clone() else {
return;
};
self.reveal_in_editor = cx.spawn(|_, mut cx| async move {
if let Some(debounce) = debounce {
cx.background_executor().timer(debounce).await;
}
// let workspace = self.workspace.clone();
// let Some(diff_editor) = self.git_diff_editor.clone() else {
// return;
// };
// self.reveal_in_editor = cx.spawn(|_, mut cx| async move {
// if let Some(debounce) = debounce {
// cx.background_executor().timer(debounce).await;
// }
let Some(editor) = workspace
.update(&mut cx, |workspace, cx| {
let git_diff_editor = workspace
.items_of_type::<Editor>(cx)
.find(|editor| &diff_editor == editor);
match git_diff_editor {
Some(existing_editor) => {
workspace.activate_item(&existing_editor, true, change_focus, cx);
existing_editor
}
None => {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(
diff_editor.boxed_clone(),
true,
change_focus,
None,
cx,
)
});
diff_editor.clone()
}
}
})
.ok()
else {
return;
};
// let Some(editor) = workspace
// .update(&mut cx, |workspace, cx| {
// let git_diff_editor = workspace
// .items_of_type::<Editor>(cx)
// .find(|editor| &diff_editor == editor);
// match git_diff_editor {
// Some(existing_editor) => {
// workspace.activate_item(&existing_editor, true, change_focus, cx);
// existing_editor
// }
// None => {
// workspace.active_pane().update(cx, |pane, cx| {
// pane.add_item(
// ` diff_editor.boxed_clone(),
// true,
// change_focus,
// None,
// cx,
// )
// });
// diff_editor.clone()
// }
// }
// })
// .ok()
// else {
// return;
// };
if let Some(first_hunk) = hunks.get().and_then(|hunks| hunks.first()) {
let hunk_buffer_range = &first_hunk.buffer_range;
if let Some(buffer_id) = hunk_buffer_range
.start
.buffer_id
.or_else(|| first_hunk.buffer_range.end.buffer_id)
{
editor
.update(&mut cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx);
let buffer = multi_buffer.buffer(buffer_id)?;
let buffer_snapshot = buffer.read(cx).snapshot();
let (excerpt_id, _) = multi_buffer
.excerpts_for_buffer(&buffer, cx)
.into_iter()
.find(|(_, excerpt)| {
hunk_buffer_range
.start
.cmp(&excerpt.context.start, &buffer_snapshot)
.is_ge()
&& hunk_buffer_range
.end
.cmp(&excerpt.context.end, &buffer_snapshot)
.is_le()
})?;
let multi_buffer_hunk_start = multi_buffer
.snapshot(cx)
.anchor_in_excerpt(excerpt_id, hunk_buffer_range.start)?;
editor.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Center)),
cx,
|s| {
s.select_ranges(Some(
multi_buffer_hunk_start..multi_buffer_hunk_start,
))
},
);
cx.notify();
Some(())
})
.ok()
.flatten();
}
}
});
// if let Some(first_hunk) = hunks.get().and_then(|hunks| hunks.first()) {
// let hunk_buffer_range = &first_hunk.buffer_range;
// if let Some(buffer_id) = hunk_buffer_range
// .start
// .buffer_id
// .or_else(|| first_hunk.buffer_range.end.buffer_id)
// {
// editor
// .update(&mut cx, |editor, cx| {
// let multi_buffer = editor.buffer().read(cx);
// let buffer = multi_buffer.buffer(buffer_id)?;
// let buffer_snapshot = buffer.read(cx).snapshot();
// let (excerpt_id, _) = multi_buffer
// .excerpts_for_buffer(&buffer, cx)
// .into_iter()
// .find(|(_, excerpt)| {
// hunk_buffer_range
// .start
// .cmp(&excerpt.context.start, &buffer_snapshot)
// .is_ge()
// && hunk_buffer_range
// .end
// .cmp(&excerpt.context.end, &buffer_snapshot)
// .is_le()
// })?;
// let multi_buffer_hunk_start = multi_buffer
// .snapshot(cx)
// .anchor_in_excerpt(excerpt_id, hunk_buffer_range.start)?;
// editor.change_selections(
// Some(Autoscroll::Strategy(AutoscrollStrategy::Center)),
// cx,
// |s| {
// s.select_ranges(Some(
// multi_buffer_hunk_start..multi_buffer_hunk_start,
// ))
// },
// );
// cx.notify();
// Some(())
// })
// .ok()
// .flatten();
// }
// }
// });
}
}
@ -1260,13 +1253,13 @@ impl Panel for GitPanel {
}
}
fn diff_display_editor(cx: &mut WindowContext) -> View<Editor> {
cx.new_view(|cx| {
let multi_buffer = cx.new_model(|_| {
MultiBuffer::new(language::Capability::ReadWrite).with_title("Project diff".to_string())
});
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
editor.set_expand_all_diff_hunks();
editor
})
}
// fn diff_display_editor(cx: &mut WindowContext) -> View<Editor> {
// cx.new_view(|cx| {
// let multi_buffer = cx.new_model(|_| {
// MultiBuffer::new(language::Capability::ReadWrite).with_title("Project diff".to_string())
// });
// let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
// editor.set_expand_all_diff_hunks();
// editor
// })
// }

View file

@ -3108,7 +3108,6 @@ impl LspStore {
WorktreeStoreEvent::WorktreeUpdateSent(worktree) => {
worktree.update(cx, |worktree, _cx| self.send_diagnostic_summaries(worktree));
}
WorktreeStoreEvent::GitRepositoryUpdated => {}
}
}

View file

@ -244,7 +244,6 @@ pub enum Event {
ActivateProjectPanel,
WorktreeAdded(WorktreeId),
WorktreeOrderChanged,
GitRepositoryUpdated,
WorktreeRemoved(WorktreeId),
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
WorktreeUpdatedGitRepositories(WorktreeId),
@ -2307,7 +2306,6 @@ impl Project {
}
WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
WorktreeStoreEvent::GitRepositoryUpdated => cx.emit(Event::GitRepositoryUpdated),
}
}

View file

@ -62,7 +62,6 @@ pub enum WorktreeStoreEvent {
WorktreeReleased(EntityId, WorktreeId),
WorktreeOrderChanged,
WorktreeUpdateSent(Model<Worktree>),
GitRepositoryUpdated,
}
impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {}
@ -376,17 +375,6 @@ impl WorktreeStore {
this.send_project_updates(cx);
})
.detach();
cx.subscribe(
worktree,
|_this, _, event: &worktree::Event, cx| match event {
worktree::Event::UpdatedGitRepositories(_) => {
cx.emit(WorktreeStoreEvent::GitRepositoryUpdated);
}
worktree::Event::DeletedEntry(_) | worktree::Event::UpdatedEntries(_) => {}
},
)
.detach();
}
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {

View file

@ -311,7 +311,7 @@ impl ProjectPanel {
this.update_visible_entries(None, cx);
cx.notify();
}
project::Event::GitRepositoryUpdated
project::Event::WorktreeUpdatedGitRepositories(_)
| project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded(_)
| project::Event::WorktreeOrderChanged => {