Git improvements (#24238)
- **Base diffs on uncommitted changes** - **Show added files in project diff view** - **Fix git panel optimism** Release Notes: - Git: update diffs to be relative to HEAD instead of the index; to pave the way for showing which hunks are staged --------- Co-authored-by: Cole <cole@zed.dev>
This commit is contained in:
parent
22b7042b9e
commit
0963401a8d
11 changed files with 241 additions and 231 deletions
|
@ -1285,7 +1285,7 @@ impl Editor {
|
||||||
|
|
||||||
let mut code_action_providers = Vec::new();
|
let mut code_action_providers = Vec::new();
|
||||||
if let Some(project) = project.clone() {
|
if let Some(project) = project.clone() {
|
||||||
get_unstaged_changes_for_buffers(
|
get_uncommitted_changes_for_buffer(
|
||||||
&project,
|
&project,
|
||||||
buffer.read(cx).all_buffers(),
|
buffer.read(cx).all_buffers(),
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
|
@ -13657,7 +13657,7 @@ impl Editor {
|
||||||
let buffer_id = buffer.read(cx).remote_id();
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
if self.buffer.read(cx).change_set_for(buffer_id).is_none() {
|
if self.buffer.read(cx).change_set_for(buffer_id).is_none() {
|
||||||
if let Some(project) = &self.project {
|
if let Some(project) = &self.project {
|
||||||
get_unstaged_changes_for_buffers(
|
get_uncommitted_changes_for_buffer(
|
||||||
project,
|
project,
|
||||||
[buffer.clone()],
|
[buffer.clone()],
|
||||||
self.buffer.clone(),
|
self.buffer.clone(),
|
||||||
|
@ -14413,7 +14413,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_unstaged_changes_for_buffers(
|
fn get_uncommitted_changes_for_buffer(
|
||||||
project: &Entity<Project>,
|
project: &Entity<Project>,
|
||||||
buffers: impl IntoIterator<Item = Entity<Buffer>>,
|
buffers: impl IntoIterator<Item = Entity<Buffer>>,
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Entity<MultiBuffer>,
|
||||||
|
@ -14422,7 +14422,7 @@ fn get_unstaged_changes_for_buffers(
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
for buffer in buffers {
|
for buffer in buffers {
|
||||||
tasks.push(project.open_unstaged_changes(buffer.clone(), cx))
|
tasks.push(project.open_uncommitted_changes(buffer.clone(), cx))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
|
|
|
@ -5619,13 +5619,13 @@ async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
|
||||||
|
|
||||||
let base_text = r#"
|
let base_text = r#"
|
||||||
impl A {
|
impl A {
|
||||||
// this is an unstaged comment
|
// this is an uncommitted comment
|
||||||
|
|
||||||
fn b() {
|
fn b() {
|
||||||
c();
|
c();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is another unstaged comment
|
// this is another uncommitted comment
|
||||||
|
|
||||||
fn d() {
|
fn d() {
|
||||||
// e
|
// e
|
||||||
|
@ -5668,13 +5668,13 @@ async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
|
||||||
cx.assert_state_with_diff(
|
cx.assert_state_with_diff(
|
||||||
"
|
"
|
||||||
ˇimpl A {
|
ˇimpl A {
|
||||||
- // this is an unstaged comment
|
- // this is an uncommitted comment
|
||||||
|
|
||||||
fn b() {
|
fn b() {
|
||||||
c();
|
c();
|
||||||
}
|
}
|
||||||
|
|
||||||
- // this is another unstaged comment
|
- // this is another uncommitted comment
|
||||||
-
|
-
|
||||||
fn d() {
|
fn d() {
|
||||||
// e
|
// e
|
||||||
|
@ -5691,13 +5691,13 @@ async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
|
||||||
|
|
||||||
let expected_display_text = "
|
let expected_display_text = "
|
||||||
impl A {
|
impl A {
|
||||||
// this is an unstaged comment
|
// this is an uncommitted comment
|
||||||
|
|
||||||
fn b() {
|
fn b() {
|
||||||
⋯
|
⋯
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is another unstaged comment
|
// this is another uncommitted comment
|
||||||
|
|
||||||
fn d() {
|
fn d() {
|
||||||
⋯
|
⋯
|
||||||
|
|
|
@ -290,7 +290,7 @@ impl EditorTestContext {
|
||||||
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
||||||
});
|
});
|
||||||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||||
fs.set_index_for_repo(
|
fs.set_head_for_repo(
|
||||||
&Self::root_path().join(".git"),
|
&Self::root_path().join(".git"),
|
||||||
&[(path.into(), diff_base.to_string())],
|
&[(path.into(), diff_base.to_string())],
|
||||||
);
|
);
|
||||||
|
|
|
@ -265,13 +265,13 @@ impl GitRepository for RealGitRepository {
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
|
|
||||||
if !paths.is_empty() {
|
if !paths.is_empty() {
|
||||||
let cmd = new_std_command(&self.git_binary_path)
|
let status = new_std_command(&self.git_binary_path)
|
||||||
.current_dir(&working_directory)
|
.current_dir(&working_directory)
|
||||||
.args(["update-index", "--add", "--remove", "--"])
|
.args(["update-index", "--add", "--remove", "--"])
|
||||||
.args(paths.iter().map(|p| p.as_ref()))
|
.args(paths.iter().map(|p| p.as_ref()))
|
||||||
.status()?;
|
.status()?;
|
||||||
if !cmd.success() {
|
if !status.success() {
|
||||||
return Err(anyhow!("Failed to stage paths: {cmd}"));
|
return Err(anyhow!("Failed to stage paths: {status}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,13 +12,11 @@ use editor::scroll::ScrollbarAutoHide;
|
||||||
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
|
||||||
use git::repository::RepoPath;
|
use git::repository::RepoPath;
|
||||||
use git::status::FileStatus;
|
use git::status::FileStatus;
|
||||||
use git::{
|
use git::{CommitAllChanges, CommitChanges, ToggleStaged, COMMIT_MESSAGE};
|
||||||
CommitAllChanges, CommitChanges, RevertAll, StageAll, ToggleStaged, UnstageAll, COMMIT_MESSAGE,
|
|
||||||
};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use language::{Buffer, BufferId};
|
use language::{Buffer, BufferId};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
use project::git::{GitRepo, RepositoryHandle};
|
use project::git::{GitEvent, GitRepo, RepositoryHandle};
|
||||||
use project::{CreateOptions, Fs, Project, ProjectPath};
|
use project::{CreateOptions, Fs, Project, ProjectPath};
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -43,7 +41,6 @@ actions!(
|
||||||
Close,
|
Close,
|
||||||
ToggleFocus,
|
ToggleFocus,
|
||||||
OpenMenu,
|
OpenMenu,
|
||||||
OpenSelected,
|
|
||||||
FocusEditor,
|
FocusEditor,
|
||||||
FocusChanges,
|
FocusChanges,
|
||||||
FillCoAuthors,
|
FillCoAuthors,
|
||||||
|
@ -76,17 +73,17 @@ struct SerializedGitPanel {
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
enum Section {
|
enum Section {
|
||||||
Changed,
|
Changed,
|
||||||
New,
|
Created,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Section {
|
impl Section {
|
||||||
pub fn contains(&self, status: FileStatus) -> bool {
|
pub fn contains(&self, status: FileStatus) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Section::Changed => !status.is_created(),
|
Section::Changed => !status.is_created(),
|
||||||
Section::New => status.is_created(),
|
Section::Created => status.is_created(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +91,6 @@ impl Section {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
struct GitHeaderEntry {
|
struct GitHeaderEntry {
|
||||||
header: Section,
|
header: Section,
|
||||||
all_staged: ToggleState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitHeaderEntry {
|
impl GitHeaderEntry {
|
||||||
|
@ -104,7 +100,7 @@ impl GitHeaderEntry {
|
||||||
pub fn title(&self) -> &'static str {
|
pub fn title(&self) -> &'static str {
|
||||||
match self.header {
|
match self.header {
|
||||||
Section::Changed => "Changed",
|
Section::Changed => "Changed",
|
||||||
Section::New => "New",
|
Section::Created => "New",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,11 +122,18 @@ impl GitListEntry {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct GitStatusEntry {
|
pub struct GitStatusEntry {
|
||||||
depth: usize,
|
pub(crate) depth: usize,
|
||||||
display_name: String,
|
pub(crate) display_name: String,
|
||||||
repo_path: RepoPath,
|
pub(crate) repo_path: RepoPath,
|
||||||
status: FileStatus,
|
pub(crate) status: FileStatus,
|
||||||
is_staged: Option<bool>,
|
pub(crate) is_staged: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PendingOperation {
|
||||||
|
finished: bool,
|
||||||
|
will_become_staged: bool,
|
||||||
|
repo_paths: HashSet<RepoPath>,
|
||||||
|
op_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GitPanel {
|
pub struct GitPanel {
|
||||||
|
@ -152,9 +155,11 @@ pub struct GitPanel {
|
||||||
entries: Vec<GitListEntry>,
|
entries: Vec<GitListEntry>,
|
||||||
entries_by_path: collections::HashMap<RepoPath, usize>,
|
entries_by_path: collections::HashMap<RepoPath, usize>,
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
pending: HashMap<RepoPath, bool>,
|
pending: Vec<PendingOperation>,
|
||||||
commit_task: Task<Result<()>>,
|
commit_task: Task<Result<()>>,
|
||||||
commit_pending: bool,
|
commit_pending: bool,
|
||||||
|
can_commit: bool,
|
||||||
|
can_commit_all: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_message_buffer(
|
fn commit_message_buffer(
|
||||||
|
@ -287,9 +292,12 @@ impl GitPanel {
|
||||||
&git_state,
|
&git_state,
|
||||||
window,
|
window,
|
||||||
move |this, git_state, event, window, cx| match event {
|
move |this, git_state, event, window, cx| match event {
|
||||||
project::git::Event::RepositoriesUpdated => {
|
GitEvent::FileSystemUpdated => {
|
||||||
|
this.schedule_update(false, window, cx);
|
||||||
|
}
|
||||||
|
GitEvent::ActiveRepositoryChanged | GitEvent::GitStateUpdated => {
|
||||||
this.active_repository = git_state.read(cx).active_repository();
|
this.active_repository = git_state.read(cx).active_repository();
|
||||||
this.schedule_update(window, cx);
|
this.schedule_update(true, window, cx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -303,7 +311,7 @@ impl GitPanel {
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
entries_by_path: HashMap::default(),
|
entries_by_path: HashMap::default(),
|
||||||
pending: HashMap::default(),
|
pending: Vec::new(),
|
||||||
current_modifiers: window.modifiers(),
|
current_modifiers: window.modifiers(),
|
||||||
width: Some(px(360.)),
|
width: Some(px(360.)),
|
||||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone())
|
scrollbar_state: ScrollbarState::new(scroll_handle.clone())
|
||||||
|
@ -321,8 +329,10 @@ impl GitPanel {
|
||||||
commit_editor,
|
commit_editor,
|
||||||
project,
|
project,
|
||||||
workspace,
|
workspace,
|
||||||
|
can_commit: false,
|
||||||
|
can_commit_all: false,
|
||||||
};
|
};
|
||||||
git_panel.schedule_update(window, cx);
|
git_panel.schedule_update(false, window, cx);
|
||||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||||
git_panel
|
git_panel
|
||||||
});
|
});
|
||||||
|
@ -617,7 +627,7 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GitListEntry::Header(section) => {
|
GitListEntry::Header(section) => {
|
||||||
let goal_staged_state = !section.all_staged.selected();
|
let goal_staged_state = !self.header_state(section.header).selected();
|
||||||
let entries = self
|
let entries = self
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -629,12 +639,17 @@ impl GitPanel {
|
||||||
.map(|status_entry| status_entry.repo_path)
|
.map(|status_entry| status_entry.repo_path)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(!section.all_staged.selected(), entries)
|
(goal_staged_state, entries)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for repo_path in repo_paths.iter() {
|
|
||||||
self.pending.insert(repo_path.clone(), stage);
|
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
|
||||||
}
|
self.pending.push(PendingOperation {
|
||||||
|
op_id,
|
||||||
|
will_become_staged: stage,
|
||||||
|
repo_paths: repo_paths.iter().cloned().collect(),
|
||||||
|
finished: false,
|
||||||
|
});
|
||||||
|
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
let repo_paths = repo_paths.clone();
|
let repo_paths = repo_paths.clone();
|
||||||
|
@ -647,9 +662,9 @@ impl GitPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
for repo_path in repo_paths {
|
for pending in this.pending.iter_mut() {
|
||||||
if this.pending.get(&repo_path) == Some(&stage) {
|
if pending.op_id == op_id {
|
||||||
this.pending.remove(&repo_path);
|
pending.finished = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
@ -696,67 +711,6 @@ impl GitPanel {
|
||||||
cx.emit(Event::OpenedEntry { path });
|
cx.emit(Event::OpenedEntry { path });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_all(&mut self, _: &git::StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let Some(active_repository) = self.active_repository.as_ref().cloned() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let mut pending_paths = Vec::new();
|
|
||||||
for entry in self.entries.iter() {
|
|
||||||
if let Some(status_entry) = entry.status_entry() {
|
|
||||||
self.pending.insert(status_entry.repo_path.clone(), true);
|
|
||||||
pending_paths.push(status_entry.repo_path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
if let Err(e) = active_repository.stage_all().await {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.show_err_toast(e, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
};
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
for repo_path in pending_paths {
|
|
||||||
this.pending.remove(&repo_path);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unstage_all(&mut self, _: &git::UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let Some(active_repository) = self.active_repository.as_ref().cloned() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let mut pending_paths = Vec::new();
|
|
||||||
for entry in self.entries.iter() {
|
|
||||||
if let Some(status_entry) = entry.status_entry() {
|
|
||||||
self.pending.insert(status_entry.repo_path.clone(), false);
|
|
||||||
pending_paths.push(status_entry.repo_path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
if let Err(e) = active_repository.unstage_all().await {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.show_err_toast(e, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
};
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
for repo_path in pending_paths {
|
|
||||||
this.pending.remove(&repo_path);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn discard_all(&mut self, _: &git::RevertAll, _window: &mut Window, _cx: &mut Context<Self>) {
|
|
||||||
// TODO: Implement discard all
|
|
||||||
println!("Discard all triggered");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit all staged changes
|
/// Commit all staged changes
|
||||||
fn commit_changes(
|
fn commit_changes(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -768,7 +722,7 @@ impl GitPanel {
|
||||||
let Some(active_repository) = self.active_repository.clone() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !active_repository.can_commit(false) {
|
if !self.can_commit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if self.commit_editor.read(cx).is_empty(cx) {
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
@ -811,7 +765,7 @@ impl GitPanel {
|
||||||
let Some(active_repository) = self.active_repository.clone() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !active_repository.can_commit(true) {
|
if !self.can_commit_all {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if self.commit_editor.read(cx).is_empty(cx) {
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
@ -926,7 +880,12 @@ impl GitPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule_update(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn schedule_update(
|
||||||
|
&mut self,
|
||||||
|
clear_pending: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
let handle = cx.entity().downgrade();
|
let handle = cx.entity().downgrade();
|
||||||
self.update_visible_entries_task = cx.spawn_in(window, |_, mut cx| async move {
|
self.update_visible_entries_task = cx.spawn_in(window, |_, mut cx| async move {
|
||||||
|
@ -957,6 +916,9 @@ impl GitPanel {
|
||||||
git_panel
|
git_panel
|
||||||
.update_in(&mut cx, |git_panel, window, cx| {
|
.update_in(&mut cx, |git_panel, window, cx| {
|
||||||
git_panel.update_visible_entries(cx);
|
git_panel.update_visible_entries(cx);
|
||||||
|
if clear_pending {
|
||||||
|
git_panel.clear_pending();
|
||||||
|
}
|
||||||
git_panel.commit_editor =
|
git_panel.commit_editor =
|
||||||
cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
|
cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
|
||||||
})
|
})
|
||||||
|
@ -965,6 +927,10 @@ impl GitPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_pending(&mut self) {
|
||||||
|
self.pending.retain(|v| !v.finished)
|
||||||
|
}
|
||||||
|
|
||||||
fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
|
fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
|
||||||
self.entries.clear();
|
self.entries.clear();
|
||||||
self.entries_by_path.clear();
|
self.entries_by_path.clear();
|
||||||
|
@ -980,12 +946,11 @@ impl GitPanel {
|
||||||
// First pass - collect all paths
|
// First pass - collect all paths
|
||||||
let path_set = HashSet::from_iter(repo.status().map(|entry| entry.repo_path));
|
let path_set = HashSet::from_iter(repo.status().map(|entry| entry.repo_path));
|
||||||
|
|
||||||
// Second pass - create entries with proper depth calculation
|
let mut has_changed_checked_boxes = false;
|
||||||
let mut new_any_staged = false;
|
let mut has_changed = false;
|
||||||
let mut new_all_staged = true;
|
let mut has_added_checked_boxes = false;
|
||||||
let mut changed_any_staged = false;
|
|
||||||
let mut changed_all_staged = true;
|
|
||||||
|
|
||||||
|
// Second pass - create entries with proper depth calculation
|
||||||
for entry in repo.status() {
|
for entry in repo.status() {
|
||||||
let (depth, difference) =
|
let (depth, difference) =
|
||||||
Self::calculate_depth_and_difference(&entry.repo_path, &path_set);
|
Self::calculate_depth_and_difference(&entry.repo_path, &path_set);
|
||||||
|
@ -993,15 +958,6 @@ impl GitPanel {
|
||||||
let is_new = entry.status.is_created();
|
let is_new = entry.status.is_created();
|
||||||
let is_staged = entry.status.is_staged();
|
let is_staged = entry.status.is_staged();
|
||||||
|
|
||||||
let new_is_staged = is_staged.unwrap_or(false);
|
|
||||||
if is_new {
|
|
||||||
new_any_staged |= new_is_staged;
|
|
||||||
new_all_staged &= new_is_staged;
|
|
||||||
} else {
|
|
||||||
changed_any_staged |= new_is_staged;
|
|
||||||
changed_all_staged &= new_is_staged;
|
|
||||||
}
|
|
||||||
|
|
||||||
let display_name = if difference > 1 {
|
let display_name = if difference > 1 {
|
||||||
// Show partial path for deeply nested files
|
// Show partial path for deeply nested files
|
||||||
entry
|
entry
|
||||||
|
@ -1030,8 +986,15 @@ impl GitPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_new {
|
if is_new {
|
||||||
|
if entry.is_staged != Some(false) {
|
||||||
|
has_added_checked_boxes = true
|
||||||
|
}
|
||||||
new_entries.push(entry);
|
new_entries.push(entry);
|
||||||
} else {
|
} else {
|
||||||
|
has_changed = true;
|
||||||
|
if entry.is_staged != Some(false) {
|
||||||
|
has_changed_checked_boxes = true
|
||||||
|
}
|
||||||
changed_entries.push(entry);
|
changed_entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1041,11 +1004,8 @@ impl GitPanel {
|
||||||
new_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
new_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
||||||
|
|
||||||
if changed_entries.len() > 0 {
|
if changed_entries.len() > 0 {
|
||||||
let toggle_state =
|
|
||||||
ToggleState::from_any_and_all(changed_any_staged, changed_all_staged);
|
|
||||||
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
||||||
header: Section::Changed,
|
header: Section::Changed,
|
||||||
all_staged: toggle_state,
|
|
||||||
}));
|
}));
|
||||||
self.entries.extend(
|
self.entries.extend(
|
||||||
changed_entries
|
changed_entries
|
||||||
|
@ -1054,10 +1014,8 @@ impl GitPanel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if new_entries.len() > 0 {
|
if new_entries.len() > 0 {
|
||||||
let toggle_state = ToggleState::from_any_and_all(new_any_staged, new_all_staged);
|
|
||||||
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
||||||
header: Section::New,
|
header: Section::Created,
|
||||||
all_staged: toggle_state,
|
|
||||||
}));
|
}));
|
||||||
self.entries
|
self.entries
|
||||||
.extend(new_entries.into_iter().map(GitListEntry::GitStatusEntry));
|
.extend(new_entries.into_iter().map(GitListEntry::GitStatusEntry));
|
||||||
|
@ -1068,12 +1026,45 @@ impl GitPanel {
|
||||||
self.entries_by_path.insert(status_entry.repo_path, ix);
|
self.entries_by_path.insert(status_entry.repo_path, ix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.can_commit = has_changed_checked_boxes || has_added_checked_boxes;
|
||||||
|
self.can_commit_all = has_changed || has_added_checked_boxes;
|
||||||
|
|
||||||
self.select_first_entry_if_none(cx);
|
self.select_first_entry_if_none(cx);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_state(&self, header_type: Section) -> ToggleState {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut staged_count = 0;
|
||||||
|
'outer: for entry in &self.entries {
|
||||||
|
let Some(entry) = entry.status_entry() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if entry.status.is_created() != (header_type == Section::Created) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
for pending in self.pending.iter().rev() {
|
||||||
|
if pending.repo_paths.contains(&entry.repo_path) {
|
||||||
|
if pending.will_become_staged {
|
||||||
|
staged_count += 1;
|
||||||
|
}
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
staged_count += entry.status.is_staged().unwrap_or(false) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if staged_count == 0 {
|
||||||
|
ToggleState::Unselected
|
||||||
|
} else if count == staged_count {
|
||||||
|
ToggleState::Selected
|
||||||
|
} else {
|
||||||
|
ToggleState::Indeterminate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_err_toast(&self, e: anyhow::Error, cx: &mut App) {
|
fn show_err_toast(&self, e: anyhow::Error, cx: &mut App) {
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return;
|
return;
|
||||||
|
@ -1089,7 +1080,6 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitPanel –– Render
|
|
||||||
impl GitPanel {
|
impl GitPanel {
|
||||||
pub fn panel_button(
|
pub fn panel_button(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1199,21 +1189,13 @@ impl GitPanel {
|
||||||
pub fn render_commit_editor(
|
pub fn render_commit_editor(
|
||||||
&self,
|
&self,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
can_commit: bool,
|
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let editor = self.commit_editor.clone();
|
let editor = self.commit_editor.clone();
|
||||||
let can_commit = can_commit && !editor.read(cx).is_empty(cx);
|
let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
|
||||||
|
let can_commit_all =
|
||||||
|
!self.commit_pending && self.can_commit_all && !editor.read(cx).is_empty(cx);
|
||||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||||
let (can_commit, can_commit_all) =
|
|
||||||
self.active_repository
|
|
||||||
.as_ref()
|
|
||||||
.map_or((false, false), |active_repository| {
|
|
||||||
(
|
|
||||||
can_commit && active_repository.can_commit(false),
|
|
||||||
can_commit && active_repository.can_commit(true),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let focus_handle_1 = self.focus_handle(cx).clone();
|
let focus_handle_1 = self.focus_handle(cx).clone();
|
||||||
let focus_handle_2 = self.focus_handle(cx).clone();
|
let focus_handle_2 = self.focus_handle(cx).clone();
|
||||||
|
@ -1466,7 +1448,7 @@ impl GitPanel {
|
||||||
has_write_access: bool,
|
has_write_access: bool,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let checkbox = Checkbox::new(header.title(), header.all_staged)
|
let checkbox = Checkbox::new(header.title(), self.header_state(header.header))
|
||||||
.disabled(!has_write_access)
|
.disabled(!has_write_access)
|
||||||
.fill()
|
.fill()
|
||||||
.elevation(ElevationIndex::Surface);
|
.elevation(ElevationIndex::Surface);
|
||||||
|
@ -1510,7 +1492,14 @@ impl GitPanel {
|
||||||
.map(|name| name.to_string_lossy().into_owned())
|
.map(|name| name.to_string_lossy().into_owned())
|
||||||
.unwrap_or_else(|| entry.repo_path.to_string_lossy().into_owned());
|
.unwrap_or_else(|| entry.repo_path.to_string_lossy().into_owned());
|
||||||
|
|
||||||
let pending = self.pending.get(&entry.repo_path).copied();
|
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 repo_path = entry.repo_path.clone();
|
||||||
let selected = self.selected_entry == Some(ix);
|
let selected = self.selected_entry == Some(ix);
|
||||||
let status_style = GitPanelSettings::get_global(cx).status_style;
|
let status_style = GitPanelSettings::get_global(cx).status_style;
|
||||||
|
@ -1559,13 +1548,19 @@ impl GitPanel {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
cx.stop_propagation();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let start_slot = h_flex()
|
let start_slot = h_flex()
|
||||||
|
.id(("start-slot", ix))
|
||||||
.gap(DynamicSpacing::Base04.rems(cx))
|
.gap(DynamicSpacing::Base04.rems(cx))
|
||||||
.child(checkbox)
|
.child(checkbox)
|
||||||
.child(git_status_icon(status, cx));
|
.child(git_status_icon(status, cx))
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||||
|
// prevent the list item active state triggering when toggling checkbox
|
||||||
|
cx.stop_propagation();
|
||||||
|
});
|
||||||
|
|
||||||
let id = ElementId::Name(format!("entry_{}", display_name).into());
|
let id = ElementId::Name(format!("entry_{}", display_name).into());
|
||||||
|
|
||||||
|
@ -1581,27 +1576,14 @@ impl GitPanel {
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.disabled(!has_write_access)
|
.disabled(!has_write_access)
|
||||||
.on_click({
|
.on_click({
|
||||||
let repo_path = entry.repo_path.clone();
|
let entry = entry.clone();
|
||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |this, _, window, cx| {
|
||||||
this.selected_entry = Some(ix);
|
this.selected_entry = Some(ix);
|
||||||
window.dispatch_action(Box::new(OpenSelected), cx);
|
|
||||||
cx.notify();
|
|
||||||
let Some(workspace) = this.workspace.upgrade() else {
|
let Some(workspace) = this.workspace.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(git_repo) = this.active_repository.as_ref() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(path) = git_repo
|
|
||||||
.repo_path_to_project_path(&repo_path)
|
|
||||||
.and_then(|project_path| {
|
|
||||||
this.project.read(cx).absolute_path(&project_path, cx)
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
ProjectDiff::deploy_at(workspace, Some(path.into()), window, cx);
|
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1691,17 +1673,6 @@ impl Render for GitPanel {
|
||||||
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
|
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
|
||||||
this.toggle_staged_for_selected(&ToggleStaged, window, cx)
|
this.toggle_staged_for_selected(&ToggleStaged, window, cx)
|
||||||
}))
|
}))
|
||||||
.on_action(
|
|
||||||
cx.listener(|this, &StageAll, window, cx| {
|
|
||||||
this.stage_all(&StageAll, window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.on_action(cx.listener(|this, &UnstageAll, window, cx| {
|
|
||||||
this.unstage_all(&UnstageAll, window, cx)
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, &RevertAll, window, cx| {
|
|
||||||
this.discard_all(&RevertAll, window, cx)
|
|
||||||
}))
|
|
||||||
.when(can_commit, |git_panel| {
|
.when(can_commit, |git_panel| {
|
||||||
git_panel
|
git_panel
|
||||||
.on_action({
|
.on_action({
|
||||||
|
@ -1764,7 +1735,7 @@ impl Render for GitPanel {
|
||||||
self.render_empty_state(cx).into_any_element()
|
self.render_empty_state(cx).into_any_element()
|
||||||
})
|
})
|
||||||
.child(self.render_divider(cx))
|
.child(self.render_divider(cx))
|
||||||
.child(self.render_commit_editor(name_and_email, can_commit, cx))
|
.child(self.render_commit_editor(name_and_email, cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
use std::{
|
use std::any::{Any, TypeId};
|
||||||
any::{Any, TypeId},
|
|
||||||
path::Path,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
|
@ -14,7 +10,7 @@ use gpui::{
|
||||||
FocusHandle, Focusable, Render, Subscription, Task, WeakEntity,
|
FocusHandle, Focusable, Render, Subscription, Task, WeakEntity,
|
||||||
};
|
};
|
||||||
use language::{Anchor, Buffer, Capability, OffsetRangeExt};
|
use language::{Anchor, Buffer, Capability, OffsetRangeExt};
|
||||||
use multi_buffer::MultiBuffer;
|
use multi_buffer::{MultiBuffer, PathKey};
|
||||||
use project::{buffer_store::BufferChangeSet, git::GitState, Project, ProjectPath};
|
use project::{buffer_store::BufferChangeSet, git::GitState, Project, ProjectPath};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
@ -25,7 +21,7 @@ use workspace::{
|
||||||
ItemNavHistory, ToolbarItemLocation, Workspace,
|
ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::git_panel::GitPanel;
|
use crate::git_panel::{GitPanel, GitStatusEntry};
|
||||||
|
|
||||||
actions!(git, [Diff]);
|
actions!(git, [Diff]);
|
||||||
|
|
||||||
|
@ -37,18 +33,21 @@ pub(crate) struct ProjectDiff {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
update_needed: postage::watch::Sender<()>,
|
update_needed: postage::watch::Sender<()>,
|
||||||
pending_scroll: Option<Arc<Path>>,
|
pending_scroll: Option<PathKey>,
|
||||||
|
|
||||||
_task: Task<Result<()>>,
|
_task: Task<Result<()>>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DiffBuffer {
|
struct DiffBuffer {
|
||||||
abs_path: Arc<Path>,
|
path_key: PathKey,
|
||||||
buffer: Entity<Buffer>,
|
buffer: Entity<Buffer>,
|
||||||
change_set: Entity<BufferChangeSet>,
|
change_set: Entity<BufferChangeSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHANGED_NAMESPACE: &'static str = "0";
|
||||||
|
const ADDED_NAMESPACE: &'static str = "1";
|
||||||
|
|
||||||
impl ProjectDiff {
|
impl ProjectDiff {
|
||||||
pub(crate) fn register(
|
pub(crate) fn register(
|
||||||
_: &mut Workspace,
|
_: &mut Workspace,
|
||||||
|
@ -72,7 +71,7 @@ impl ProjectDiff {
|
||||||
|
|
||||||
pub fn deploy_at(
|
pub fn deploy_at(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
path: Option<Arc<Path>>,
|
entry: Option<GitStatusEntry>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
|
@ -92,9 +91,9 @@ impl ProjectDiff {
|
||||||
);
|
);
|
||||||
project_diff
|
project_diff
|
||||||
};
|
};
|
||||||
if let Some(path) = path {
|
if let Some(entry) = entry {
|
||||||
project_diff.update(cx, |project_diff, cx| {
|
project_diff.update(cx, |project_diff, cx| {
|
||||||
project_diff.scroll_to(path, window, cx);
|
project_diff.scroll_to(entry, window, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,10 +125,8 @@ impl ProjectDiff {
|
||||||
let git_state_subscription = cx.subscribe_in(
|
let git_state_subscription = cx.subscribe_in(
|
||||||
&git_state,
|
&git_state,
|
||||||
window,
|
window,
|
||||||
move |this, _git_state, event, _window, _cx| match event {
|
move |this, _git_state, _event, _window, _cx| {
|
||||||
project::git::Event::RepositoriesUpdated => {
|
*this.update_needed.borrow_mut() = ();
|
||||||
*this.update_needed.borrow_mut() = ();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -155,15 +152,39 @@ impl ProjectDiff {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_to(&mut self, path: Arc<Path>, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn scroll_to(
|
||||||
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path, cx) {
|
&mut self,
|
||||||
|
entry: GitStatusEntry,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(path) = git_repo
|
||||||
|
.repo_path_to_project_path(&entry.repo_path)
|
||||||
|
.and_then(|project_path| self.project.read(cx).absolute_path(&project_path, cx))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let path_key = if entry.status.is_created() {
|
||||||
|
PathKey::namespaced(ADDED_NAMESPACE, &path)
|
||||||
|
} else {
|
||||||
|
PathKey::namespaced(CHANGED_NAMESPACE, &path)
|
||||||
|
};
|
||||||
|
self.scroll_to_path(path_key, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||||
s.select_ranges([position..position]);
|
s.select_ranges([position..position]);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.pending_scroll = Some(path);
|
self.pending_scroll = Some(path_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,9 +244,14 @@ impl ProjectDiff {
|
||||||
let Some(abs_path) = self.project.read(cx).absolute_path(&project_path, cx) else {
|
let Some(abs_path) = self.project.read(cx).absolute_path(&project_path, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let abs_path = Arc::from(abs_path);
|
// Craft some artificial paths so that created entries will appear last.
|
||||||
|
let path_key = if entry.status.is_created() {
|
||||||
|
PathKey::namespaced(ADDED_NAMESPACE, &abs_path)
|
||||||
|
} else {
|
||||||
|
PathKey::namespaced(CHANGED_NAMESPACE, &abs_path)
|
||||||
|
};
|
||||||
|
|
||||||
previous_paths.remove(&abs_path);
|
previous_paths.remove(&path_key);
|
||||||
let load_buffer = self
|
let load_buffer = self
|
||||||
.project
|
.project
|
||||||
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||||
|
@ -235,11 +261,11 @@ impl ProjectDiff {
|
||||||
let buffer = load_buffer.await?;
|
let buffer = load_buffer.await?;
|
||||||
let changes = project
|
let changes = project
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.open_unstaged_changes(buffer.clone(), cx)
|
project.open_uncommitted_changes(buffer.clone(), cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Ok(DiffBuffer {
|
Ok(DiffBuffer {
|
||||||
abs_path,
|
path_key,
|
||||||
buffer,
|
buffer,
|
||||||
change_set: changes,
|
change_set: changes,
|
||||||
})
|
})
|
||||||
|
@ -259,7 +285,7 @@ impl ProjectDiff {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let abs_path = diff_buffer.abs_path;
|
let path_key = diff_buffer.path_key;
|
||||||
let buffer = diff_buffer.buffer;
|
let buffer = diff_buffer.buffer;
|
||||||
let change_set = diff_buffer.change_set;
|
let change_set = diff_buffer.change_set;
|
||||||
|
|
||||||
|
@ -272,15 +298,15 @@ impl ProjectDiff {
|
||||||
|
|
||||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||||
multibuffer.set_excerpts_for_path(
|
multibuffer.set_excerpts_for_path(
|
||||||
abs_path.clone(),
|
path_key.clone(),
|
||||||
buffer,
|
buffer,
|
||||||
diff_hunk_ranges,
|
diff_hunk_ranges,
|
||||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if self.pending_scroll.as_ref() == Some(&abs_path) {
|
if self.pending_scroll.as_ref() == Some(&path_key) {
|
||||||
self.scroll_to(abs_path, window, cx);
|
self.scroll_to_path(path_key, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl RepositorySelector {
|
||||||
fn handle_project_git_event(
|
fn handle_project_git_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
git_state: &Entity<GitState>,
|
git_state: &Entity<GitState>,
|
||||||
_event: &project::git::Event,
|
_event: &project::git::GitEvent,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub struct MultiBuffer {
|
||||||
/// Contains the state of the buffers being edited
|
/// Contains the state of the buffers being edited
|
||||||
buffers: RefCell<HashMap<BufferId, BufferState>>,
|
buffers: RefCell<HashMap<BufferId, BufferState>>,
|
||||||
// only used by consumers using `set_excerpts_for_buffer`
|
// only used by consumers using `set_excerpts_for_buffer`
|
||||||
buffers_by_path: BTreeMap<Arc<Path>, Vec<ExcerptId>>,
|
buffers_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
|
||||||
diff_bases: HashMap<BufferId, ChangeSetState>,
|
diff_bases: HashMap<BufferId, ChangeSetState>,
|
||||||
all_diff_hunks_expanded: bool,
|
all_diff_hunks_expanded: bool,
|
||||||
subscriptions: Topic,
|
subscriptions: Topic,
|
||||||
|
@ -143,6 +143,15 @@ impl MultiBufferDiffHunk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
|
||||||
|
pub struct PathKey(String);
|
||||||
|
|
||||||
|
impl PathKey {
|
||||||
|
pub fn namespaced(namespace: &str, path: &Path) -> Self {
|
||||||
|
Self(format!("{}/{}", namespace, path.to_string_lossy()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type MultiBufferPoint = Point;
|
pub type MultiBufferPoint = Point;
|
||||||
type ExcerptOffset = TypedOffset<Excerpt>;
|
type ExcerptOffset = TypedOffset<Excerpt>;
|
||||||
type ExcerptPoint = TypedPoint<Excerpt>;
|
type ExcerptPoint = TypedPoint<Excerpt>;
|
||||||
|
@ -1395,7 +1404,7 @@ impl MultiBuffer {
|
||||||
anchor_ranges
|
anchor_ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn location_for_path(&self, path: &Arc<Path>, cx: &App) -> Option<Anchor> {
|
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
|
||||||
let excerpt_id = self.buffers_by_path.get(path)?.first()?;
|
let excerpt_id = self.buffers_by_path.get(path)?.first()?;
|
||||||
let snapshot = self.snapshot(cx);
|
let snapshot = self.snapshot(cx);
|
||||||
let excerpt = snapshot.excerpt(*excerpt_id)?;
|
let excerpt = snapshot.excerpt(*excerpt_id)?;
|
||||||
|
@ -1408,7 +1417,7 @@ impl MultiBuffer {
|
||||||
|
|
||||||
pub fn set_excerpts_for_path(
|
pub fn set_excerpts_for_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: Arc<Path>,
|
path: PathKey,
|
||||||
buffer: Entity<Buffer>,
|
buffer: Entity<Buffer>,
|
||||||
ranges: Vec<Range<Point>>,
|
ranges: Vec<Range<Point>>,
|
||||||
context_line_count: u32,
|
context_line_count: u32,
|
||||||
|
@ -1517,11 +1526,11 @@ impl MultiBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paths(&self) -> impl Iterator<Item = Arc<Path>> + '_ {
|
pub fn paths(&self) -> impl Iterator<Item = PathKey> + '_ {
|
||||||
self.buffers_by_path.keys().cloned()
|
self.buffers_by_path.keys().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_excerpts_for_path(&mut self, path: Arc<Path>, cx: &mut Context<Self>) {
|
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||||
if let Some(to_remove) = self.buffers_by_path.remove(&path) {
|
if let Some(to_remove) = self.buffers_by_path.remove(&path) {
|
||||||
self.remove_excerpts(to_remove, cx)
|
self.remove_excerpts(to_remove, cx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use language::{Buffer, Rope};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{env, path::PathBuf};
|
use std::env;
|
||||||
use util::test::sample_text;
|
use util::test::sample_text;
|
||||||
|
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
|
@ -1596,7 +1596,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let path1: Arc<Path> = Arc::from(PathBuf::from("path1"));
|
let path1: PathKey = PathKey::namespaced("0", Path::new("/"));
|
||||||
let buf2 = cx.new(|cx| {
|
let buf2 = cx.new(|cx| {
|
||||||
Buffer::local(
|
Buffer::local(
|
||||||
indoc! {
|
indoc! {
|
||||||
|
@ -1615,7 +1615,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let path2: Arc<Path> = Arc::from(PathBuf::from("path2"));
|
let path2 = PathKey::namespaced("x", Path::new("/"));
|
||||||
|
|
||||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||||
multibuffer.update(cx, |multibuffer, cx| {
|
multibuffer.update(cx, |multibuffer, cx| {
|
||||||
|
|
|
@ -149,37 +149,32 @@ impl BufferChangeSetState {
|
||||||
) -> oneshot::Receiver<()> {
|
) -> oneshot::Receiver<()> {
|
||||||
match diff_bases_change {
|
match diff_bases_change {
|
||||||
DiffBasesChange::SetIndex(index) => {
|
DiffBasesChange::SetIndex(index) => {
|
||||||
self.index_text = index.map(|mut text| {
|
let mut index = index.unwrap_or_default();
|
||||||
text::LineEnding::normalize(&mut text);
|
text::LineEnding::normalize(&mut index);
|
||||||
Arc::new(text)
|
self.index_text = Some(Arc::new(index));
|
||||||
});
|
|
||||||
self.index_changed = true;
|
self.index_changed = true;
|
||||||
}
|
}
|
||||||
DiffBasesChange::SetHead(head) => {
|
DiffBasesChange::SetHead(head) => {
|
||||||
self.head_text = head.map(|mut text| {
|
let mut head = head.unwrap_or_default();
|
||||||
text::LineEnding::normalize(&mut text);
|
text::LineEnding::normalize(&mut head);
|
||||||
Arc::new(text)
|
self.head_text = Some(Arc::new(head));
|
||||||
});
|
|
||||||
self.head_changed = true;
|
self.head_changed = true;
|
||||||
}
|
}
|
||||||
DiffBasesChange::SetBoth(mut text) => {
|
DiffBasesChange::SetBoth(text) => {
|
||||||
if let Some(text) = text.as_mut() {
|
let mut text = text.unwrap_or_default();
|
||||||
text::LineEnding::normalize(text);
|
text::LineEnding::normalize(&mut text);
|
||||||
}
|
self.head_text = Some(Arc::new(text));
|
||||||
self.head_text = text.map(Arc::new);
|
|
||||||
self.index_text = self.head_text.clone();
|
self.index_text = self.head_text.clone();
|
||||||
self.head_changed = true;
|
self.head_changed = true;
|
||||||
self.index_changed = true;
|
self.index_changed = true;
|
||||||
}
|
}
|
||||||
DiffBasesChange::SetEach { index, head } => {
|
DiffBasesChange::SetEach { index, head } => {
|
||||||
self.index_text = index.map(|mut text| {
|
let mut index = index.unwrap_or_default();
|
||||||
text::LineEnding::normalize(&mut text);
|
text::LineEnding::normalize(&mut index);
|
||||||
Arc::new(text)
|
let mut head = head.unwrap_or_default();
|
||||||
});
|
text::LineEnding::normalize(&mut head);
|
||||||
self.head_text = head.map(|mut text| {
|
self.index_text = Some(Arc::new(index));
|
||||||
text::LineEnding::normalize(&mut text);
|
self.head_text = Some(Arc::new(head));
|
||||||
Arc::new(text)
|
|
||||||
});
|
|
||||||
self.head_changed = true;
|
self.head_changed = true;
|
||||||
self.index_changed = true;
|
self.index_changed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,13 @@ enum Message {
|
||||||
Unstage(GitRepo, Vec<RepoPath>),
|
Unstage(GitRepo, Vec<RepoPath>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum GitEvent {
|
||||||
RepositoriesUpdated,
|
ActiveRepositoryChanged,
|
||||||
|
FileSystemUpdated,
|
||||||
|
GitStateUpdated,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for GitState {}
|
impl EventEmitter<GitEvent> for GitState {}
|
||||||
|
|
||||||
impl GitState {
|
impl GitState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -103,7 +105,7 @@ impl GitState {
|
||||||
fn on_worktree_store_event(
|
fn on_worktree_store_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree_store: Entity<WorktreeStore>,
|
worktree_store: Entity<WorktreeStore>,
|
||||||
_event: &WorktreeStoreEvent,
|
event: &WorktreeStoreEvent,
|
||||||
cx: &mut Context<'_, Self>,
|
cx: &mut Context<'_, Self>,
|
||||||
) {
|
) {
|
||||||
// TODO inspect the event
|
// TODO inspect the event
|
||||||
|
@ -172,7 +174,14 @@ impl GitState {
|
||||||
self.repositories = new_repositories;
|
self.repositories = new_repositories;
|
||||||
self.active_index = new_active_index;
|
self.active_index = new_active_index;
|
||||||
|
|
||||||
cx.emit(Event::RepositoriesUpdated);
|
match event {
|
||||||
|
WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
|
||||||
|
cx.emit(GitEvent::GitStateUpdated);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cx.emit(GitEvent::FileSystemUpdated);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_repositories(&self) -> Vec<RepositoryHandle> {
|
pub fn all_repositories(&self) -> Vec<RepositoryHandle> {
|
||||||
|
@ -314,7 +323,7 @@ impl RepositoryHandle {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
git_state.active_index = Some(index);
|
git_state.active_index = Some(index);
|
||||||
cx.emit(Event::RepositoriesUpdated);
|
cx.emit(GitEvent::ActiveRepositoryChanged);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue