Require /file or /tab when using Suggest Edits (#19960)

Richard Feldman and Antonio created

Now if you try to do Suggest Edits without a file context, you see this
(and it doesn't run the query).

<img width="635" alt="Screenshot 2024-10-30 at 10 51 24 AM"
src="https://github.com/user-attachments/assets/a3997ba6-98a9-4bfa-81b6-1d8579c26fd7">


Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>

Change summary

assets/prompts/suggest_edits.hbs        |  0 
crates/assistant/src/assistant_panel.rs | 42 ++++++++++++++++++++++++++
crates/assistant/src/context.rs         | 21 +++++++++++-
crates/assistant/src/prompts.rs         |  2 
docs/src/assistant/prompting.md         |  4 +-
5 files changed, 62 insertions(+), 7 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1462,6 +1462,7 @@ type MessageHeader = MessageMetadata;
 
 #[derive(Clone)]
 enum AssistError {
+    FileRequired,
     PaymentRequired,
     MaxMonthlySpendReached,
     Message(SharedString),
@@ -1628,7 +1629,10 @@ impl ContextEditor {
 
         self.last_error = None;
 
-        if let Some(user_message) = self
+        if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) {
+            self.last_error = Some(AssistError::FileRequired);
+            cx.notify();
+        } else if let Some(user_message) = self
             .context
             .update(cx, |context, cx| context.assist(request_type, cx))
         {
@@ -3740,6 +3744,7 @@ impl ContextEditor {
                 .elevation_2(cx)
                 .occlude()
                 .child(match last_error {
+                    AssistError::FileRequired => self.render_file_required_error(cx),
                     AssistError::PaymentRequired => self.render_payment_required_error(cx),
                     AssistError::MaxMonthlySpendReached => {
                         self.render_max_monthly_spend_reached_error(cx)
@@ -3752,6 +3757,41 @@ impl ContextEditor {
         )
     }
 
+    fn render_file_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
+        v_flex()
+            .gap_0p5()
+            .child(
+                h_flex()
+                    .gap_1p5()
+                    .items_center()
+                    .child(Icon::new(IconName::Warning).color(Color::Warning))
+                    .child(
+                        Label::new("Suggest Edits needs a file to edit").weight(FontWeight::MEDIUM),
+                    ),
+            )
+            .child(
+                div()
+                    .id("error-message")
+                    .max_h_24()
+                    .overflow_y_scroll()
+                    .child(Label::new(
+                        "To include files, type /file or /tab in your prompt.",
+                    )),
+            )
+            .child(
+                h_flex()
+                    .justify_end()
+                    .mt_1()
+                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+                        |this, _, cx| {
+                            this.last_error = None;
+                            cx.notify();
+                        },
+                    ))),
+            )
+            .into_any()
+    }
+
     fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
         const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
 

crates/assistant/src/context.rs 🔗

@@ -2,8 +2,9 @@
 mod context_tests;
 
 use crate::{
-    prompts::PromptBuilder, slash_command::SlashCommandLine, AssistantEdit, AssistantPatch,
-    AssistantPatchStatus, MessageId, MessageStatus,
+    prompts::PromptBuilder,
+    slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
+    AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
 };
 use anyhow::{anyhow, Context as _, Result};
 use assistant_slash_command::{
@@ -66,7 +67,7 @@ impl ContextId {
     }
 }
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum RequestType {
     /// Request a normal chat response from the model.
     Chat,
@@ -989,6 +990,20 @@ impl Context {
         &self.slash_command_output_sections
     }
 
+    pub fn contains_files(&self, cx: &AppContext) -> bool {
+        let buffer = self.buffer.read(cx);
+        self.slash_command_output_sections.iter().any(|section| {
+            section.is_valid(buffer)
+                && section
+                    .metadata
+                    .as_ref()
+                    .and_then(|metadata| {
+                        serde_json::from_value::<FileCommandMetadata>(metadata.clone()).ok()
+                    })
+                    .is_some()
+        })
+    }
+
     pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
         self.pending_tool_uses_by_id.values().collect()
     }

crates/assistant/src/prompts.rs 🔗

@@ -311,7 +311,7 @@ impl PromptBuilder {
     }
 
     pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
-        self.handlebars.lock().render("edit_workflow", &())
+        self.handlebars.lock().render("suggest_edits", &())
     }
 
     pub fn generate_project_slash_command_prompt(

docs/src/assistant/prompting.md 🔗

@@ -137,7 +137,7 @@ Zed has the following internal prompt templates:
 
 - `content_prompt.hbs`: Used for generating content in the editor.
 - `terminal_assistant_prompt.hbs`: Used for the terminal assistant feature.
-- `edit_workflow.hbs`: Used for generating the edit workflow prompt.
+- `suggest_edits.hbs`: Used for generating the model instructions for the XML Suggest Edits should return.
 - `step_resolution.hbs`: Used for generating the step resolution prompt.
 
 At this point it is unknown if we will expand templates further to be user-creatable.
@@ -215,7 +215,7 @@ The following templates can be overridden:
    given system information and latest terminal output if relevant.
    ```
 
-3. `edit_workflow.hbs`: Used for generating the edit workflow prompt.
+3. `suggest_edits.hbs`: Used for generating the model instructions for the XML Suggest Edits should return.
 
 4. `step_resolution.hbs`: Used for generating the step resolution prompt.