use anyhow::{Context as _, Result};use buffer_diff::BufferDiff;use collections::HashMap;use editor::{ Addon, Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyDiffHunkDelegate, SplittableEditor, hover_markdown_style, multibuffer_context_lines,};use futures_lite::future::yield_now;use git::repository::{CommitDetails, CommitDiff, RepoPath, is_binary_content};use git::status::{FileStatus, StatusCode, TrackedStatus};use git::{ BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, ParsedGitRemote, parse_git_remote_url,};use gpui::{ AnyElement, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, PromptLevel, Render, ScrollHandle, StatefulInteractiveElement as _, Styled, Task, WeakEntity, Window, actions,};use language::{ Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _, ReplicaId, Rope, TextBuffer,};use markdown::{Markdown, MarkdownElement};use multi_buffer::PathKey;use project::{Project, ProjectPath, WorktreeId, git_store::Repository};use settings::{DiffViewStyle, Settings};use std::{ any::{Any, TypeId}, collections::HashSet, path::PathBuf, sync::Arc,};use theme::ActiveTheme;use ui::{ContextMenu, DiffStat, Disclosure, Divider, Tooltip, WithScrollbar, prelude::*};use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff};use workspace::item::TabTooltipContent;use workspace::{ Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{ItemEvent, TabContentParams}, notifications::NotifyTaskExt, pane::SaveIntent, searchable::SearchableItemHandle,}; use crate::commit_tooltip::CommitAvatar;use crate::git_panel::GitPanel; actions!( git, [ ApplyCurrentStash, PopCurrentStash, DropCurrentStash, OpenFileAtHead, ]); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|workspace, _: &ApplyCurrentStash, window, cx| { CommitView::apply_stash(workspace, window, cx); }); workspace.register_action(|workspace, _: &DropCurrentStash, window, cx| { CommitView::remove_stash(workspace, window, cx); }); workspace.register_action(|workspace, _: &PopCurrentStash, window, cx| { CommitView::pop_stash(workspace, window, cx); }); }) .detach();} pub struct CommitView { commit: CommitDetails, editor: Entity<SplittableEditor>, message: Entity<Markdown>, message_expanded: bool, message_scroll_handle: ScrollHandle, stash: Option<usize>, multibuffer: Entity<MultiBuffer>, repository: Entity<Repository>, project: Entity<Project>, workspace: WeakEntity<Workspace>, remote: Option<GitRemote>,} struct GitBlob { path: RepoPath, worktree_id: WorktreeId, is_deleted: bool, is_binary: bool, display_name: String,} struct CommitDiffAddon { file_statuses: HashMap<language::BufferId, FileStatus>, commit_view: WeakEntity<CommitView>,} impl Addon for CommitDiffAddon { fn to_any(&self) -> &dyn std::any::Any { self } fn override_status_for_buffer_id( &self, buffer_id: language::BufferId, _cx: &App, ) -> Option<FileStatus> { self.file_statuses.get(&buffer_id).copied() } fn extend_buffer_header_context_menu( &self, menu: ContextMenu, buffer: &language::BufferSnapshot, _window: &mut Window, cx: &mut App, ) -> ContextMenu { let file_to_open = buffer.file().and_then(|file| { let commit_view = self.commit_view.upgrade()?; let commit_view = commit_view.read(cx); let project_path = commit_view .repository .read(cx) .repo_path_to_project_path(&RepoPath::from_rel_path(file.path()), cx)?; let exists_at_head = commit_view .workspace .upgrade()? .read(cx) .project() .read(cx) .entry_for_path(&project_path, cx) .is_some(); exists_at_head.then(|| file.clone()) }); menu.when_some(file_to_open, |menu, file| { let commit_view = self.commit_view.clone(); menu.entry( "Open File in Project", Some(Box::new(OpenFileAtHead)), move |window, cx| { commit_view .update(cx, |view, cx| view.open_file_at_head(&file, window, cx)) .log_err(); }, ) }) }} const FILE_NAMESPACE_SORT_PREFIX: u64 = 1; impl CommitView { pub fn open( commit_sha: String, repo: WeakEntity<Repository>, workspace: WeakEntity<Workspace>, stash: Option<usize>, file_filter: Option<RepoPath>, window: &mut Window, cx: &mut App, ) { let commit_diff = repo .update(cx, |repo, _| repo.load_commit_diff(commit_sha.clone())) .ok(); let commit_details = repo .update(cx, |repo, _| repo.show(commit_sha.clone())) .ok(); window .spawn(cx, async move |cx| { let commit_diff = commit_diff?; let commit_details = commit_details?; let (commit_diff, commit_details) = futures::join!(commit_diff, commit_details); let mut commit_diff = commit_diff.log_err()?.log_err()?; let commit_details = commit_details.log_err()?.log_err()?; // Filter to specific file if requested if let Some(ref filter_path) = file_filter { commit_diff.files.retain(|f| &f.path == filter_path); } let repo = repo.upgrade()?; workspace .update_in(cx, |workspace, window, cx| { let project = workspace.project(); let workspace_entity = cx.entity(); let workspace_handle = cx.weak_entity(); let commit_view = cx.new(|cx| { CommitView::new( commit_details, commit_diff, repo, project.clone(), workspace_entity, workspace_handle, stash, window, cx, ) }); let pane = workspace.active_pane(); pane.update(cx, |pane, cx| { let ix = pane.items().position(|item| { let commit_view = item.downcast::<CommitView>(); commit_view .is_some_and(|view| view.read(cx).commit.sha == commit_sha) }); if let Some(ix) = ix { let existing = pane .items() .filter_map(|item| item.downcast::<CommitView>()) .find(|view| view.read(cx).commit.sha == commit_sha) .unwrap(); pane.remove_item(existing.item_id(), false, false, window, cx); pane.add_item( Box::new(commit_view), true, true, Some(ix), window, cx, ); } else { pane.add_item(Box::new(commit_view), true, true, None, window, cx); } }) }) .log_err() }) .detach(); } fn new( commit: CommitDetails, commit_diff: CommitDiff, repository: Entity<Repository>, project: Entity<Project>, workspace_entity: Entity<Workspace>, workspace: WeakEntity<Workspace>, stash: Option<usize>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let language_registry = project.read(cx).languages().clone(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadOnly); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer }); let message = cx.new(|cx| { Markdown::new( commit.message.clone(), Some(language_registry.clone()), None, cx, ) }); let editor = cx.new(|cx| { let editor = SplittableEditor::new( EditorSettings::get_global(cx).diff_view_style, multibuffer.clone(), project.clone(), workspace_entity.clone(), window, cx, ); editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); editor.rhs_editor().update(cx, |editor, cx| { editor.set_show_bookmarks(false, cx); editor.set_show_breakpoints(false, cx); editor.set_show_diff_review_button(true, cx); }); editor }); let commit_sha = Arc::<str>::from(commit.sha.as_ref()); let first_worktree_id = project .read(cx) .worktrees(cx) .next() .map(|worktree| worktree.read(cx).id()); let repository_clone = repository.clone(); cx.spawn_in(window, async move |this, cx| { let mut binary_buffer_ids: HashSet<language::BufferId> = HashSet::default(); let mut file_statuses: HashMap<language::BufferId, FileStatus> = HashMap::default(); for file in commit_diff.files { let is_created = file.old_text.is_none(); let is_deleted = file.new_text.is_none(); let raw_new_text = file.new_text.unwrap_or_default(); let raw_old_text = file.old_text; let is_binary = file.is_binary || is_binary_content(raw_new_text.as_bytes()) || raw_old_text .as_ref() .is_some_and(|text| is_binary_content(text.as_bytes())); let new_text = if is_binary { "(binary file not shown)".to_string() } else { raw_new_text }; let old_text = if is_binary { None } else { raw_old_text }; let worktree_id = repository_clone .update(cx, |repository, cx| { repository .repo_path_to_project_path(&file.path, cx) .map(|path| path.worktree_id) .or(first_worktree_id) }) .context("project has no worktrees")?; let short_sha = commit_sha .get(0..git::SHORT_SHA_LENGTH) .unwrap_or(&commit_sha); let file_name = file .path .file_name() .map(|name| name.to_string()) .unwrap_or_else(|| file.path.display(PathStyle::local()).to_string()); let display_name = format!("{short_sha} - {file_name}"); let file = Arc::new(GitBlob { path: file.path.clone(), is_deleted, is_binary, worktree_id, display_name, }) as Arc<dyn language::File>; let buffer = build_buffer(new_text, file, &language_registry, cx).await?; let buffer_id = cx.update(|_, cx| buffer.read(cx).remote_id())?; let status_code = if is_created { StatusCode::Added } else if is_deleted { StatusCode::Deleted } else { StatusCode::Modified }; file_statuses.insert( buffer_id, FileStatus::Tracked(TrackedStatus { index_status: status_code, worktree_status: StatusCode::Unmodified, }), ); if is_binary { binary_buffer_ids.insert(buffer_id); } let buffer_diff = if is_binary { cx.update(|_, cx| { let snapshot = buffer.read(cx).snapshot(); cx.new(|cx| { BufferDiff::new_unchanged( &snapshot, snapshot.language().cloned(), Some(language_registry.clone()), cx, ) }) })? } else { build_buffer_diff(old_text, &buffer, &language_registry, cx).await? }; let (excerpt_ranges, path) = cx.update(|_, cx| { let snapshot = buffer.read(cx).snapshot(); let path = PathKey::with_sort_prefix( FILE_NAMESPACE_SORT_PREFIX, snapshot.file().unwrap().path().clone(), ); let ranges = if is_binary { vec![language::Point::zero()..snapshot.max_point()] } else { let diff_snapshot = buffer_diff.read(cx).snapshot(cx); let mut hunks = diff_snapshot.hunks(&snapshot).peekable(); if hunks.peek().is_none() { vec![language::Point::zero()..snapshot.max_point()] } else { hunks .map(|hunk| hunk.buffer_range.to_point(&snapshot)) .collect::<Vec<_>>() } }; (ranges, path) })?; // Batch the insertion of excerpts and yield between batches, to avoid blocking the main thread when a single file has many hunks. const EXCERPT_BATCH_SIZE: usize = 10; let total = excerpt_ranges.len(); let mut batch_end = 0; while batch_end < total { let is_first_batch = batch_end == 0; batch_end = (batch_end + EXCERPT_BATCH_SIZE).min(total); let ranges = excerpt_ranges[..batch_end].to_vec(); this.update_in(cx, |this, window, cx| { this.editor.update(cx, |editor, cx| { editor.update_excerpts_for_path( path.clone(), buffer.clone(), ranges, multibuffer_context_lines(cx), buffer_diff.clone(), cx, ); if is_first_batch && editor.diff_view_style() == DiffViewStyle::Split { editor.split(window, cx); } }); })?; if batch_end < total { yield_now().await; } } } this.update(cx, |this, cx| { let commit_view = cx.weak_entity(); this.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, _cx| { editor.register_addon(CommitDiffAddon { file_statuses, commit_view, }); }); }); if !binary_buffer_ids.is_empty() { this.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, cx| { editor.fold_buffers(binary_buffer_ids, cx); }); }); } })?; anyhow::Ok(()) }) .detach(); let snapshot = repository.read(cx).snapshot(); let remote_url = snapshot .remote_upstream_url .as_ref() .or(snapshot.remote_origin_url.as_ref()); let remote = remote_url.and_then(|url| { let provider_registry = GitHostingProviderRegistry::default_global(cx); parse_git_remote_url(provider_registry, url).map(|(host, parsed)| GitRemote { host, owner: parsed.owner.into(), repo: parsed.repo.into(), }) }); Self { commit, editor, message, message_expanded: false, message_scroll_handle: ScrollHandle::new(), multibuffer, stash, repository, project, workspace, remote, } } fn render_commit_avatar( &self, sha: &SharedString, size: impl Into<gpui::AbsoluteLength>, window: &mut Window, cx: &mut App, ) -> AnyElement { CommitAvatar::new( sha, Some(self.commit.author_email.clone()), self.remote.as_ref(), ) .size(size)Lines 1–500 of 1380. Load more
use anyhow::{Context as _, Result}; use buffer_diff::BufferDiff; use collections::HashMap; use editor::{ Addon, Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyDiffHunkDelegate, SplittableEditor, hover_markdown_style, multibuffer_context_lines, }; use futures_lite::future::yield_now; use git::repository::{CommitDetails, CommitDiff, RepoPath, is_binary_content}; use git::status::{FileStatus, StatusCode, TrackedStatus}; use git::{ BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, ParsedGitRemote, parse_git_remote_url, }; use gpui::{ AnyElement, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, PromptLevel, Render, ScrollHandle, StatefulInteractiveElement as _, Styled, Task, WeakEntity, Window, actions, }; use language::{ Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _, ReplicaId, Rope, TextBuffer, }; use markdown::{Markdown, MarkdownElement}; use multi_buffer::PathKey; use project::{Project, ProjectPath, WorktreeId, git_store::Repository}; use settings::{DiffViewStyle, Settings}; use std::{ any::{Any, TypeId}, collections::HashSet, path::PathBuf, sync::Arc, }; use theme::ActiveTheme; use ui::{ContextMenu, DiffStat, Disclosure, Divider, Tooltip, WithScrollbar, prelude::*}; use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff}; use workspace::item::TabTooltipContent; use workspace::{ Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{ItemEvent, TabContentParams}, notifications::NotifyTaskExt, pane::SaveIntent, searchable::SearchableItemHandle, }; use crate::commit_tooltip::CommitAvatar; use crate::git_panel::GitPanel; actions!( git, [ ApplyCurrentStash, PopCurrentStash, DropCurrentStash, OpenFileAtHead, ] ); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|workspace, _: &ApplyCurrentStash, window, cx| { CommitView::apply_stash(workspace, window, cx); }); workspace.register_action(|workspace, _: &DropCurrentStash, window, cx| { CommitView::remove_stash(workspace, window, cx); }); workspace.register_action(|workspace, _: &PopCurrentStash, window, cx| { CommitView::pop_stash(workspace, window, cx); }); }) .detach(); } pub struct CommitView { commit: CommitDetails, editor: Entity<SplittableEditor>, message: Entity<Markdown>, message_expanded: bool, message_scroll_handle: ScrollHandle, stash: Option<usize>, multibuffer: Entity<MultiBuffer>, repository: Entity<Repository>, project: Entity<Project>, workspace: WeakEntity<Workspace>, remote: Option<GitRemote>, } struct GitBlob { path: RepoPath, worktree_id: WorktreeId, is_deleted: bool, is_binary: bool, display_name: String, } struct CommitDiffAddon { file_statuses: HashMap<language::BufferId, FileStatus>, commit_view: WeakEntity<CommitView>, } impl Addon for CommitDiffAddon { fn to_any(&self) -> &dyn std::any::Any { self } fn override_status_for_buffer_id( &self, buffer_id: language::BufferId, _cx: &App, ) -> Option<FileStatus> { self.file_statuses.get(&buffer_id).copied() } fn extend_buffer_header_context_menu( &self, menu: ContextMenu, buffer: &language::BufferSnapshot, _window: &mut Window, cx: &mut App, ) -> ContextMenu { let file_to_open = buffer.file().and_then(|file| { let commit_view = self.commit_view.upgrade()?; let commit_view = commit_view.read(cx); let project_path = commit_view .repository .read(cx) .repo_path_to_project_path(&RepoPath::from_rel_path(file.path()), cx)?; let exists_at_head = commit_view .workspace .upgrade()? .read(cx) .project() .read(cx) .entry_for_path(&project_path, cx) .is_some(); exists_at_head.then(|| file.clone()) }); menu.when_some(file_to_open, |menu, file| { let commit_view = self.commit_view.clone(); menu.entry( "Open File in Project", Some(Box::new(OpenFileAtHead)), move |window, cx| { commit_view .update(cx, |view, cx| view.open_file_at_head(&file, window, cx)) .log_err(); }, ) }) } } const FILE_NAMESPACE_SORT_PREFIX: u64 = 1; impl CommitView { pub fn open( commit_sha: String, repo: WeakEntity<Repository>, workspace: WeakEntity<Workspace>, stash: Option<usize>, file_filter: Option<RepoPath>, window: &mut Window, cx: &mut App, ) { let commit_diff = repo .update(cx, |repo, _| repo.load_commit_diff(commit_sha.clone())) .ok(); let commit_details = repo .update(cx, |repo, _| repo.show(commit_sha.clone())) .ok(); window .spawn(cx, async move |cx| { let commit_diff = commit_diff?; let commit_details = commit_details?; let (commit_diff, commit_details) = futures::join!(commit_diff, commit_details); let mut commit_diff = commit_diff.log_err()?.log_err()?; let commit_details = commit_details.log_err()?.log_err()?; // Filter to specific file if requested if let Some(ref filter_path) = file_filter { commit_diff.files.retain(|f| &f.path == filter_path); } let repo = repo.upgrade()?; workspace .update_in(cx, |workspace, window, cx| { let project = workspace.project(); let workspace_entity = cx.entity(); let workspace_handle = cx.weak_entity(); let commit_view = cx.new(|cx| { CommitView::new( commit_details, commit_diff, repo, project.clone(), workspace_entity, workspace_handle, stash, window, cx, ) }); let pane = workspace.active_pane(); pane.update(cx, |pane, cx| { let ix = pane.items().position(|item| { let commit_view = item.downcast::<CommitView>(); commit_view .is_some_and(|view| view.read(cx).commit.sha == commit_sha) }); if let Some(ix) = ix { let existing = pane .items() .filter_map(|item| item.downcast::<CommitView>()) .find(|view| view.read(cx).commit.sha == commit_sha) .unwrap(); pane.remove_item(existing.item_id(), false, false, window, cx); pane.add_item( Box::new(commit_view), true, true, Some(ix), window, cx, ); } else { pane.add_item(Box::new(commit_view), true, true, None, window, cx); } }) }) .log_err() }) .detach(); } fn new( commit: CommitDetails, commit_diff: CommitDiff, repository: Entity<Repository>, project: Entity<Project>, workspace_entity: Entity<Workspace>, workspace: WeakEntity<Workspace>, stash: Option<usize>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let language_registry = project.read(cx).languages().clone(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadOnly); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer }); let message = cx.new(|cx| { Markdown::new( commit.message.clone(), Some(language_registry.clone()), None, cx, ) }); let editor = cx.new(|cx| { let editor = SplittableEditor::new( EditorSettings::get_global(cx).diff_view_style, multibuffer.clone(), project.clone(), workspace_entity.clone(), window, cx, ); editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); editor.rhs_editor().update(cx, |editor, cx| { editor.set_show_bookmarks(false, cx); editor.set_show_breakpoints(false, cx); editor.set_show_diff_review_button(true, cx); }); editor }); let commit_sha = Arc::<str>::from(commit.sha.as_ref()); let first_worktree_id = project .read(cx) .worktrees(cx) .next() .map(|worktree| worktree.read(cx).id()); let repository_clone = repository.clone(); cx.spawn_in(window, async move |this, cx| { let mut binary_buffer_ids: HashSet<language::BufferId> = HashSet::default(); let mut file_statuses: HashMap<language::BufferId, FileStatus> = HashMap::default(); for file in commit_diff.files { let is_created = file.old_text.is_none(); let is_deleted = file.new_text.is_none(); let raw_new_text = file.new_text.unwrap_or_default(); let raw_old_text = file.old_text; let is_binary = file.is_binary || is_binary_content(raw_new_text.as_bytes()) || raw_old_text .as_ref() .is_some_and(|text| is_binary_content(text.as_bytes())); let new_text = if is_binary { "(binary file not shown)".to_string() } else { raw_new_text }; let old_text = if is_binary { None } else { raw_old_text }; let worktree_id = repository_clone .update(cx, |repository, cx| { repository .repo_path_to_project_path(&file.path, cx) .map(|path| path.worktree_id) .or(first_worktree_id) }) .context("project has no worktrees")?; let short_sha = commit_sha .get(0..git::SHORT_SHA_LENGTH) .unwrap_or(&commit_sha); let file_name = file .path .file_name() .map(|name| name.to_string()) .unwrap_or_else(|| file.path.display(PathStyle::local()).to_string()); let display_name = format!("{short_sha} - {file_name}"); let file = Arc::new(GitBlob { path: file.path.clone(), is_deleted, is_binary, worktree_id, display_name, }) as Arc<dyn language::File>; let buffer = build_buffer(new_text, file, &language_registry, cx).await?; let buffer_id = cx.update(|_, cx| buffer.read(cx).remote_id())?; let status_code = if is_created { StatusCode::Added } else if is_deleted { StatusCode::Deleted } else { StatusCode::Modified }; file_statuses.insert( buffer_id, FileStatus::Tracked(TrackedStatus { index_status: status_code, worktree_status: StatusCode::Unmodified, }), ); if is_binary { binary_buffer_ids.insert(buffer_id); } let buffer_diff = if is_binary { cx.update(|_, cx| { let snapshot = buffer.read(cx).snapshot(); cx.new(|cx| { BufferDiff::new_unchanged( &snapshot, snapshot.language().cloned(), Some(language_registry.clone()), cx, ) }) })? } else { build_buffer_diff(old_text, &buffer, &language_registry, cx).await? }; let (excerpt_ranges, path) = cx.update(|_, cx| { let snapshot = buffer.read(cx).snapshot(); let path = PathKey::with_sort_prefix( FILE_NAMESPACE_SORT_PREFIX, snapshot.file().unwrap().path().clone(), ); let ranges = if is_binary { vec![language::Point::zero()..snapshot.max_point()] } else { let diff_snapshot = buffer_diff.read(cx).snapshot(cx); let mut hunks = diff_snapshot.hunks(&snapshot).peekable(); if hunks.peek().is_none() { vec![language::Point::zero()..snapshot.max_point()] } else { hunks .map(|hunk| hunk.buffer_range.to_point(&snapshot)) .collect::<Vec<_>>() } }; (ranges, path) })?; // Batch the insertion of excerpts and yield between batches, to avoid blocking the main thread when a single file has many hunks. const EXCERPT_BATCH_SIZE: usize = 10; let total = excerpt_ranges.len(); let mut batch_end = 0; while batch_end < total { let is_first_batch = batch_end == 0; batch_end = (batch_end + EXCERPT_BATCH_SIZE).min(total); let ranges = excerpt_ranges[..batch_end].to_vec(); this.update_in(cx, |this, window, cx| { this.editor.update(cx, |editor, cx| { editor.update_excerpts_for_path( path.clone(), buffer.clone(), ranges, multibuffer_context_lines(cx), buffer_diff.clone(), cx, ); if is_first_batch && editor.diff_view_style() == DiffViewStyle::Split { editor.split(window, cx); } }); })?; if batch_end < total { yield_now().await; } } } this.update(cx, |this, cx| { let commit_view = cx.weak_entity(); this.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, _cx| { editor.register_addon(CommitDiffAddon { file_statuses, commit_view, }); }); }); if !binary_buffer_ids.is_empty() { this.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, cx| { editor.fold_buffers(binary_buffer_ids, cx); }); }); } })?; anyhow::Ok(()) }) .detach(); let snapshot = repository.read(cx).snapshot(); let remote_url = snapshot .remote_upstream_url .as_ref() .or(snapshot.remote_origin_url.as_ref()); let remote = remote_url.and_then(|url| { let provider_registry = GitHostingProviderRegistry::default_global(cx); parse_git_remote_url(provider_registry, url).map(|(host, parsed)| GitRemote { host, owner: parsed.owner.into(), repo: parsed.repo.into(), }) }); Self { commit, editor, message, message_expanded: false, message_scroll_handle: ScrollHandle::new(), multibuffer, stash, repository, project, workspace, remote, } } fn render_commit_avatar( &self, sha: &SharedString, size: impl Into<gpui::AbsoluteLength>, window: &mut Window, cx: &mut App, ) -> AnyElement { CommitAvatar::new( sha, Some(self.commit.author_email.clone()), self.remote.as_ref(), ) .size(size)