agent: Add several UX improvements (#29828)

Danilo Leal , Bennet Bo Fenner , Nathan Sobo , Cole Miller , Nathan Sobo , and Cole Miller created

Still a work in progress.

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com>
Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com>

Change summary

crates/agent/src/active_thread.rs                      | 176 +++++------
crates/agent/src/context_picker.rs                     |  13 
crates/agent/src/context_picker/completion_provider.rs |  43 ++
crates/agent/src/inline_prompt_editor.rs               |  18 +
crates/agent/src/message_editor.rs                     |   5 
crates/agent/src/thread.rs                             |  23 +
crates/agent/src/ui.rs                                 |   2 
crates/agent/src/ui/max_mode_tooltip.rs                |  33 ++
crates/assistant/src/terminal_inline_assistant.rs      |   7 
crates/assistant_tools/src/edit_file_tool.rs           |   3 
crates/project/src/project.rs                          |   7 
11 files changed, 220 insertions(+), 110 deletions(-)

Detailed changes

crates/agent/src/active_thread.rs 🔗

@@ -2,10 +2,9 @@ use crate::context::{AgentContextHandle, RULES_ICON};
 use crate::context_picker::{ContextPicker, MentionLink};
 use crate::context_store::ContextStore;
 use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
-use crate::message_editor::insert_message_creases;
 use crate::thread::{
-    LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, QueueState, Thread,
-    ThreadError, ThreadEvent, ThreadFeedback,
+    LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent,
+    ThreadFeedback,
 };
 use crate::thread_store::{RulesLoadingError, ThreadStore};
 use crate::tool_use::{PendingToolUseStatus, ToolUse};
@@ -1243,7 +1242,6 @@ impl ActiveThread {
         &mut self,
         message_id: MessageId,
         message_segments: &[MessageSegment],
-        message_creases: &[MessageCrease],
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -1262,7 +1260,6 @@ impl ActiveThread {
         );
         editor.update(cx, |editor, cx| {
             editor.set_text(message_text.clone(), window, cx);
-            insert_message_creases(editor, message_creases, &self.context_store, window, cx);
             editor.focus_handle(cx).focus(window);
             editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
         });
@@ -1710,7 +1707,6 @@ impl ActiveThread {
         let Some(message) = self.thread.read(cx).message(message_id) else {
             return Empty.into_any();
         };
-        let message_creases = message.creases.clone();
 
         let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
             return Empty.into_any();
@@ -1729,33 +1725,13 @@ impl ActiveThread {
         let tool_uses = thread.tool_uses_for_message(message_id, cx);
         let has_tool_uses = !tool_uses.is_empty();
         let is_generating = thread.is_generating();
+        let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
 
         let is_first_message = ix == 0;
         let is_last_message = ix == self.messages.len() - 1;
 
-        let show_feedback = thread.is_turn_end(ix);
-
-        let generating_label = is_last_message
-            .then(|| match (thread.queue_state(), is_generating) {
-                (Some(QueueState::Sending), _) => Some(
-                    AnimatedLabel::new("Sending")
-                        .size(LabelSize::Small)
-                        .into_any_element(),
-                ),
-                (Some(QueueState::Queued { position }), _) => Some(
-                    Label::new(format!("Queue position: {position}"))
-                        .size(LabelSize::Small)
-                        .color(Color::Muted)
-                        .into_any_element(),
-                ),
-                (_, true) => Some(
-                    AnimatedLabel::new("Generating")
-                        .size(LabelSize::Small)
-                        .into_any_element(),
-                ),
-                _ => None,
-            })
-            .flatten();
+        let loading_dots = (is_generating_stale && is_last_message)
+            .then(|| AnimatedLabel::new("").size(LabelSize::Small));
 
         let editing_message_state = self
             .editing_message
@@ -1778,6 +1754,8 @@ impl ActiveThread {
         // For all items that should be aligned with the LLM's response.
         const RESPONSE_PADDING_X: Pixels = px(19.);
 
+        let show_feedback = thread.is_turn_end(ix);
+
         let feedback_container = h_flex()
             .group("feedback_container")
             .mt_1()
@@ -1925,7 +1903,6 @@ impl ActiveThread {
                                                 open_context(&context, workspace, window, cx);
                                                 cx.notify();
                                             }
-                                            cx.stop_propagation();
                                         }
                                     })),
                                 )
