Detailed changes
@@ -124,7 +124,8 @@ pub use multi_buffer::{
ToOffset, ToPoint,
};
use multi_buffer::{
- ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
+ ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
+ ToOffsetUtf16,
};
use project::{
lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
@@ -580,6 +581,15 @@ struct BufferOffset(usize);
pub trait Addon: 'static {
fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
+ fn render_buffer_header_controls(
+ &self,
+ _: &ExcerptInfo,
+ _: &Window,
+ _: &App,
+ ) -> Option<AnyElement> {
+ None
+ }
+
fn to_any(&self) -> &dyn std::any::Any;
}
@@ -2633,6 +2633,16 @@ impl EditorElement {
),
)
})
+ .children(
+ self.editor
+ .read(cx)
+ .addons
+ .values()
+ .filter_map(|addon| {
+ addon.render_buffer_header_controls(for_excerpt, window, cx)
+ })
+ .take(1),
+ )
.child(
h_flex()
.cursor_pointer()
@@ -14,8 +14,9 @@ use git::repository::RepoPath;
use git::status::FileStatus;
use git::{CommitAllChanges, CommitChanges, ToggleStaged};
use gpui::*;
-use language::Buffer;
+use language::{Buffer, File};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
+use multi_buffer::ExcerptInfo;
use panel::PanelHeader;
use project::git::{GitEvent, Repository};
use project::{Fs, Project, ProjectPath};
@@ -1007,19 +1008,19 @@ impl GitPanel {
};
if status_entry.status.is_created() {
self.new_count += 1;
- if self.entry_appears_staged(status_entry) != Some(false) {
+ if self.entry_is_staged(status_entry) != Some(false) {
self.new_staged_count += 1;
}
} else {
self.tracked_count += 1;
- if self.entry_appears_staged(status_entry) != Some(false) {
+ if self.entry_is_staged(status_entry) != Some(false) {
self.tracked_staged_count += 1;
}
}
}
}
- fn entry_appears_staged(&self, entry: &GitStatusEntry) -> Option<bool> {
+ fn entry_is_staged(&self, entry: &GitStatusEntry) -> Option<bool> {
for pending in self.pending.iter().rev() {
if pending.repo_paths.contains(&entry.repo_path) {
return Some(pending.will_become_staged);
@@ -1301,6 +1302,49 @@ impl GitPanel {
)
}
+ pub fn render_buffer_header_controls(
+ &self,
+ entity: &Entity<Self>,
+ file: &Arc<dyn File>,
+ _: &Window,
+ cx: &App,
+ ) -> Option<AnyElement> {
+ let repo = self.active_repository.as_ref()?.read(cx);
+ let repo_path = repo.worktree_id_path_to_repo_path(file.worktree_id(cx), file.path())?;
+ let ix = self.entries_by_path.get(&repo_path)?;
+ let entry = self.entries.get(*ix)?;
+
+ let is_staged = self.entry_is_staged(entry.status_entry()?);
+
+ let checkbox = Checkbox::new("stage-file", is_staged.into())
+ .disabled(!self.has_write_access(cx))
+ .fill()
+ .elevation(ElevationIndex::Surface)
+ .on_click({
+ let entry = entry.clone();
+ let git_panel = entity.downgrade();
+ move |_, window, cx| {
+ git_panel
+ .update(cx, |this, cx| {
+ this.toggle_staged_for_entry(&entry, window, cx);
+ cx.stop_propagation();
+ })
+ .ok();
+ }
+ });
+ Some(
+ h_flex()
+ .id("start-slot")
+ .child(checkbox)
+ .child(git_status_icon(entry.status_entry()?.status, cx))
+ .on_mouse_down(MouseButton::Left, |_, _, cx| {
+ // prevent the list item active state triggering when toggling checkbox
+ cx.stop_propagation();
+ })
+ .into_any_element(),
+ )
+ }
+
fn render_entries(
&self,
has_write_access: bool,
@@ -1473,14 +1517,6 @@ impl GitPanel {
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| entry.repo_path.to_string_lossy().into_owned());
- let pending = self.pending.iter().rev().find_map(|pending| {
- if pending.repo_paths.contains(&entry.repo_path) {
- Some(pending.will_become_staged)
- } else {
- None
- }
- });
-
let repo_path = entry.repo_path.clone();
let selected = self.selected_entry == Some(ix);
let status_style = GitPanelSettings::get_global(cx).status_style;
@@ -1512,10 +1548,7 @@ impl GitPanel {
let id: ElementId = ElementId::Name(format!("entry_{}", display_name).into());
- let mut is_staged = pending
- .or_else(|| entry.is_staged)
- .map(ToggleState::from)
- .unwrap_or(ToggleState::Indeterminate);
+ let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
if !self.has_staged_changes() && !entry.status.is_created() {
is_staged = ToggleState::Selected;
@@ -1597,6 +1630,16 @@ impl GitPanel {
)
.into_any_element()
}
+
+ fn has_write_access(&self, cx: &App) -> bool {
+ let room = self
+ .workspace
+ .upgrade()
+ .and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
+
+ room.as_ref()
+ .map_or(true, |room| room.read(cx).local_participant().can_write())
+ }
}
impl Render for GitPanel {
@@ -1734,6 +1777,28 @@ impl EventEmitter<Event> for GitPanel {}
impl EventEmitter<PanelEvent> for GitPanel {}
+pub(crate) struct GitPanelAddon {
+ pub(crate) git_panel: Entity<GitPanel>,
+}
+
+impl editor::Addon for GitPanelAddon {
+ fn to_any(&self) -> &dyn std::any::Any {
+ self
+ }
+
+ fn render_buffer_header_controls(
+ &self,
+ excerpt_info: &ExcerptInfo,
+ window: &Window,
+ cx: &App,
+ ) -> Option<AnyElement> {
+ let file = excerpt_info.buffer.file()?;
+ let git_panel = self.git_panel.read(cx);
+
+ git_panel.render_buffer_header_controls(&self.git_panel, &file, window, cx)
+ }
+}
+
impl Panel for GitPanel {
fn persistent_name() -> &'static str {
"GitPanel"
@@ -21,7 +21,7 @@ use workspace::{
ItemNavHistory, ToolbarItemLocation, Workspace,
};
-use crate::git_panel::{GitPanel, GitStatusEntry};
+use crate::git_panel::{GitPanel, GitPanelAddon, GitStatusEntry};
actions!(git, [Diff]);
@@ -29,6 +29,7 @@ pub(crate) struct ProjectDiff {
multibuffer: Entity<MultiBuffer>,
editor: Entity<Editor>,
project: Entity<Project>,
+ git_panel: Entity<GitPanel>,
git_state: Entity<GitState>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
@@ -79,9 +80,16 @@ impl ProjectDiff {
workspace.activate_item(&existing, true, true, window, cx);
existing
} else {
- let workspace_handle = cx.entity().downgrade();
- let project_diff =
- cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));
+ let workspace_handle = cx.entity();
+ let project_diff = cx.new(|cx| {
+ Self::new(
+ workspace.project().clone(),
+ workspace_handle,
+ workspace.panel::<GitPanel>(cx).unwrap(),
+ window,
+ cx,
+ )
+ });
workspace.add_item_to_active_pane(
Box::new(project_diff.clone()),
None,
@@ -100,7 +108,8 @@ impl ProjectDiff {
fn new(
project: Entity<Project>,
- workspace: WeakEntity<Workspace>,
+ workspace: Entity<Workspace>,
+ git_panel: Entity<GitPanel>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -116,6 +125,9 @@ impl ProjectDiff {
cx,
);
diff_display_editor.set_expand_all_diff_hunks(cx);
+ diff_display_editor.register_addon(GitPanelAddon {
+ git_panel: git_panel.clone(),
+ });
diff_display_editor
});
cx.subscribe_in(&editor, window, Self::handle_editor_event)
@@ -141,7 +153,8 @@ impl ProjectDiff {
Self {
project,
git_state: git_state.clone(),
- workspace,
+ git_panel: git_panel.clone(),
+ workspace: workspace.downgrade(),
focus_handle,
editor,
multibuffer,
@@ -423,9 +436,16 @@ impl Item for ProjectDiff {
where
Self: Sized,
{
- Some(
- cx.new(|cx| ProjectDiff::new(self.project.clone(), self.workspace.clone(), window, cx)),
- )
+ let workspace = self.workspace.upgrade()?;
+ Some(cx.new(|cx| {
+ ProjectDiff::new(
+ self.project.clone(),
+ workspace,
+ self.git_panel.clone(),
+ window,
+ cx,
+ )
+ }))
}
fn is_dirty(&self, cx: &App) -> bool {
@@ -15,6 +15,7 @@ use gpui::{
use language::{Buffer, LanguageRegistry};
use rpc::{proto, AnyProtoClient};
use settings::WorktreeId;
+use std::path::Path;
use std::sync::Arc;
use text::BufferId;
use util::{maybe, ResultExt};
@@ -341,10 +342,18 @@ impl Repository {
}
pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
- if path.worktree_id != self.worktree_id {
+ self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
+ }
+
+ pub fn worktree_id_path_to_repo_path(
+ &self,
+ worktree_id: WorktreeId,
+ path: &Path,
+ ) -> Option<RepoPath> {
+ if worktree_id != self.worktree_id {
return None;
}
- self.repository_entry.relativize(&path.path).log_err()
+ self.repository_entry.relativize(path).log_err()
}
pub fn open_commit_buffer(
@@ -58,12 +58,12 @@ impl From<bool> for ToggleState {
}
}
-// impl From<Option<bool>> for ToggleState {
-// fn from(selected: Option<bool>) -> Self {
-// match selected {
-// Some(true) => Self::Selected,
-// Some(false) => Self::Unselected,
-// None => Self::Unselected,
-// }
-// }
-// }
+impl From<Option<bool>> for ToggleState {
+ fn from(selected: Option<bool>) -> Self {
+ match selected {
+ Some(true) => Self::Selected,
+ Some(false) => Self::Unselected,
+ None => Self::Indeterminate,
+ }
+ }
+}