use crate::{Thread, ThreadEvent, ToggleKeep};use anyhow::Result;use buffer_diff::DiffHunkStatus;use collections::HashSet;use editor::{ Direction, Editor, EditorEvent, MultiBuffer, ToPoint, actions::{GoToHunk, GoToPreviousHunk},};use gpui::{ Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, SharedString, Subscription, Task, WeakEntity, Window, prelude::*,};use language::{Capability, DiskState, OffsetRangeExt, Point};use multi_buffer::PathKey;use project::{Project, ProjectPath};use std::{ any::{Any, TypeId}, ops::Range, sync::Arc,};use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*};use workspace::{ Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{BreadcrumbText, ItemEvent, TabContentParams}, searchable::SearchableItemHandle,}; pub struct AssistantDiff { multibuffer: Entity<MultiBuffer>, editor: Entity<Editor>, thread: Entity<Thread>, focus_handle: FocusHandle, workspace: WeakEntity<Workspace>, title: SharedString, _subscriptions: Vec<Subscription>,} impl AssistantDiff { pub fn deploy( thread: Entity<Thread>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut App, ) -> Result<()> { let existing_diff = workspace.update(cx, |workspace, cx| { workspace .items_of_type::<AssistantDiff>(cx) .find(|diff| diff.read(cx).thread == thread) })?; if let Some(existing_diff) = existing_diff { workspace.update(cx, |workspace, cx| { workspace.activate_item(&existing_diff, true, true, window, cx); }) } else { let assistant_diff = cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx)); workspace.update(cx, |workspace, cx| { workspace.add_item_to_center(Box::new(assistant_diff), window, cx); }) } } pub fn new( thread: Entity<Thread>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); let project = thread.read(cx).project().clone(); let render_diff_hunk_controls = Arc::new({ let assistant_diff = cx.entity(); move |row, status: &DiffHunkStatus, hunk_range, is_created_file, line_height, _editor: &Entity<Editor>, window: &mut Window, cx: &mut App| { render_diff_hunk_controls( row, status, hunk_range, is_created_file, line_height, &assistant_diff, window, cx, ) } }); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx); editor.disable_inline_diagnostics(); editor.set_expand_all_diff_hunks(cx); editor.set_render_diff_hunk_controls(render_diff_hunk_controls, cx); editor.register_addon(AssistantDiffAddon); editor }); let action_log = thread.read(cx).action_log().clone(); let mut this = Self { _subscriptions: vec![ cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx) }), cx.subscribe(&thread, |this, _thread, event, cx| { this.handle_thread_event(event, cx) }), ], title: SharedString::default(), multibuffer, editor, thread, focus_handle, workspace, }; this.update_excerpts(window, cx); this.update_title(cx); this } fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { let thread = self.thread.read(cx); let changed_buffers = thread.action_log().read(cx).changed_buffers(cx); let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>(); for (buffer, changed) in changed_buffers { let Some(file) = buffer.read(cx).file().cloned() else { continue; }; let path_key = PathKey::namespaced("", file.full_path(cx).into()); paths_to_delete.remove(&path_key); let snapshot = buffer.read(cx).snapshot(); let diff = changed.diff.read(cx); let diff_hunk_ranges = diff .hunks_intersecting_range( language::Anchor::MIN..language::Anchor::MAX, &snapshot, cx, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) .collect::<Vec<_>>(); let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| { let was_empty = multibuffer.is_empty(); let is_excerpt_newly_added = multibuffer.set_excerpts_for_path( path_key.clone(), buffer.clone(), diff_hunk_ranges, editor::DEFAULT_MULTIBUFFER_CONTEXT, cx, ); multibuffer.add_diff(changed.diff.clone(), cx); (was_empty, is_excerpt_newly_added) }); self.editor.update(cx, |editor, cx| { if was_empty { editor.change_selections(None, window, cx, |selections| { selections.select_ranges([0..0]) }); } if is_excerpt_newly_added && buffer .read(cx) .file() .map_or(false, |file| file.disk_state() == DiskState::Deleted) { editor.fold_buffer(snapshot.text.remote_id(), cx) } }); } self.multibuffer.update(cx, |multibuffer, cx| { for path in paths_to_delete { multibuffer.remove_excerpts_for_path(path, cx); } }); if self.multibuffer.read(cx).is_empty() && self .editor .read(cx) .focus_handle(cx) .contains_focused(window, cx) { self.focus_handle.focus(window); } else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() { self.editor.update(cx, |editor, cx| { editor.focus_handle(cx).focus(window); }); } } fn update_title(&mut self, cx: &mut Context<Self>) { let new_title = self .thread .read(cx) .summary() .unwrap_or("Assistant Changes".into()); if new_title != self.title { self.title = new_title; cx.emit(EditorEvent::TitleChanged); } } fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) { match event { ThreadEvent::SummaryChanged => self.update_title(cx), _ => {} } } fn toggle_keep(&mut self, _: &crate::ToggleKeep, _window: &mut Window, cx: &mut Context<Self>) { let ranges = self .editor .read(cx) .selections .disjoint_anchor_ranges() .collect::<Vec<_>>(); let snapshot = self.multibuffer.read(cx).snapshot(cx); let diff_hunks_in_ranges = self .editor .read(cx) .diff_hunks_in_ranges(&ranges, &snapshot) .collect::<Vec<_>>(); for hunk in diff_hunks_in_ranges { let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { self.thread.update(cx, |thread, cx| { let accept = hunk.status().has_secondary_hunk(); thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx) }); } } } fn reject(&mut self, _: &crate::Reject, window: &mut Window, cx: &mut Context<Self>) { let ranges = self .editor .update(cx, |editor, cx| editor.selections.ranges(cx)); self.editor.update(cx, |editor, cx| { editor.restore_hunks_in_ranges(ranges, window, cx) }) } fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { let max_point = editor.buffer().read(cx).read(cx).max_point(); editor.restore_hunks_in_ranges(vec![Point::zero()..max_point], window, cx) }) } fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context<Self>) { self.thread .update(cx, |thread, cx| thread.keep_all_edits(cx)); } fn review_diff_hunks( &mut self, hunk_ranges: Vec<Range<editor::Anchor>>, accept: bool, cx: &mut Context<Self>, ) { let snapshot = self.multibuffer.read(cx).snapshot(cx); let diff_hunks_in_ranges = self .editor .read(cx) .diff_hunks_in_ranges(&hunk_ranges, &snapshot) .collect::<Vec<_>>(); for hunk in diff_hunks_in_ranges { let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { self.thread.update(cx, |thread, cx| { thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx) }); } } }} impl EventEmitter<EditorEvent> for AssistantDiff {} impl Focusable for AssistantDiff { fn focus_handle(&self, cx: &App) -> FocusHandle { if self.multibuffer.read(cx).is_empty() { self.focus_handle.clone() } else { self.editor.focus_handle(cx) } }} impl Item for AssistantDiff { type Event = EditorEvent; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> { Some(Icon::new(IconName::ZedAssistant).color(Color::Muted)) } fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) { Editor::to_item_events(event, f) } fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.editor .update(cx, |editor, cx| editor.deactivated(window, cx)); } fn navigate( &mut self, data: Box<dyn Any>, window: &mut Window, cx: &mut Context<Self>, ) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> { Some("Assistant Diff".into()) } fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { let summary = self .thread .read(cx) .summary() .unwrap_or("Assistant Changes".into()); Label::new(format!("Review: {}", summary)) .color(if params.selected { Color::Default } else { Color::Muted }) .into_any_element() } fn telemetry_event_text(&self) -> Option<&'static str> { Some("Assistant Diff Opened") } fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> { Some(Box::new(self.editor.clone())) } fn for_each_project_item( &self, cx: &App, f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem), ) { self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &App) -> bool { false } fn set_nav_history( &mut self, nav_history: ItemNavHistory, _: &mut Window, cx: &mut Context<Self>, ) { self.editor.update(cx, |editor, _| { editor.set_nav_history(Some(nav_history)); }); } fn clone_on_split( &self, _workspace_id: Option<workspace::WorkspaceId>, window: &mut Window, cx: &mut Context<Self>, ) -> Option<Entity<Self>> where Self: Sized, { Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx))) } fn is_dirty(&self, cx: &App) -> bool { self.multibuffer.read(cx).is_dirty(cx) } fn has_conflict(&self, cx: &App) -> bool { self.multibuffer.read(cx).has_conflict(cx) } fn can_save(&self, _: &App) -> bool { true } fn save( &mut self, format: bool, project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.editor.save(format, project, window, cx) } fn save_as( &mut self, _: Entity<Project>, _: ProjectPath, _window: &mut Window, _: &mut Context<Self>, ) -> Task<Result<()>> { unreachable!() } fn reload( &mut self, project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.editor.reload(project, window, cx) } fn act_as_type<'a>( &'a self, type_id: TypeId, self_handle: &'a Entity<Self>, _: &'a App, ) -> Option<AnyView> { if type_id == TypeId::of::<Self>() { Some(self_handle.to_any()) } else if type_id == TypeId::of::<Editor>() { Some(self.editor.to_any()) } else { None } } fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft } fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> { self.editor.breadcrumbs(theme, cx) } fn added_to_workspace( &mut self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Self>, ) { self.editor.update(cx, |editor, cx| { editor.added_to_workspace(workspace, window, cx) }); }} impl Render for AssistantDiff { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let is_empty = self.multibuffer.read(cx).is_empty(); div() .track_focus(&self.focus_handle) .key_context(if is_empty { "EmptyPane" } else { "AssistantDiff" }) .on_action(cx.listener(Self::toggle_keep)) .on_action(cx.listener(Self::reject)) .on_action(cx.listener(Self::reject_all)) .on_action(cx.listener(Self::keep_all)) .bg(cx.theme().colors().editor_background) .flex() .items_center() .justify_center() .size_full() .when(is_empty, |el| el.child("No changes to review")) .when(!is_empty, |el| el.child(self.editor.clone())) }} fn render_diff_hunk_controls( row: u32, status: &DiffHunkStatus, hunk_range: Range<editor::Anchor>, is_created_file: bool,Lines 1–500 of 772. Load more
use crate::{Thread, ThreadEvent, ToggleKeep}; use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::HashSet; use editor::{ Direction, Editor, EditorEvent, MultiBuffer, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, }; use gpui::{ Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, SharedString, Subscription, Task, WeakEntity, Window, prelude::*, }; use language::{Capability, DiskState, OffsetRangeExt, Point}; use multi_buffer::PathKey; use project::{Project, ProjectPath}; use std::{ any::{Any, TypeId}, ops::Range, sync::Arc, }; use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*}; use workspace::{ Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{BreadcrumbText, ItemEvent, TabContentParams}, searchable::SearchableItemHandle, }; pub struct AssistantDiff { multibuffer: Entity<MultiBuffer>, editor: Entity<Editor>, thread: Entity<Thread>, focus_handle: FocusHandle, workspace: WeakEntity<Workspace>, title: SharedString, _subscriptions: Vec<Subscription>, } impl AssistantDiff { pub fn deploy( thread: Entity<Thread>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut App, ) -> Result<()> { let existing_diff = workspace.update(cx, |workspace, cx| { workspace .items_of_type::<AssistantDiff>(cx) .find(|diff| diff.read(cx).thread == thread) })?; if let Some(existing_diff) = existing_diff { workspace.update(cx, |workspace, cx| { workspace.activate_item(&existing_diff, true, true, window, cx); }) } else { let assistant_diff = cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx)); workspace.update(cx, |workspace, cx| { workspace.add_item_to_center(Box::new(assistant_diff), window, cx); }) } } pub fn new( thread: Entity<Thread>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); let project = thread.read(cx).project().clone(); let render_diff_hunk_controls = Arc::new({ let assistant_diff = cx.entity(); move |row, status: &DiffHunkStatus, hunk_range, is_created_file, line_height, _editor: &Entity<Editor>, window: &mut Window, cx: &mut App| { render_diff_hunk_controls( row, status, hunk_range, is_created_file, line_height, &assistant_diff, window, cx, ) } }); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx); editor.disable_inline_diagnostics(); editor.set_expand_all_diff_hunks(cx); editor.set_render_diff_hunk_controls(render_diff_hunk_controls, cx); editor.register_addon(AssistantDiffAddon); editor }); let action_log = thread.read(cx).action_log().clone(); let mut this = Self { _subscriptions: vec![ cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx) }), cx.subscribe(&thread, |this, _thread, event, cx| { this.handle_thread_event(event, cx) }), ], title: SharedString::default(), multibuffer, editor, thread, focus_handle, workspace, }; this.update_excerpts(window, cx); this.update_title(cx); this } fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { let thread = self.thread.read(cx); let changed_buffers = thread.action_log().read(cx).changed_buffers(cx); let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>(); for (buffer, changed) in changed_buffers { let Some(file) = buffer.read(cx).file().cloned() else { continue; }; let path_key = PathKey::namespaced("", file.full_path(cx).into()); paths_to_delete.remove(&path_key); let snapshot = buffer.read(cx).snapshot(); let diff = changed.diff.read(cx); let diff_hunk_ranges = diff .hunks_intersecting_range( language::Anchor::MIN..language::Anchor::MAX, &snapshot, cx, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) .collect::<Vec<_>>(); let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| { let was_empty = multibuffer.is_empty(); let is_excerpt_newly_added = multibuffer.set_excerpts_for_path( path_key.clone(), buffer.clone(), diff_hunk_ranges, editor::DEFAULT_MULTIBUFFER_CONTEXT, cx, ); multibuffer.add_diff(changed.diff.clone(), cx); (was_empty, is_excerpt_newly_added) }); self.editor.update(cx, |editor, cx| { if was_empty { editor.change_selections(None, window, cx, |selections| { selections.select_ranges([0..0]) }); } if is_excerpt_newly_added && buffer .read(cx) .file() .map_or(false, |file| file.disk_state() == DiskState::Deleted) { editor.fold_buffer(snapshot.text.remote_id(), cx) } }); } self.multibuffer.update(cx, |multibuffer, cx| { for path in paths_to_delete { multibuffer.remove_excerpts_for_path(path, cx); } }); if self.multibuffer.read(cx).is_empty() && self .editor .read(cx) .focus_handle(cx) .contains_focused(window, cx) { self.focus_handle.focus(window); } else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() { self.editor.update(cx, |editor, cx| { editor.focus_handle(cx).focus(window); }); } } fn update_title(&mut self, cx: &mut Context<Self>) { let new_title = self .thread .read(cx) .summary() .unwrap_or("Assistant Changes".into()); if new_title != self.title { self.title = new_title; cx.emit(EditorEvent::TitleChanged); } } fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) { match event { ThreadEvent::SummaryChanged => self.update_title(cx), _ => {} } } fn toggle_keep(&mut self, _: &crate::ToggleKeep, _window: &mut Window, cx: &mut Context<Self>) { let ranges = self .editor .read(cx) .selections .disjoint_anchor_ranges() .collect::<Vec<_>>(); let snapshot = self.multibuffer.read(cx).snapshot(cx); let diff_hunks_in_ranges = self .editor .read(cx) .diff_hunks_in_ranges(&ranges, &snapshot) .collect::<Vec<_>>(); for hunk in diff_hunks_in_ranges { let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { self.thread.update(cx, |thread, cx| { let accept = hunk.status().has_secondary_hunk(); thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx) }); } } } fn reject(&mut self, _: &crate::Reject, window: &mut Window, cx: &mut Context<Self>) { let ranges = self .editor .update(cx, |editor, cx| editor.selections.ranges(cx)); self.editor.update(cx, |editor, cx| { editor.restore_hunks_in_ranges(ranges, window, cx) }) } fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { let max_point = editor.buffer().read(cx).read(cx).max_point(); editor.restore_hunks_in_ranges(vec![Point::zero()..max_point], window, cx) }) } fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context<Self>) { self.thread .update(cx, |thread, cx| thread.keep_all_edits(cx)); } fn review_diff_hunks( &mut self, hunk_ranges: Vec<Range<editor::Anchor>>, accept: bool, cx: &mut Context<Self>, ) { let snapshot = self.multibuffer.read(cx).snapshot(cx); let diff_hunks_in_ranges = self .editor .read(cx) .diff_hunks_in_ranges(&hunk_ranges, &snapshot) .collect::<Vec<_>>(); for hunk in diff_hunks_in_ranges { let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { self.thread.update(cx, |thread, cx| { thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx) }); } } } } impl EventEmitter<EditorEvent> for AssistantDiff {} impl Focusable for AssistantDiff { fn focus_handle(&self, cx: &App) -> FocusHandle { if self.multibuffer.read(cx).is_empty() { self.focus_handle.clone() } else { self.editor.focus_handle(cx) } } } impl Item for AssistantDiff { type Event = EditorEvent; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> { Some(Icon::new(IconName::ZedAssistant).color(Color::Muted)) } fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) { Editor::to_item_events(event, f) } fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.editor .update(cx, |editor, cx| editor.deactivated(window, cx)); } fn navigate( &mut self, data: Box<dyn Any>, window: &mut Window, cx: &mut Context<Self>, ) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> { Some("Assistant Diff".into()) } fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { let summary = self .thread .read(cx) .summary() .unwrap_or("Assistant Changes".into()); Label::new(format!("Review: {}", summary)) .color(if params.selected { Color::Default } else { Color::Muted }) .into_any_element() } fn telemetry_event_text(&self) -> Option<&'static str> { Some("Assistant Diff Opened") } fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> { Some(Box::new(self.editor.clone())) } fn for_each_project_item( &self, cx: &App, f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem), ) { self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &App) -> bool { false } fn set_nav_history( &mut self, nav_history: ItemNavHistory, _: &mut Window, cx: &mut Context<Self>, ) { self.editor.update(cx, |editor, _| { editor.set_nav_history(Some(nav_history)); }); } fn clone_on_split( &self, _workspace_id: Option<workspace::WorkspaceId>, window: &mut Window, cx: &mut Context<Self>, ) -> Option<Entity<Self>> where Self: Sized, { Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx))) } fn is_dirty(&self, cx: &App) -> bool { self.multibuffer.read(cx).is_dirty(cx) } fn has_conflict(&self, cx: &App) -> bool { self.multibuffer.read(cx).has_conflict(cx) } fn can_save(&self, _: &App) -> bool { true } fn save( &mut self, format: bool, project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.editor.save(format, project, window, cx) } fn save_as( &mut self, _: Entity<Project>, _: ProjectPath, _window: &mut Window, _: &mut Context<Self>, ) -> Task<Result<()>> { unreachable!() } fn reload( &mut self, project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.editor.reload(project, window, cx) } fn act_as_type<'a>( &'a self, type_id: TypeId, self_handle: &'a Entity<Self>, _: &'a App, ) -> Option<AnyView> { if type_id == TypeId::of::<Self>() { Some(self_handle.to_any()) } else if type_id == TypeId::of::<Editor>() { Some(self.editor.to_any()) } else { None } } fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft } fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> { self.editor.breadcrumbs(theme, cx) } fn added_to_workspace( &mut self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Self>, ) { self.editor.update(cx, |editor, cx| { editor.added_to_workspace(workspace, window, cx) }); } } impl Render for AssistantDiff { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let is_empty = self.multibuffer.read(cx).is_empty(); div() .track_focus(&self.focus_handle) .key_context(if is_empty { "EmptyPane" } else { "AssistantDiff" }) .on_action(cx.listener(Self::toggle_keep)) .on_action(cx.listener(Self::reject)) .on_action(cx.listener(Self::reject_all)) .on_action(cx.listener(Self::keep_all)) .bg(cx.theme().colors().editor_background) .flex() .items_center() .justify_center() .size_full() .when(is_empty, |el| el.child("No changes to review")) .when(!is_empty, |el| el.child(self.editor.clone())) } } fn render_diff_hunk_controls( row: u32, status: &DiffHunkStatus, hunk_range: Range<editor::Anchor>, is_created_file: bool,