ui: Add buttons to switch diff view when diffing buffers (#64529)
# Objective - This PR enables users to switch between unified/split diff views when comparing buffers. - Fixes #64419 . ## Solution - The unified/split toggle (`DiffStyleControls`) is normally surfaced automatically by `BufferSearchBar`, which detects any active item exposing a `SplittableEditor`. That auto-detection is gated to multi-buffer views, since it's paired with an expand/collapse-all-hunks control that only makes sense across multiple files. `FileDiffView` (used by `zed --diff <old> <new>`) is a singleton buffer, so it's intentionally excluded from that generic path. - Git's own single-file diff view, [`SoloDiffView`](https://github.com/zed-industries/zed/blob/f2e33599724432f329f9e23b353a8fb28cd6ece3/crates/git_ui/src/solo_diff_view.rs#L52), hits the same limitation and works around it with a small, dedicated `ToolbarItemView` (`SoloDiffStyleToolbar`) that renders `DiffStyleControls` directly, independent of `BufferSearchBar`. - This PR follows the same pattern: a new `FileDiffStyleToolbar` (in `git_ui_core::file_diff_view`) activates whenever a `FileDiffView` is the focused pane item, and renders just the unified/split toggle, no expand/collapse or file-status controls, since neither concept applies to a plain two-file diff. It's registered in `initialize_pane` alongside the existing git toolbars. ## Testing - Did you test these changes? If so, how? Ran `cargo test -p git_ui_core`, all passed. I've also attached the results of manual testing in the Showcase section below. - Are there any parts that need more testing? I think more tests can be added. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase Adds a unified/split toggle to the diff view opened via `zed --diff <old> <new>`, matching the toggle already available in git's diff views. <details> <summary>Click to view before/after</summary> **Before**: <img width="1920" height="1080" alt="screenshot_20260921_020610" src="https://github.com/user-attachments/assets/92cbcf75-0cac-4555-8d2d-ce927064515f" /> **After**: <img width="1920" height="1080" alt="screenshot_20260921_020355" src="https://github.com/user-attachments/assets/56f9ebc6-11df-41cc-a44d-99bfa8caa130" /> </details> --- Release Notes: - Added a unified/split toggle to the diff view opened via `zed --diff <old> <new>` command. --------- Co-authored-by: Kirill Bulatov <mail4score@gmail.com> Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
1 parent
c1053ff5fd
commit
14d3c2b61c
3 files changed
+111
-9
No files matched your search
@@ -3,13 +3,13 @@
|
||||
use anyhow::Result;
|
||||
use buffer_diff::BufferDiff;
|
||||
use editor::{
|
||||
Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer, MultiBuffer,
|
||||
SplittableEditor,
|
||||
DiffStyleControls, Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer,
|
||||
MultiBuffer, SplittableEditor,
|
||||
};
|
||||
use futures::{FutureExt, select_biased};
|
||||
use gpui::{
|
||||
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, Font,
|
||||
IntoElement, Render, Task, WeakEntity, Window,
|
||||
App, AppContext as _, AsyncApp, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Font, IntoElement, Render, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::{Buffer, HighlightedText, Point};
|
||||
use project::{Project, ProjectPath};
|
||||
@@ -21,10 +21,11 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{Color, Icon, IconName, SharedString};
|
||||
use ui::{Color, Icon, IconName, SharedString, prelude::*};
|
||||
use util::paths::PathExt as _;
|
||||
use workspace::{
|
||||
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||
Workspace,
|
||||
item::{ItemEvent, SaveOptions},
|
||||
searchable::SearchableItemHandle,
|
||||
};
|
||||
@@ -128,6 +129,22 @@ impl FileDiffView {
|
||||
splittable
|
||||
});
|
||||
|
||||
let include_root = project.read(cx).visible_worktrees(cx).count() > 1;
|
||||
let breadcrumb_path = |buffer: &Entity<Buffer>| {
|
||||
buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.resolve_file_path(include_root, cx)
|
||||
.unwrap_or_else(|| MultiBuffer::DEFAULT_TITLE.to_string())
|
||||
};
|
||||
let old_path = breadcrumb_path(&old_buffer);
|
||||
let new_path = breadcrumb_path(&new_buffer);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.rhs_editor().update(cx, |editor, _| {
|
||||
editor.set_breadcrumb_header(format!("{old_path} ↔ {new_path}"));
|
||||
});
|
||||
});
|
||||
|
||||
let (buffer_changes_tx, mut buffer_changes_rx) = watch::channel(());
|
||||
|
||||
// The buffers' languages may load after the diff was built, e.g. when
|
||||
@@ -394,6 +411,57 @@ impl Render for FileDiffView {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileDiffStyleToolbar {
|
||||
file_diff: Option<WeakEntity<FileDiffView>>,
|
||||
}
|
||||
|
||||
impl FileDiffStyleToolbar {
|
||||
pub fn new(_: &mut Context<Self>) -> Self {
|
||||
Self { file_diff: None }
|
||||
}
|
||||
|
||||
fn file_diff(&self) -> Option<Entity<FileDiffView>> {
|
||||
self.file_diff.as_ref()?.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for FileDiffStyleToolbar {}
|
||||
|
||||
impl ToolbarItemView for FileDiffStyleToolbar {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
self.file_diff = active_pane_item
|
||||
.and_then(|item| item.act_as::<FileDiffView>(cx))
|
||||
.map(|entity| entity.downgrade());
|
||||
|
||||
if self.file_diff.is_some() {
|
||||
ToolbarItemLocation::PrimaryLeft
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FileDiffStyleToolbar {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(file_diff) = self.file_diff() else {
|
||||
return Empty.into_any_element();
|
||||
};
|
||||
|
||||
let editor = file_diff.read(cx).editor.clone();
|
||||
|
||||
h_flex()
|
||||
.pl_0p5()
|
||||
.gap_1()
|
||||
.child(DiffStyleControls::new(editor))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -560,7 +628,34 @@ mod tests {
|
||||
path!("test/new_file.txt")
|
||||
)
|
||||
);
|
||||
})
|
||||
let (breadcrumbs, _) = diff_view.breadcrumbs(cx).unwrap();
|
||||
assert_eq!(
|
||||
breadcrumbs
|
||||
.iter()
|
||||
.map(|crumb| crumb.text.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["old_file.txt ↔ new_file.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
let toolbar = cx.new(FileDiffStyleToolbar::new);
|
||||
let location = toolbar.update_in(cx, |toolbar, window, cx| {
|
||||
toolbar.set_active_pane_item(Some(&diff_view), window, cx)
|
||||
});
|
||||
assert_eq!(location, ToolbarItemLocation::PrimaryLeft);
|
||||
assert_eq!(
|
||||
toolbar.read_with(cx, |toolbar, _| toolbar.file_diff()),
|
||||
Some(diff_view.clone())
|
||||
);
|
||||
|
||||
let location = toolbar.update_in(cx, |toolbar, window, cx| {
|
||||
toolbar.set_active_pane_item(None, window, cx)
|
||||
});
|
||||
assert_eq!(location, ToolbarItemLocation::Hidden);
|
||||
assert_eq!(
|
||||
toolbar.read_with(cx, |toolbar, _| toolbar.file_diff()),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -38,6 +38,7 @@ use git_ui::project_diff::ProjectDiffToolbar;
|
||||
use git_ui::solo_diff_view::{SoloDiffGitToolbar, SoloDiffStyleToolbar};
|
||||
use git_ui::staged_diff::StagedDiffToolbar;
|
||||
use git_ui::unstaged_diff::UnstagedDiffToolbar;
|
||||
use git_ui_core::file_diff_view::FileDiffStyleToolbar;
|
||||
use gpui::{
|
||||
Action, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent,
|
||||
Element, Entity, FocusHandle, Focusable, Image, ImageFormat, KeyBinding, ParentElement,
|
||||
@@ -1466,6 +1467,8 @@ fn initialize_pane(
|
||||
toolbar.add_item(multibuffer_hint, window, cx);
|
||||
let solo_diff_style_toolbar = cx.new(SoloDiffStyleToolbar::new);
|
||||
toolbar.add_item(solo_diff_style_toolbar, window, cx);
|
||||
let file_diff_style_toolbar = cx.new(FileDiffStyleToolbar::new);
|
||||
toolbar.add_item(file_diff_style_toolbar, window, cx);
|
||||
let breadcrumbs = cx.new(|_| Breadcrumbs::new());
|
||||
toolbar.add_item(breadcrumbs, window, cx);
|
||||
let buffer_search_bar = cx.new(|cx| {
|
||||
|
||||
+6
-2
@@ -4,6 +4,8 @@ set -euo pipefail
|
||||
source script/lib/blob-store.sh
|
||||
|
||||
build_flag="--release"
|
||||
zed_features=""
|
||||
remote_server_features=""
|
||||
target_dir="release"
|
||||
open_result=false
|
||||
local_install=false
|
||||
@@ -34,6 +36,8 @@ do
|
||||
d)
|
||||
export CARGO_INCREMENTAL=true
|
||||
build_flag="";
|
||||
zed_features="--features util/debug-embed"
|
||||
remote_server_features="--features debug-embed"
|
||||
target_dir="debug"
|
||||
;;
|
||||
i) local_install=true;;
|
||||
@@ -87,10 +91,10 @@ script/generate-licenses
|
||||
rustup target add $target_triple
|
||||
|
||||
echo "Compiling zed binaries"
|
||||
cargo --config .cargo/bundle-config.toml build ${build_flag} --package zed --package cli --target $target_triple
|
||||
cargo --config .cargo/bundle-config.toml build ${build_flag} ${zed_features} --package zed --package cli --target $target_triple
|
||||
# Build remote_server in separate invocation to prevent feature unification from other crates
|
||||
# from influencing dynamic libraries required by it.
|
||||
cargo --config .cargo/bundle-config.toml build ${build_flag} --package remote_server --target $target_triple
|
||||
cargo --config .cargo/bundle-config.toml build ${build_flag} ${remote_server_features} --package remote_server --target $target_triple
|
||||
|
||||
echo "Creating application bundle"
|
||||
pushd crates/zed
|
||||
|
||||
Reference in new issue
Block a user