@@ -2011,13 +1988,15 @@ impl ActiveThread {
                                     )
                                 }),
                         )
+                        .when(editing_message_state.is_none(), |this| {
+                            this.tooltip(Tooltip::text("Click To Edit"))
+                        })
                         .on_click(cx.listener({
                             let message_segments = message.segments.clone();
                             move |this, _, window, cx| {
                                 this.start_editing_message(
                                     message_id,
                                     &message_segments,
-                                    &message_creases,
                                     window,
                                     cx,
                                 );
@@ -2053,80 +2032,84 @@ impl ActiveThread {
 
         v_flex()
             .w_full()
-            .when_some(checkpoint, |parent, checkpoint| {
-                let mut is_pending = false;
-                let mut error = None;
-                if let Some(last_restore_checkpoint) =
-                    self.thread.read(cx).last_restore_checkpoint()
-                {
-                    if last_restore_checkpoint.message_id() == message_id {
-                        match last_restore_checkpoint {
-                            LastRestoreCheckpoint::Pending { .. } => is_pending = true,
-                            LastRestoreCheckpoint::Error { error: err, .. } => {
-                                error = Some(err.clone());
+            .map(|parent| {
+                if let Some(checkpoint) = checkpoint.filter(|_| is_generating) {
+                    let mut is_pending = false;
+                    let mut error = None;
+                    if let Some(last_restore_checkpoint) =
+                        self.thread.read(cx).last_restore_checkpoint()
+                    {
+                        if last_restore_checkpoint.message_id() == message_id {
+                            match last_restore_checkpoint {
+                                LastRestoreCheckpoint::Pending { .. } => is_pending = true,
+                                LastRestoreCheckpoint::Error { error: err, .. } => {
+                                    error = Some(err.clone());
+                                }
                             }
                         }
                     }
-                }
 
-                let restore_checkpoint_button =
-                    Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
-                        .icon(if error.is_some() {
-                            IconName::XCircle
-                        } else {
-                            IconName::Undo
-                        })
-                        .icon_size(IconSize::XSmall)
-                        .icon_position(IconPosition::Start)
-                        .icon_color(if error.is_some() {
-                            Some(Color::Error)
-                        } else {
-                            None
-                        })
-                        .label_size(LabelSize::XSmall)
-                        .disabled(is_pending)
-                        .on_click(cx.listener(move |this, _, _window, cx| {
-                            this.thread.update(cx, |thread, cx| {
-                                thread
-                                    .restore_checkpoint(checkpoint.clone(), cx)
-                                    .detach_and_log_err(cx);
-                            });
-                        }));
-
-                let restore_checkpoint_button = if is_pending {
-                    restore_checkpoint_button
-                        .with_animation(
-                            ("pulsating-restore-checkpoint-button", ix),
-                            Animation::new(Duration::from_secs(2))
-                                .repeat()
-                                .with_easing(pulsating_between(0.6, 1.)),
-                            |label, delta| label.alpha(delta),
-                        )
-                        .into_any_element()
-                } else if let Some(error) = error {
-                    restore_checkpoint_button
-                        .tooltip(Tooltip::text(error.to_string()))
-                        .into_any_element()
-                } else {
-                    restore_checkpoint_button.into_any_element()
-                };
+                    let restore_checkpoint_button =
+                        Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
+                            .icon(if error.is_some() {
+                                IconName::XCircle
+                            } else {
+                                IconName::Undo
+                            })
+                            .icon_size(IconSize::XSmall)
+                            .icon_position(IconPosition::Start)
+                            .icon_color(if error.is_some() {
+                                Some(Color::Error)
+                            } else {
+                                None
+                            })
+                            .label_size(LabelSize::XSmall)
+                            .disabled(is_pending)
+                            .on_click(cx.listener(move |this, _, _window, cx| {
+                                this.thread.update(cx, |thread, cx| {
+                                    thread
+                                        .restore_checkpoint(checkpoint.clone(), cx)
+                                        .detach_and_log_err(cx);
+                                });
+                            }));
+
+                    let restore_checkpoint_button = if is_pending {
+                        restore_checkpoint_button
+                            .with_animation(
+                                ("pulsating-restore-checkpoint-button", ix),
+                                Animation::new(Duration::from_secs(2))
+                                    .repeat()
+                                    .with_easing(pulsating_between(0.6, 1.)),
+                                |label, delta| label.alpha(delta),
+                            )
+                            .into_any_element()
+                    } else if let Some(error) = error {
+                        restore_checkpoint_button
+                            .tooltip(Tooltip::text(error.to_string()))
+                            .into_any_element()
+                    } else {
+                        restore_checkpoint_button.into_any_element()
+                    };
 
-                parent.child(
-                    h_flex()
-                        .pt_2p5()
-                        .px_2p5()
-                        .w_full()
-                        .gap_1()
-                        .child(ui::Divider::horizontal())
-                        .child(restore_checkpoint_button)
-                        .child(ui::Divider::horizontal()),
-                )
+                    parent.child(
+                        h_flex()
+                            .pt_2p5()
+                            .px_2p5()
+                            .w_full()
+                            .gap_1()
+                            .child(ui::Divider::horizontal())
+                            .child(restore_checkpoint_button)
+                            .child(ui::Divider::horizontal()),
+                    )
+                } else {
+                    parent
+                }
             })
             .when(is_first_message, |parent| {
                 parent.child(self.render_rules_item(cx))
             })
             .child(styled_message)
-            .when_some(generating_label, |this, generating_label| {
+            .when(is_generating && is_last_message, |this| {
                 this.child(
                     h_flex()
                         .h_8()
@@ -2134,7 +2117,7 @@ impl ActiveThread {
                         .mb_4()
                         .ml_4()
                         .py_1p5()
-                        .child(generating_label),
+                        .when_some(loading_dots, |this, loading_dots| this.child(loading_dots)),
                 )
             })
             .when(show_feedback, move |parent| {
@@ -2385,7 +2368,6 @@ impl ActiveThread {
                                     let workspace = self.workspace.clone();
                                     move |text, window, cx| {
                                         open_markdown_link(text, workspace.clone(), window, cx);
-                                        cx.stop_propagation();
                                     }
                                 }))
                                 .into_any_element()

crates/agent/src/context_picker.rs 🔗

@@ -482,7 +482,13 @@ impl ContextPicker {
             return vec![];
         };
 
-        recent_context_picker_entries(context_store, self.thread_store.clone(), workspace, cx)
+        recent_context_picker_entries(
+            context_store,
+            self.thread_store.clone(),
+            workspace,
+            None,
+            cx,
+        )
     }
 
     fn notify_current_picker(&mut self, cx: &mut Context<Self>) {
@@ -578,11 +584,12 @@ fn recent_context_picker_entries(
     context_store: Entity<ContextStore>,
     thread_store: Option<WeakEntity<ThreadStore>>,
     workspace: Entity<Workspace>,
+    exclude_path: Option<ProjectPath>,
     cx: &App,
 ) -> Vec<RecentEntry> {
     let mut recent = Vec::with_capacity(6);
-
-    let current_files = context_store.read(cx).file_paths(cx);
+    let mut current_files = context_store.read(cx).file_paths(cx);
+    current_files.extend(exclude_path);
     let workspace = workspace.read(cx);
     let project = workspace.project().read(cx);
 

crates/agent/src/context_picker/completion_provider.rs 🔗

@@ -237,6 +237,7 @@ pub struct ContextPickerCompletionProvider {
     context_store: WeakEntity<ContextStore>,
     thread_store: Option<WeakEntity<ThreadStore>>,
     editor: WeakEntity<Editor>,
+    excluded_buffer: Option<WeakEntity<Buffer>>,
 }
 
 impl ContextPickerCompletionProvider {
@@ -245,12 +246,14 @@ impl ContextPickerCompletionProvider {
         context_store: WeakEntity<ContextStore>,
         thread_store: Option<WeakEntity<ThreadStore>>,
         editor: WeakEntity<Editor>,
+        exclude_buffer: Option<WeakEntity<Buffer>>,
     ) -> Self {
         Self {
             workspace,
             context_store,
             thread_store,
             editor,
+            excluded_buffer: exclude_buffer,
         }
     }
 
@@ -736,10 +739,18 @@ impl CompletionProvider for ContextPickerCompletionProvider {
         let MentionCompletion { mode, argument, .. } = state;
         let query = argument.unwrap_or_else(|| "".to_string());
 
+        let excluded_path = self
+            .excluded_buffer
+            .as_ref()
+            .and_then(WeakEntity::upgrade)
+            .and_then(|b| b.read(cx).file())
+            .map(|file| ProjectPath::from_file(file.as_ref(), cx));
+
         let recent_entries = recent_context_picker_entries(
             context_store.clone(),
             thread_store.clone(),
             workspace.clone(),
+            excluded_path.clone(),
             cx,
         );
 
@@ -772,11 +783,17 @@ impl CompletionProvider for ContextPickerCompletionProvider {
                     .into_iter()
                     .filter_map(|mat| match mat {
                         Match::File(FileMatch { mat, is_recent }) => {
+                            let project_path = ProjectPath {
+                                worktree_id: WorktreeId::from_usize(mat.worktree_id),
+                                path: mat.path.clone(),
+                            };
+
+                            if excluded_path.as_ref() == Some(&project_path) {
+                                return None;
+                            }
+
                             Some(Self::completion_for_path(
-                                ProjectPath {
-                                    worktree_id: WorktreeId::from_usize(mat.worktree_id),
-                                    path: mat.path.clone(),
-                                },
+                                project_path,
                                 &mat.path_prefix,
                                 is_recent,
                                 mat.is_dir,
@@ -1138,6 +1155,7 @@ mod tests {
                         "five.txt": "",
                         "six.txt": "",
                         "seven.txt": "",
+                        "eight.txt": "",
                     }
                 }),
             )
@@ -1164,9 +1182,12 @@ mod tests {
             separator!("b/five.txt"),
             separator!("b/six.txt"),
             separator!("b/seven.txt"),
+            separator!("b/eight.txt"),
         ];
+
+        let mut opened_editors = Vec::new();
         for path in paths {
-            workspace
+            let buffer = workspace
                 .update_in(&mut cx, |workspace, window, cx| {
                     workspace.open_path(
                         ProjectPath {
@@ -1181,6 +1202,7 @@ mod tests {
                 })
                 .await
                 .unwrap();
+            opened_editors.push(buffer);
         }
 
         let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -1210,12 +1232,23 @@ mod tests {
 
         let editor_entity = editor.downgrade();
         editor.update_in(&mut cx, |editor, window, cx| {
+            let last_opened_buffer = opened_editors.last().and_then(|editor| {
+                editor
+                    .downcast::<Editor>()?
+                    .read(cx)
+                    .buffer()
+                    .read(cx)
+                    .as_singleton()
+                    .as_ref()
+                    .map(Entity::downgrade)
+            });
             window.focus(&editor.focus_handle(cx));
             editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
                 workspace.downgrade(),
                 context_store.downgrade(),
                 None,
                 editor_entity,
+                last_opened_buffer,
             ))));
         });
 

crates/agent/src/inline_prompt_editor.rs 🔗

@@ -12,7 +12,8 @@ use crate::{RemoveAllContext, ToggleContextPicker};
 use client::ErrorExt;
 use collections::VecDeque;
 use editor::{
-    Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, GutterDimensions, MultiBuffer,
+    ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle,
+    GutterDimensions, MultiBuffer,
     actions::{MoveDown, MoveUp},
 };
 use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
@@ -849,6 +850,7 @@ impl PromptEditor<BufferCodegen> {
         cx: &mut Context<PromptEditor<BufferCodegen>>,
     ) -> PromptEditor<BufferCodegen> {
         let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
+        let codegen_buffer = codegen.read(cx).buffer(cx).read(cx).as_singleton();
         let mode = PromptEditorMode::Buffer {
             id,
             codegen,
@@ -872,8 +874,15 @@ impl PromptEditor<BufferCodegen> {
             editor.set_show_cursor_when_unfocused(true, cx);
             editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
             editor.register_addon(ContextCreasesAddon::new());
+            editor.set_context_menu_options(ContextMenuOptions {
+                min_entries_visible: 12,
+                max_entries_visible: 12,
+                placement: None,
+            });
+
             editor
         });
+
         let prompt_editor_entity = prompt_editor.downgrade();
         prompt_editor.update(cx, |editor, _| {
             editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
@@ -881,6 +890,7 @@ impl PromptEditor<BufferCodegen> {
                 context_store.downgrade(),
                 thread_store.clone(),
                 prompt_editor_entity,
+                codegen_buffer.as_ref().map(Entity::downgrade),
             ))));
         });
 
@@ -1035,6 +1045,11 @@ impl PromptEditor<TerminalCodegen> {
             );
             editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
             editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
+            editor.set_context_menu_options(ContextMenuOptions {
+                min_entries_visible: 12,
+                max_entries_visible: 12,
+                placement: None,
+            });
             editor
         });
 
@@ -1045,6 +1060,7 @@ impl PromptEditor<TerminalCodegen> {
                 context_store.downgrade(),
                 thread_store.clone(),
                 prompt_editor_entity,
+                None,
             ))));
         });
 

crates/agent/src/message_editor.rs 🔗

@@ -4,7 +4,7 @@ use std::sync::Arc;
 use crate::assistant_model_selector::{AssistantModelSelector, ModelType};
 use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
 use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
-use crate::ui::{AgentPreview, AnimatedLabel};
+use crate::ui::{AgentPreview, AnimatedLabel, MaxModeTooltip};
 use buffer_diff::BufferDiff;
 use collections::{HashMap, HashSet};
 use editor::actions::{MoveUp, Paste};
@@ -116,6 +116,7 @@ pub(crate) fn create_editor(
             context_store,
             Some(thread_store),
             editor_entity,
+            None,
         ))));
     });
     editor
@@ -451,7 +452,7 @@ impl MessageEditor {
                         });
                     });
                 }))
