git_ui: Allow opening a file with the diff hunks expanded (#40616)

Josh Piasecki created

So i just discovered `editor::ExpandAllDiffHunks`

I have been really missing the ability to look at changes NOT in a multi
buffer so i was very pleased to finally figure out that this is already
possible in Zed.

i have seen alot of discussion/issues requesting this feature so i think
it is safe to say i'm not the only one that is not aware it exists.

i think the wording in the docs could better communicate what this
feature actually is, however, i think an even better way to show users
that this feature exists would be to just put it in front of them.

In the `GitPanel`:
- `menu::Confirm` opens the project diff
- `menu::SecondaryConfirm` opens the selected file in a new editor.

I think it would be REALLY nice if opening a file with
`SecondaryConfirm` opened the file with the diff hunks already expanded
and scrolled the editor to the first hunk.

ideally i see this being toggle-able in settings something like
`GitPanel - Open File with Diffs Expanded` or something. so the user
could turn this off if they preferred.

I tried creating a new keybinding using the new `actions::Sequence`
it was something like:
```json
{
  "context": "GitPanel && ChangesList",
  "bindings": {
    "cmd-enter" : [ "actions::Sequence", ["menu:SecondaryConfirm", "editor::ToggleFocus", "editor::ExpandAllDiffHunks", "editor::GoToHunk"]]
  }
}
```
but the action sequence does not work. i think because opening the file
is an async task.

i have a first attempt here, of just trying to get the diff hunks to
expand after opening the file.
i tried to copy and paste the logic/structure as best i could from the
confirm method in file_finder.rs:1432

it compiles, but it does not work, and i do not have enough experience
in rust or in this project to figure out anything further.

if anyone was interested in working on this with me i would enjoy
learning more and i think this would be a nice way to showcase this
tool!

Change summary

crates/git_ui/src/git_panel.rs | 51 +++++++++++++++++++++++++++++------
1 file changed, 42 insertions(+), 9 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -12,7 +12,9 @@ use agent_settings::AgentSettings;
 use anyhow::Context as _;
 use askpass::AskPassDelegate;
 use db::kvp::KEY_VALUE_STORE;
-use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
+use editor::{
+    Direction, Editor, EditorElement, EditorMode, MultiBuffer, actions::ExpandAllDiffHunks,
+};
 use futures::StreamExt as _;
 use git::blame::ParsedCommitMessage;
 use git::repository::{
@@ -69,7 +71,7 @@ use cloud_llm_client::CompletionIntent;
 use workspace::{
     Workspace,
     dock::{DockPosition, Panel, PanelEvent},
-    notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId},
+    notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt},
 };
 
 actions!(
@@ -792,15 +794,46 @@ impl GitPanel {
                 return None;
             }
 
-            self.workspace
+            let open_task = self
+                .workspace
                 .update(cx, |workspace, cx| {
-                    workspace
-                        .open_path_preview(path, None, false, false, true, window, cx)
-                        .detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
-                            Some(format!("{e}"))
-                        });
+                    workspace.open_path_preview(path, None, false, false, true, window, cx)
                 })
-                .ok()
+                .ok()?;
+
+            cx.spawn_in(window, async move |_, mut cx| {
+                let item = open_task
+                    .await
+                    .notify_async_err(&mut cx)
+                    .ok_or_else(|| anyhow::anyhow!("Failed to open file"))?;
+                if let Some(active_editor) = item.downcast::<Editor>() {
+                    if let Some(diff_task) =
+                        active_editor.update(cx, |editor, _cx| editor.wait_for_diff_to_load())?
+                    {
+                        diff_task.await;
+                    }
+
+                    cx.update(|window, cx| {
+                        active_editor.update(cx, |editor, cx| {
+                            editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
+
+                            let snapshot = editor.snapshot(window, cx);
+                            editor.go_to_hunk_before_or_after_position(
+                                &snapshot,
+                                language::Point::new(0, 0),
+                                Direction::Next,
+                                window,
+                                cx,
+                            );
+                        })
+                    })?;
+                }
+
+                anyhow::Ok(())
+            })
+            .detach();
+
+            Some(())
         });
     }