Show `restart` transformation button after successful inline assist (#20439)

Wes Higbee and Danilo Leal created

When using inline assist, after successfully generating a transformation
it's not possible to generate a new transformation. Currently, you have
to modify the prompt (i.e. add a `<SPACE>` and hit `<ENTER>`) to
regenerate.

So, I changed the restart button to be visible after a successful
transformation. And in that case I map it to a different keyboard
shortcut because `menu::Confirm` is mapped to accept the current
suggestion.

Now, I can invoke a series of transforms back to back until I get what I
want!

It might also be desired to keep the accept button visible after
modifying the prompt (before submitting it). In that case we'll need to
remap accept to an alternate key (perhaps the same alt-shift-enter I am
using for restart. That wouldn't be too insane to remember. But maybe
someone has a better idea.

I don't care what the shortcut is, I just want the ability to regenerate
without adding/deleting spaces.

## Before

**Two choices** after a suggestions is presented. Also, a great example
of why I would want to regenerate the suggestion, it left some tokens
`<rewrite_this>`!

![CleanShot 2024-12-13 at 00 34
09](https://github.com/user-attachments/assets/3c1786ca-8ec5-48e2-b3dd-64de36e61f6a)

## After

**Three choices** after a suggestion is presented, the circular icon is
for regenerate, just like you see if you modify the prompt text.
![CleanShot 2024-12-13 at 00 37
58](https://github.com/user-attachments/assets/ceda300f-0851-48bf-ad3a-be44308c734e)


## Release Notes:

- Added Restart Button to Inline Assistant When PromptΒ Is Unchanged

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

assets/icons/rotate_cw.svg               |  5 ++
assets/keymaps/default-linux.json        |  1 
assets/keymaps/default-macos.json        |  1 
crates/assistant/src/inline_assistant.rs | 49 ++++++++++++++++---------
crates/menu/src/menu.rs                  |  1 
5 files changed, 39 insertions(+), 18 deletions(-)

Detailed changes

assets/icons/rotate_cw.svg πŸ”—

@@ -1 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 6.5L9.99556 4.21778C9.27778 3.5 8.12 3 7 3C6.20888 3 5.43552 3.2346 4.77772 3.67412C4.11992 4.11365 3.60723 4.73836 3.30448 5.46927C3.00173 6.20017 2.92252 7.00444 3.07686 7.78036C3.2312 8.55628 3.61216 9.26902 4.17157 9.82842C4.73098 10.3878 5.44372 10.7688 6.21964 10.9231C6.99556 11.0775 7.79983 10.9983 8.53073 10.6955C8.88113 10.5504 9.20712 10.357 9.5 10.1225" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 4V6.5H9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

assets/keymaps/default-linux.json πŸ”—

@@ -17,6 +17,7 @@
       "escape": "menu::Cancel",
       "ctrl-escape": "menu::Cancel",
       "ctrl-c": "menu::Cancel",
+      "alt-shift-enter": "menu::Restart",
       "alt-enter": ["picker::ConfirmInput", { "secondary": false }],
       "ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
       "ctrl-shift-w": "workspace::CloseWindow",

assets/keymaps/default-macos.json πŸ”—

@@ -24,6 +24,7 @@
       "cmd-escape": "menu::Cancel",
       "ctrl-escape": "menu::Cancel",
       "ctrl-c": "menu::Cancel",
+      "alt-shift-enter": "menu::Restart",
       "cmd-shift-w": "workspace::CloseWindow",
       "shift-escape": "workspace::ToggleZoom",
       "cmd-o": "workspace::Open",

crates/assistant/src/inline_assistant.rs πŸ”—

@@ -1441,6 +1441,15 @@ impl Render for PromptEditor {
                 ]
             }
             CodegenStatus::Error(_) | CodegenStatus::Done => {
+                let must_rerun =
+                    self.edited_since_done || matches!(status, CodegenStatus::Error(_));
+                // when accept button isn't visible, then restart maps to confirm
+                // when accept button is visible, then restart must be mapped to an alternate keyboard shortcut
+                let restart_key: &dyn gpui::Action = if must_rerun {
+                    &menu::Confirm
+                } else {
+                    &menu::Restart
+                };
                 vec![
                     IconButton::new("cancel", IconName::Close)
                         .icon_color(Color::Muted)
@@ -1450,23 +1459,22 @@ impl Render for PromptEditor {
                             cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
                         )
                         .into_any_element(),
-                    if self.edited_since_done || matches!(status, CodegenStatus::Error(_)) {
-                        IconButton::new("restart", IconName::RotateCw)
-                            .icon_color(Color::Info)
-                            .shape(IconButtonShape::Square)
-                            .tooltip(|cx| {
-                                Tooltip::with_meta(
-                                    "Restart Transformation",
-                                    Some(&menu::Confirm),
-                                    "Changes will be discarded",
-                                    cx,
-                                )
-                            })
-                            .on_click(cx.listener(|_, _, cx| {
-                                cx.emit(PromptEditorEvent::StartRequested);
-                            }))
-                            .into_any_element()
-                    } else {
+                    IconButton::new("restart", IconName::RotateCw)
+                        .icon_color(Color::Muted)
+                        .shape(IconButtonShape::Square)
+                        .tooltip(|cx| {
+                            Tooltip::with_meta(
+                                "Regenerate Transformation",
+                                Some(restart_key),
+                                "Current change will be discarded",
+                                cx,
+                            )
+                        })
+                        .on_click(cx.listener(|_, _, cx| {
+                            cx.emit(PromptEditorEvent::StartRequested);
+                        }))
+                        .into_any_element(),
+                    if !must_rerun {
                         IconButton::new("confirm", IconName::Check)
                             .icon_color(Color::Info)
                             .shape(IconButtonShape::Square)
@@ -1475,6 +1483,8 @@ impl Render for PromptEditor {
                                 cx.emit(PromptEditorEvent::ConfirmRequested);
                             }))
                             .into_any_element()
+                    } else {
+                        div().into_any_element()
                     },
                 ]
             }
@@ -1491,6 +1501,7 @@ impl Render for PromptEditor {
             .py(cx.line_height() / 2.5)
             .on_action(cx.listener(Self::confirm))
             .on_action(cx.listener(Self::cancel))
+            .on_action(cx.listener(Self::restart))
             .on_action(cx.listener(Self::move_up))
             .on_action(cx.listener(Self::move_down))
             .capture_action(cx.listener(Self::cycle_prev))
@@ -1837,6 +1848,10 @@ impl PromptEditor {
         }
     }
 
+    fn restart(&mut self, _: &menu::Restart, cx: &mut ViewContext<Self>) {
+        cx.emit(PromptEditorEvent::StartRequested);
+    }
+
     fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
         match self.codegen.read(cx).status(cx) {
             CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {