Cargo.lock 🔗
@@ -7198,8 +7198,10 @@ dependencies = [
"git_ui",
"gpui",
"language",
+ "language_model",
"menu",
"project",
+ "project_panel",
"rand 0.9.2",
"remote_connection",
"search",
Anthony Eid created
Cargo.lock | 2
crates/git/src/repository.rs | 5
crates/git_graph/Cargo.toml | 2
crates/git_graph/src/git_graph.rs | 477 +++++++++++++++++++-----
crates/git_ui/src/git_panel.rs | 44 -
crates/project_panel/src/project_panel.rs | 107 +---
6 files changed, 428 insertions(+), 209 deletions(-)
@@ -7198,8 +7198,10 @@ dependencies = [
"git_ui",
"gpui",
"language",
+ "language_model",
"menu",
"project",
+ "project_panel",
"rand 0.9.2",
"remote_connection",
"search",
@@ -2732,7 +2732,6 @@ impl GitRepository for RealGitRepository {
async move {
let git = git_binary?;
- // todo!: should we include no optional locks here?
let mut git_log_command = vec![
"log",
GRAPH_COMMIT_FORMAT,
@@ -2826,6 +2825,10 @@ impl GitRepository for RealGitRepository {
args.push("--grep");
args.push(search_args.query.as_str());
+ if let LogSource::File(file_path) = &log_source {
+ args.extend(["--", file_path.as_unix_str()]);
+ }
+
let mut command = git.build_command(&args);
command.stdout(Stdio::piped());
command.stderr(Stdio::null());
@@ -30,6 +30,7 @@ gpui.workspace = true
language.workspace = true
menu.workspace = true
project.workspace = true
+project_panel.workspace = true
search.workspace = true
settings.workspace = true
smallvec.workspace = true
@@ -45,6 +46,7 @@ db = { workspace = true, features = ["test-support"] }
fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
+language_model.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
remote_connection = { workspace = true, features = ["test-support"] }
@@ -26,6 +26,7 @@ use project::{
RepositoryEvent, RepositoryId,
},
};
+use project_panel::ProjectPanel;
use search::{
SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch,
ToggleCaseSensitive, buffer_search,
@@ -734,15 +735,32 @@ pub fn init(cx: &mut App) {
workspace::register_serializable_item::<GitGraph>(cx);
cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
- workspace.register_action_renderer(|div, workspace, _, cx| {
- let active_item_file = workspace
- .active_item(cx)
- .and_then(|item| item.downcast::<Editor>())
- .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
- .and_then(|buffer| buffer.read(cx).file())
- .cloned();
-
- div.when(
+ workspace.register_action_renderer(|div, workspace, window, cx| {
+ div.when_some(
+ resolve_file_history_target(workspace, window, cx),
+ |div, (repo_id, log_source)| {
+ let git_store = workspace.project().read(cx).git_store().clone();
+ let workspace = workspace.weak_handle();
+
+ div.on_action(move |_: &git::FileHistory, window, cx| {
+ let git_store = git_store.clone();
+ workspace
+ .update(cx, |workspace, cx| {
+ open_or_reuse_graph(
+ workspace,
+ repo_id,
+ git_store,
+ log_source.clone(),
+ None,
+ window,
+ cx,
+ );
+ })
+ .ok();
+ })
+ },
+ )
+ .when(
workspace.project().read(cx).active_repository(cx).is_some(),
|div| {
let workspace = workspace.weak_handle();
@@ -759,31 +777,14 @@ pub fn init(cx: &mut App) {
};
let selected_repo_id = repo.read(cx).id;
- let existing = workspace
- .items_of_type::<GitGraph>(cx)
- .find(|graph| graph.read(cx).repo_id == selected_repo_id);
- if let Some(existing) = existing {
- workspace.activate_item(&existing, true, true, window, cx);
- return;
- }
-
let git_store =
workspace.project().read(cx).git_store().clone();
- let workspace_handle = workspace.weak_handle();
- let git_graph = cx.new(|cx| {
- GitGraph::new(
- selected_repo_id,
- git_store,
- workspace_handle,
- None,
- window,
- cx,
- )
- });
- workspace.add_item_to_active_pane(
- Box::new(git_graph),
+ open_or_reuse_graph(
+ workspace,
+ selected_repo_id,
+ git_store,
+ LogSource::All,
None,
- true,
window,
cx,
);
@@ -803,36 +804,14 @@ pub fn init(cx: &mut App) {
};
let selected_repo_id = repo.read(cx).id;
- let existing = workspace
- .items_of_type::<GitGraph>(cx)
- .find(|graph| graph.read(cx).repo_id == selected_repo_id);
- if let Some(existing) = existing {
- existing.update(cx, |graph, cx| {
- graph.select_commit_by_sha(sha.as_str(), cx);
- });
- workspace.activate_item(&existing, true, true, window, cx);
- return;
- }
-
let git_store =
workspace.project().read(cx).git_store().clone();
- let workspace_handle = workspace.weak_handle();
- let git_graph = cx.new(|cx| {
- let mut graph = GitGraph::new(
- selected_repo_id,
- git_store,
- workspace_handle,
- None,
- window,
- cx,
- );
- graph.select_commit_by_sha(sha.as_str(), cx);
- graph
- });
- workspace.add_item_to_active_pane(
- Box::new(git_graph),
- None,
- true,
+ open_or_reuse_graph(
+ workspace,
+ selected_repo_id,
+ git_store,
+ LogSource::All,
+ Some(sha),
window,
cx,
);
@@ -842,58 +821,91 @@ pub fn init(cx: &mut App) {
)
},
)
- .when_some(active_item_file, move |this, active_file| {
- this.on_action({
- let workspace = workspace.weak_handle();
+ });
+ })
+ .detach();
+}
- move |_: &git::FileHistory, window, cx| {
- workspace
- .update(cx, |workspace, cx| {
- let git_store = workspace.project().read(cx).git_store().clone();
- let workspace_handle = workspace.weak_handle();
- let file_path = active_file.path();
- let file_worktree_id = active_file.worktree_id(cx);
-
- let project_path = ProjectPath {
- worktree_id: file_worktree_id,
- path: file_path.clone(),
- };
+fn resolve_file_history_target(
+ workspace: &Workspace,
+ window: &Window,
+ cx: &App,
+) -> Option<(RepositoryId, LogSource)> {
+ if let Some(panel) = workspace.panel::<ProjectPanel>(cx)
+ && panel.read(cx).focus_handle(cx).contains_focused(window, cx)
+ && let Some(project_path) = panel.read(cx).selected_file_project_path(cx)
+ {
+ let git_store = workspace.project().read(cx).git_store();
+ let (repo, repo_path) = git_store
+ .read(cx)
+ .repository_and_path_for_project_path(&project_path, cx)?;
+ return Some((repo.read(cx).id, LogSource::File(repo_path)));
+ }
- let Some((repo, repo_path)) = git_store
- .read(cx)
- .repository_and_path_for_project_path(&project_path, cx)
- else {
- return;
- };
+ if let Some(panel) = workspace.panel::<git_ui::git_panel::GitPanel>(cx)
+ && panel.read(cx).focus_handle(cx).contains_focused(window, cx)
+ && let Some((repository, repo_path)) = panel.read(cx).selected_file_history_target()
+ {
+ return Some((repository.read(cx).id, LogSource::File(repo_path)));
+ }
- let repo_id = repo.read(cx).id;
- let log_source = LogSource::File(repo_path);
+ let editor = workspace.active_item_as::<Editor>(cx)?;
- let git_graph = cx.new(|cx| {
- GitGraph::new(
- repo_id,
- git_store,
- workspace_handle,
- Some(log_source),
- window,
- cx,
- )
- });
- workspace.add_item_to_active_pane(
- Box::new(git_graph),
- None,
- true,
- window,
- cx,
- );
- })
- .ok();
- }
- })
- })
- });
- })
- .detach();
+ let file = editor
+ .read(cx)
+ .file_at(editor.read(cx).selections.newest_anchor().head(), cx)?;
+ let project_path = ProjectPath {
+ worktree_id: file.worktree_id(cx),
+ path: file.path().clone(),
+ };
+
+ let git_store = workspace.project().read(cx).git_store();
+ let (repo, repo_path) = git_store
+ .read(cx)
+ .repository_and_path_for_project_path(&project_path, cx)?;
+ Some((repo.read(cx).id, LogSource::File(repo_path)))
+}
+
+fn open_or_reuse_graph(
+ workspace: &mut Workspace,
+ repo_id: RepositoryId,
+ git_store: Entity<GitStore>,
+ log_source: LogSource,
+ sha: Option<String>,
+ window: &mut Window,
+ cx: &mut Context<Workspace>,
+) {
+ let existing = workspace.items_of_type::<GitGraph>(cx).find(|graph| {
+ let graph = graph.read(cx);
+ graph.repo_id == repo_id && graph.log_source == log_source
+ });
+
+ if let Some(existing) = existing {
+ if let Some(sha) = sha {
+ existing.update(cx, |graph, cx| {
+ graph.select_commit_by_sha(sha.as_str(), cx);
+ });
+ }
+ workspace.activate_item(&existing, true, true, window, cx);
+ return;
+ }
+
+ let workspace_handle = workspace.weak_handle();
+ let git_graph = cx.new(|cx| {
+ let mut graph = GitGraph::new(
+ repo_id,
+ git_store,
+ workspace_handle,
+ Some(log_source),
+ window,
+ cx,
+ );
+ if let Some(sha) = sha {
+ graph.select_commit_by_sha(sha.as_str(), cx);
+ }
+ graph
+ });
+ workspace.add_item_to_active_pane(Box::new(git_graph), None, true, window, cx);
}
fn lane_center_x(bounds: Bounds<Pixels>, lane: f32) -> Pixels {
@@ -1628,9 +1640,11 @@ impl GitGraph {
.and_then(|data| data.commit_oid_to_index.get(&oid))
.copied()
else {
+ this.pending_select_sha = Some(oid);
return;
};
+ this.pending_select_sha = None;
this.select_entry(index, ScrollStrategy::Center, cx);
}
@@ -2910,11 +2924,22 @@ impl Item for GitGraph {
.file_name()
.map(|name| name.to_string_lossy().to_string())
});
+ let file_history_path = match &self.log_source {
+ LogSource::File(path) => Some(path.as_unix_str().to_string()),
+ _ => None,
+ };
Some(TabTooltipContent::Custom(Box::new(Tooltip::element({
move |_, _| {
v_flex()
- .child(Label::new("Git Graph"))
+ .child(Label::new(if file_history_path.is_some() {
+ "File History"
+ } else {
+ "Git Graph"
+ }))
+ .when_some(file_history_path.clone(), |this, path| {
+ this.child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
+ })
.when_some(repo_name.clone(), |this, name| {
this.child(Label::new(name).color(Color::Muted).size(LabelSize::Small))
})
@@ -2924,6 +2949,14 @@ impl Item for GitGraph {
}
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
+ if let LogSource::File(path) = &self.log_source {
+ return path
+ .as_ref()
+ .file_name()
+ .map(|name| SharedString::from(name.to_string()))
+ .unwrap_or_else(|| SharedString::from(path.as_unix_str().to_string()));
+ }
+
self.get_repository(cx)
.and_then(|repo| {
repo.read(cx)
@@ -3117,6 +3150,10 @@ mod tests {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
theme_settings::init(theme::LoadThemes::JustBase, cx);
+ language_model::init(cx);
+ git_ui::init(cx);
+ project_panel::init(cx);
+ init(cx);
});
}
@@ -3942,6 +3979,230 @@ mod tests {
);
}
+ #[gpui::test]
+ async fn test_file_history_action_uses_focused_source_and_reuses_matching_graph(
+ cx: &mut TestAppContext,
+ ) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ Path::new("/project"),
+ json!({
+ ".git": {},
+ "tracked1.txt": "tracked 1",
+ "tracked2.txt": "tracked 2",
+ }),
+ )
+ .await;
+
+ let commits = vec![Arc::new(InitialGraphCommitData {
+ sha: Oid::from_bytes(&[1; 20]).unwrap(),
+ parents: smallvec![],
+ ref_names: vec!["HEAD".into(), "refs/heads/main".into()],
+ })];
+ fs.set_graph_commits(Path::new("/project/.git"), commits);
+
+ let project = Project::test(fs.clone(), [Path::new("/project")], cx).await;
+ cx.run_until_parked();
+
+ let repository = project.read_with(cx, |project, cx| {
+ project
+ .active_repository(cx)
+ .expect("should have active repository")
+ });
+ let tracked1_repo_path = RepoPath::new(&"tracked1.txt").unwrap();
+ let tracked2_repo_path = RepoPath::new(&"tracked2.txt").unwrap();
+ let tracked1 = repository
+ .read_with(cx, |repository, cx| {
+ repository.repo_path_to_project_path(&tracked1_repo_path, cx)
+ })
+ .expect("tracked1 should resolve to project path");
+ let tracked2 = repository
+ .read_with(cx, |repository, cx| {
+ repository.repo_path_to_project_path(&tracked2_repo_path, cx)
+ })
+ .expect("tracked2 should resolve to project path");
+
+ let workspace_window = cx.add_window(|window, cx| {
+ workspace::MultiWorkspace::test_new(project.clone(), window, cx)
+ });
+ let workspace = workspace_window
+ .read_with(cx, |multi, _| multi.workspace().clone())
+ .expect("workspace should exist");
+
+ let (weak_workspace, async_window_cx) = workspace_window
+ .update(cx, |multi, window, cx| {
+ (multi.workspace().downgrade(), window.to_async(cx))
+ })
+ .expect("window should be available");
+ cx.background_executor.allow_parking();
+ let project_panel = cx
+ .foreground_executor()
+ .clone()
+ .block_test(ProjectPanel::load(
+ weak_workspace.clone(),
+ async_window_cx.clone(),
+ ))
+ .expect("project panel should load");
+ let git_panel = cx
+ .foreground_executor()
+ .clone()
+ .block_test(git_ui::git_panel::GitPanel::load(
+ weak_workspace,
+ async_window_cx,
+ ))
+ .expect("git panel should load");
+ cx.background_executor.forbid_parking();
+
+ workspace_window
+ .update(cx, |multi, window, cx| {
+ let workspace = multi.workspace();
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_panel(project_panel.clone(), window, cx);
+ workspace.add_panel(git_panel.clone(), window, cx);
+ });
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+
+ workspace_window
+ .update(cx, |multi, window, cx| {
+ let workspace = multi.workspace();
+ project_panel.update(cx, |panel, cx| {
+ panel.select_path_for_test(tracked1.clone(), cx)
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.focus_panel::<ProjectPanel>(window, cx);
+ });
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+ workspace_window
+ .update(cx, |_, window, cx| {
+ window.dispatch_action(Box::new(git::FileHistory), cx);
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+
+ workspace.read_with(cx, |workspace, cx| {
+ let graphs = workspace.items_of_type::<GitGraph>(cx).collect::<Vec<_>>();
+ assert_eq!(graphs.len(), 1);
+ assert_eq!(
+ graphs[0].read(cx).log_source,
+ LogSource::File(tracked1_repo_path.clone())
+ );
+ });
+
+ workspace_window
+ .update(cx, |multi, window, cx| {
+ let workspace = multi.workspace();
+ git_panel.update(cx, |panel, cx| {
+ panel.select_entry_by_path(tracked1.clone(), window, cx);
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.focus_panel::<git_ui::git_panel::GitPanel>(window, cx);
+ });
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+ workspace_window
+ .update(cx, |_, window, cx| {
+ window.dispatch_action(Box::new(git::FileHistory), cx);
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+
+ workspace.read_with(cx, |workspace, cx| {
+ let graphs = workspace.items_of_type::<GitGraph>(cx).collect::<Vec<_>>();
+ assert_eq!(graphs.len(), 1);
+ assert_eq!(
+ graphs[0].read(cx).log_source,
+ LogSource::File(tracked1_repo_path.clone())
+ );
+ });
+
+ let tracked1_buffer = project
+ .update(cx, |project, cx| project.open_buffer(tracked1.clone(), cx))
+ .await
+ .expect("tracked1 buffer should open");
+ let tracked2_buffer = project
+ .update(cx, |project, cx| project.open_buffer(tracked2.clone(), cx))
+ .await
+ .expect("tracked2 buffer should open");
+ workspace_window
+ .update(cx, |multi, window, cx| {
+ let workspace = multi.workspace();
+ let multibuffer = cx.new(|cx| {
+ let mut multibuffer = editor::MultiBuffer::new(language::Capability::ReadWrite);
+ multibuffer.set_excerpts_for_buffer(
+ tracked1_buffer.clone(),
+ [Default::default()..tracked1_buffer.read(cx).max_point()],
+ 0,
+ cx,
+ );
+ multibuffer.set_excerpts_for_buffer(
+ tracked2_buffer.clone(),
+ [Default::default()..tracked2_buffer.read(cx).max_point()],
+ 0,
+ cx,
+ );
+ multibuffer
+ });
+ let editor = cx.new(|cx| {
+ Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item_to_active_pane(
+ Box::new(editor.clone()),
+ None,
+ true,
+ window,
+ cx,
+ );
+ });
+ editor.update(cx, |editor, cx| {
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ let second_excerpt_point = snapshot
+ .range_for_buffer(tracked2_buffer.read(cx).remote_id())
+ .expect("tracked2 excerpt should exist")
+ .start;
+ let anchor = snapshot.anchor_before(second_excerpt_point);
+ editor.change_selections(
+ editor::SelectionEffects::no_scroll(),
+ window,
+ cx,
+ |selections| {
+ selections.select_anchor_ranges([anchor..anchor]);
+ },
+ );
+ window.focus(&editor.focus_handle(cx), cx);
+ });
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+
+ workspace_window
+ .update(cx, |_, window, cx| {
+ window.dispatch_action(Box::new(git::FileHistory), cx);
+ })
+ .expect("workspace window should be available");
+ cx.run_until_parked();
+
+ workspace.read_with(cx, |workspace, cx| {
+ let graphs = workspace.items_of_type::<GitGraph>(cx).collect::<Vec<_>>();
+ assert_eq!(graphs.len(), 2);
+ let latest = graphs
+ .into_iter()
+ .max_by_key(|graph| graph.entity_id())
+ .expect("expected a git graph");
+ assert_eq!(
+ latest.read(cx).log_source,
+ LogSource::File(tracked2_repo_path)
+ );
+ });
+ }
+
#[gpui::test]
async fn test_graph_data_reloaded_after_stash_change(cx: &mut TestAppContext) {
init_test(cx);
@@ -7,8 +7,7 @@ use crate::project_diff::{self, BranchDiff, Diff, ProjectDiff};
use crate::remote_output::{self, RemoteAction, SuccessMessage};
use crate::{branch_picker, picker_prompt, render_remote_button};
use crate::{
- file_history_view::FileHistoryView, git_panel_settings::GitPanelSettings, git_status_icon,
- repository_selector::RepositorySelector,
+ git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
};
use agent_settings::AgentSettings;
use anyhow::Context as _;
@@ -1300,26 +1299,6 @@ impl GitPanel {
});
}
- fn file_history(&mut self, _: &git::FileHistory, window: &mut Window, cx: &mut Context<Self>) {
- maybe!({
- let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
- let active_repo = self.active_repository.as_ref()?;
- let repo_path = entry.repo_path.clone();
- let git_store = self.project.read(cx).git_store();
-
- FileHistoryView::open(
- repo_path,
- git_store.downgrade(),
- active_repo.downgrade(),
- self.workspace.clone(),
- window,
- cx,
- );
-
- Some(())
- });
- }
-
fn open_file(
&mut self,
_: &menu::SecondaryConfirm,
@@ -4983,8 +4962,11 @@ impl GitPanel {
.separator()
.action("Open Diff", menu::Confirm.boxed_clone())
.action("Open File", menu::SecondaryConfirm.boxed_clone())
- .separator()
- .action_disabled_when(is_created, "View File History", Box::new(git::FileHistory))
+ .when(!is_created, |context_menu| {
+ context_menu
+ .separator()
+ .action("View File History", Box::new(git::FileHistory))
+ })
});
self.selected_entry = Some(ix);
self.set_context_menu(context_menu, position, window, cx);
@@ -5617,6 +5599,17 @@ impl GitPanel {
}
}
+impl GitPanel {
+ pub fn selected_file_history_target(&self) -> Option<(Entity<Repository>, RepoPath)> {
+ let entry = self.get_selected_entry()?.status_entry()?;
+ let repository = self.active_repository.clone()?;
+ if entry.status.is_created() {
+ return None;
+ }
+ Some((repository, entry.repo_path.clone()))
+ }
+}
+
#[cfg(any(test, feature = "test-support"))]
impl GitPanel {
pub fn new_test(
@@ -5685,9 +5678,6 @@ impl Render for GitPanel {
.on_action(cx.listener(Self::close_panel))
.on_action(cx.listener(Self::open_diff))
.on_action(cx.listener(Self::open_file))
- // TODO!: We should remove this listener, so that git graph
- // implementation of file history is used.
- .on_action(cx.listener(Self::file_history))
.on_action(cx.listener(Self::focus_changes_list))
.on_action(cx.listener(Self::focus_editor))
.on_action(cx.listener(Self::expand_commit_editor))
@@ -523,74 +523,6 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.delete(action, window, cx));
}
});
-
- // TODO!: We should remove this `register_action` call, so that git
- // graph implementation of file history is used.
- workspace.register_action(|workspace, _: &git::FileHistory, window, cx| {
- // First try to get from project panel if it's focused
- if let Some(panel) = workspace.panel::<ProjectPanel>(cx) {
- let maybe_project_path = panel.read(cx).selection.and_then(|selection| {
- let project = workspace.project().read(cx);
- let worktree = project.worktree_for_id(selection.worktree_id, cx)?;
- let entry = worktree.read(cx).entry_for_id(selection.entry_id)?;
- if entry.is_file() {
- Some(ProjectPath {
- worktree_id: selection.worktree_id,
- path: entry.path.clone(),
- })
- } else {
- None
- }
- });
-
- if let Some(project_path) = maybe_project_path {
- let project = workspace.project();
- let git_store = project.read(cx).git_store();
- if let Some((repo, repo_path)) = git_store
- .read(cx)
- .repository_and_path_for_project_path(&project_path, cx)
- {
- git_ui::file_history_view::FileHistoryView::open(
- repo_path,
- git_store.downgrade(),
- repo.downgrade(),
- workspace.weak_handle(),
- window,
- cx,
- );
- return;
- }
- }
- }
-
- // Fallback: try to get from active editor
- if let Some(active_item) = workspace.active_item(cx)
- && let Some(editor) = active_item.downcast::<Editor>()
- && let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton()
- && let Some(file) = buffer.read(cx).file()
- {
- let worktree_id = file.worktree_id(cx);
- let project_path = ProjectPath {
- worktree_id,
- path: file.path().clone(),
- };
- let project = workspace.project();
- let git_store = project.read(cx).git_store();
- if let Some((repo, repo_path)) = git_store
- .read(cx)
- .repository_and_path_for_project_path(&project_path, cx)
- {
- git_ui::file_history_view::FileHistoryView::open(
- repo_path,
- git_store.downgrade(),
- repo.downgrade(),
- workspace.weak_handle(),
- window,
- cx,
- );
- }
- }
- });
})
.detach();
}
@@ -1115,16 +1047,18 @@ impl ProjectPanel {
|| (settings.hide_root && visible_worktrees_count == 1));
let should_show_compare = !is_dir && self.file_abs_paths_to_diff(cx).is_some();
- let has_git_repo = !is_dir && {
+ let has_file_history = !is_dir && {
let project_path = project::ProjectPath {
worktree_id,
path: entry.path.clone(),
};
- project
- .git_store()
- .read(cx)
+ let git_store = project.git_store().read(cx);
+ git_store
.repository_and_path_for_project_path(&project_path, cx)
.is_some()
+ && !git_store
+ .project_path_git_status(&project_path, cx)
+ .is_some_and(|status| status.is_created())
};
let has_pasteable_content = self.has_pasteable_content(cx);
@@ -1192,7 +1126,7 @@ impl ProjectPanel {
Box::new(git::RestoreFile { skip_prompt: false }),
)
})
- .when(has_git_repo, |menu| {
+ .when(has_file_history, |menu| {
menu.separator()
.action("View File History", Box::new(git::FileHistory))
})
@@ -3777,6 +3711,14 @@ impl ProjectPanel {
Some((worktree.read(cx), entry))
}
+ pub fn selected_file_project_path(&self, cx: &App) -> Option<ProjectPath> {
+ let (worktree, entry) = self.selected_sub_entry(cx)?;
+ Some(ProjectPath {
+ worktree_id: worktree.read(cx).id(),
+ path: entry.is_file().then(|| entry.path.clone())?,
+ })
+ }
+
/// Compared to selected_entry, this function resolves to the currently
/// selected subentry if dir auto-folding is enabled.
fn selected_sub_entry<'a>(
@@ -7249,6 +7191,25 @@ impl Panel for ProjectPanel {
}
}
+impl ProjectPanel {
+ pub fn select_path_for_test(&mut self, project_path: ProjectPath, cx: &App) {
+ let Some(worktree) = self
+ .project
+ .read(cx)
+ .worktree_for_id(project_path.worktree_id, cx)
+ else {
+ return;
+ };
+ let Some(entry) = worktree.read(cx).entry_for_path(project_path.path.as_ref()) else {
+ return;
+ };
+ self.selection = Some(SelectedEntry {
+ worktree_id: project_path.worktree_id,
+ entry_id: entry.id,
+ });
+ }
+}
+
impl Focusable for ProjectPanel {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()