Introduce a New `assistant: insert into editor` Action (#13467)

Richard Feldman , Antonio Scandurra , and Bennet created

This implements the functionality (paired with @as-cii), but we weren't
sure what the clearest name would be for the action. It's essentially
the inverse of "quote selection" - but what's the opposite of quoting
the selection?

One idea:
* Rename "quote selection" to "Insert **into** assistant"
* Name this "Insert **from** assistant"

Release Notes:

- Added action to insert from assistant into editor (default keybinding:
`cmd-<` on macOS, `ctrl-<` on Linux)

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Bennet <bennet@zed.dev>

Change summary

assets/keymaps/default-linux.json       |  2 +
assets/keymaps/default-macos.json       |  2 +
crates/assistant/src/assistant.rs       |  1 
crates/assistant/src/assistant_panel.rs | 46 ++++++++++++++++++++++++--
4 files changed, 47 insertions(+), 4 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -155,6 +155,7 @@
       //   }
       // ],
       "ctrl->": "assistant::QuoteSelection",
+      "ctrl-<": "assistant::InsertIntoEditor",
       "ctrl-alt-e": "editor::SelectEnclosingSymbol"
     }
   },
@@ -548,6 +549,7 @@
       "ctrl-enter": "assistant::Assist",
       "ctrl-s": "workspace::Save",
       "ctrl->": "assistant::QuoteSelection",
+      "ctrl-<": "assistant::InsertIntoEditor",
       "shift-enter": "assistant::Split",
       "ctrl-r": "assistant::CycleMessageRole",
       "enter": "assistant::ConfirmCommand",

assets/keymaps/default-macos.json 🔗

@@ -193,6 +193,7 @@
         }
       ],
       "cmd->": "assistant::QuoteSelection",
+      "cmd-<": "assistant::InsertIntoEditor",
       "cmd-alt-e": "editor::SelectEnclosingSymbol"
     }
   },
@@ -238,6 +239,7 @@
       "cmd-enter": "assistant::Assist",
       "cmd-s": "workspace::Save",
       "cmd->": "assistant::QuoteSelection",
+      "cmd-<": "assistant::InsertIntoEditor",
       "shift-enter": "assistant::Split",
       "ctrl-r": "assistant::CycleMessageRole",
       "enter": "assistant::ConfirmCommand",

crates/assistant/src/assistant_panel.rs 🔗

@@ -9,9 +9,10 @@ use crate::{
     },
     terminal_inline_assistant::TerminalInlineAssistant,
     ApplyEdit, Assist, CompletionProvider, ConfirmCommand, ContextStore, CycleMessageRole,
-    InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, MessageId,
-    MessageMetadata, MessageStatus, ModelSelector, QuoteSelection, ResetKey, Role, SavedContext,
-    SavedContextMetadata, SavedMessage, Split, ToggleFocus, ToggleHistory, ToggleModelSelector,
+    InlineAssist, InlineAssistant, InsertIntoEditor, LanguageModelRequest,
+    LanguageModelRequestMessage, MessageId, MessageMetadata, MessageStatus, ModelSelector,
+    QuoteSelection, ResetKey, Role, SavedContext, SavedContextMetadata, SavedMessage, Split,
+    ToggleFocus, ToggleHistory, ToggleModelSelector,
 };
 use anyhow::{anyhow, Result};
 use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
@@ -86,7 +87,8 @@ pub fn init(cx: &mut AppContext) {
                     workspace.toggle_panel_focus::<AssistantPanel>(cx);
                 })
                 .register_action(AssistantPanel::inline_assist)
-                .register_action(ContextEditor::quote_selection);
+                .register_action(ContextEditor::quote_selection)
+                .register_action(ContextEditor::insert_selection);
         },
     )
     .detach();
@@ -2975,6 +2977,42 @@ impl ContextEditor {
         });
     }
 
+    fn insert_selection(
+        workspace: &mut Workspace,
+        _: &InsertIntoEditor,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
+            return;
+        };
+        let Some(context_editor_view) = panel.read(cx).active_context_editor().cloned() else {
+            return;
+        };
+        let Some(active_editor_view) = workspace
+            .active_item(cx)
+            .and_then(|item| item.act_as::<Editor>(cx))
+        else {
+            return;
+        };
+
+        let context_editor = context_editor_view.read(cx).editor.read(cx);
+        let anchor = context_editor.selections.newest_anchor();
+        let text = context_editor
+            .buffer()
+            .read(cx)
+            .read(cx)
+            .text_for_range(anchor.range())
+            .collect::<String>();
+
+        // If nothing is selected, don't delete the current selection; instead, be a no-op.
+        if !text.is_empty() {
+            active_editor_view.update(cx, |editor, cx| {
+                editor.insert(&text, cx);
+                editor.focus(cx);
+            })
+        }
+    }
+
     fn quote_selection(
         workspace: &mut Workspace,
         _: &QuoteSelection,