-                .tooltip(Tooltip::text("Toggle Max Mode"))
+                .tooltip(|_, cx| cx.new(MaxModeTooltip::new).into())
                 .into_any_element(),
         )
     }

crates/agent/src/thread.rs 🔗

@@ -358,6 +358,7 @@ pub struct Thread {
     feedback: Option<ThreadFeedback>,
     message_feedback: HashMap<MessageId, ThreadFeedback>,
     last_auto_capture_at: Option<Instant>,
+    last_received_chunk_at: Option<Instant>,
     request_callback: Option<
         Box<dyn FnMut(&LanguageModelRequest, &[Result<LanguageModelCompletionEvent, String>])>,
     >,
@@ -419,6 +420,7 @@ impl Thread {
             feedback: None,
             message_feedback: HashMap::default(),
             last_auto_capture_at: None,
+            last_received_chunk_at: None,
             request_callback: None,
             remaining_turns: u32::MAX,
             configured_model,
@@ -525,6 +527,7 @@ impl Thread {
             feedback: None,
             message_feedback: HashMap::default(),
             last_auto_capture_at: None,
+            last_received_chunk_at: None,
             request_callback: None,
             remaining_turns: u32::MAX,
             configured_model,
@@ -632,6 +635,19 @@ impl Thread {
         !self.pending_completions.is_empty() || !self.all_tools_finished()
     }
 
+    /// Indicates whether streaming of language model events is stale.
+    /// When `is_generating()` is false, this method returns `None`.
+    pub fn is_generation_stale(&self) -> Option<bool> {
+        const STALE_THRESHOLD: u128 = 250;
+
+        self.last_received_chunk_at
+            .map(|instant| instant.elapsed().as_millis() > STALE_THRESHOLD)
+    }
+
+    fn received_chunk(&mut self) {
+        self.last_received_chunk_at = Some(Instant::now());
+    }
+
     pub fn queue_state(&self) -> Option<QueueState> {
         self.pending_completions
             .first()
@@ -1328,6 +1344,8 @@ impl Thread {
             prompt_id: prompt_id.clone(),
         };
 
+        self.last_received_chunk_at = Some(Instant::now());
+
         let task = cx.spawn(async move |thread, cx| {
             let stream_completion_future = model.stream_completion_with_usage(request, &cx);
             let initial_token_usage =
@@ -1398,6 +1416,8 @@ impl Thread {
                                 current_token_usage = token_usage;
                             }
                             LanguageModelCompletionEvent::Text(chunk) => {
+                                thread.received_chunk();
+
                                 cx.emit(ThreadEvent::ReceivedTextChunk);
                                 if let Some(last_message) = thread.messages.last_mut() {
                                     if last_message.role == Role::Assistant
@@ -1426,6 +1446,8 @@ impl Thread {
                                 text: chunk,
                                 signature,
                             } => {
+                                thread.received_chunk();
+
                                 if let Some(last_message) = thread.messages.last_mut() {
                                     if last_message.role == Role::Assistant
                                         && !thread.tool_use.has_tool_results(last_message.id)
@@ -1512,6 +1534,7 @@ impl Thread {
                 }
 
                 thread.update(cx, |thread, cx| {
+                    thread.last_received_chunk_at = None;
                     thread
                         .pending_completions
                         .retain(|completion| completion.id != pending_completion_id);

crates/agent/src/ui.rs 🔗

@@ -2,6 +2,7 @@ mod agent_notification;
 pub mod agent_preview;
 mod animated_label;
 mod context_pill;
+mod max_mode_tooltip;
 mod upsell;
 mod usage_banner;
 
@@ -9,4 +10,5 @@ pub use agent_notification::*;
 pub use agent_preview::*;
 pub use animated_label::*;
 pub use context_pill::*;
+pub use max_mode_tooltip::*;
 pub use usage_banner::*;

crates/agent/src/ui/max_mode_tooltip.rs 🔗

@@ -0,0 +1,33 @@
+use gpui::{Context, IntoElement, Render, Window};
+use ui::{prelude::*, tooltip_container};
+
+pub struct MaxModeTooltip;
+
+impl MaxModeTooltip {
+    pub fn new(_cx: &mut Context<Self>) -> Self {
+        Self
+    }
+}
+
+impl Render for MaxModeTooltip {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        tooltip_container(_window, cx, |this, _, _| {
+            this.gap_1()
+                .child(
+                    h_flex()
+                        .gap_1p5()
+                        .child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
+                        .child(Label::new("Zed's Max Mode"))
+                )
+                .child(
+                    div()
+                        .max_w_72()
+                        .child(
+                            Label::new("This mode enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
+                                .size(LabelSize::Small)
+                                .color(Color::Muted)
+                        )
+                )
+        })
+    }
+}

crates/assistant/src/terminal_inline_assistant.rs 🔗

@@ -5,7 +5,7 @@ use assistant_settings::AssistantSettings;
 use client::telemetry::Telemetry;
 use collections::{HashMap, VecDeque};
 use editor::{
-    Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
+    ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
     actions::{MoveDown, MoveUp, SelectAll},
 };
 use fs::Fs;
@@ -730,6 +730,11 @@ impl PromptEditor {
             );
             editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
             editor.set_placeholder_text(Self::placeholder_text(window, cx), cx);
+            editor.set_context_menu_options(ContextMenuOptions {
+                min_entries_visible: 12,
+                max_entries_visible: 12,
+                placement: None,
+            });
             editor
         });
 

crates/assistant_tools/src/edit_file_tool.rs 🔗

@@ -304,6 +304,7 @@ impl EditFileToolCard {
             editor.set_soft_wrap_mode(SoftWrap::None, cx);
             editor.scroll_manager.set_forbid_vertical_scroll(true);
             editor.set_show_scrollbars(false, cx);
+            editor.set_show_indent_guides(false, cx);
             editor.set_read_only(true);
             editor.set_show_breakpoints(false, cx);
             editor.set_show_code_actions(false, cx);
@@ -640,7 +641,7 @@ impl ToolCard for EditFileToolCard {
                             .border_t_1()
                             .border_color(border_color)
                             .bg(cx.theme().colors().editor_background)
-                            .child(div().pl_1().child(editor))
+                            .child(editor)
                             .when(
                                 !self.full_height_expanded && is_collapsible,
                                 |editor_container| editor_container.child(gradient_overlay),

crates/project/src/project.rs 🔗

@@ -318,6 +318,13 @@ pub struct ProjectPath {
 }
 
 impl ProjectPath {
+    pub fn from_file(value: &dyn language::File, cx: &App) -> Self {
+        ProjectPath {
+            worktree_id: value.worktree_id(cx),
+            path: value.path().clone(),
+        }
+    }
+
     pub fn from_proto(p: proto::ProjectPath) -> Self {
         Self {
             worktree_id: WorktreeId::from_proto(p.worktree_id),