use crate::DEFAULT_THREAD_TITLE;use crate::SendImmediately;use crate::{ ChatWithFollow, completion_provider::{ AgentContextSelection, AvailableCommand, AvailableSkill, PromptCompletionProvider, PromptCompletionProviderDelegate, PromptContextAction, PromptContextType, PromptLocalCommand, SlashCommandCompletion, }, mention_set::{Mention, MentionImage, MentionSet, insert_crease_for_mention},};use acp_thread::MentionUri;use agent::ThreadStore;use agent_client_protocol::schema::v1 as acp;use anyhow::{Result, anyhow};use base64::Engine as _;use editor::{ Addon, AnchorRangeExt, ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, Inlay, MultiBuffer, MultiBufferOffset, MultiBufferSnapshot, ToOffset, actions::{Copy, Cut, Paste}, code_context_menus::CodeContextMenu, display_map::{CreaseId, CreaseSnapshot}, scroll::Autoscroll,};use futures::{FutureExt as _, future::join_all};use gpui::{ AppContext, ClipboardEntry, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, Image, ImageFormat, KeyContext, SharedString, Subscription, Task, TaskExt, TextStyle, WeakEntity,};use language::{Buffer, language_settings::InlayHintKind};use parking_lot::RwLock;use project::AgentId;use project::{ CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectPath, Worktree,};use rope::Point;use settings::Settings;use std::{cmp::min, fmt::Write, ops::Range, rc::Rc, sync::Arc};use text::LineEnding;use theme_settings::ThemeSettings;use ui::{ContextMenu, prelude::*};use util::paths::PathStyle;use util::{ResultExt, debug_panic};use workspace::{CollaboratorId, Workspace};use zed_actions::agent::{Chat, PasteRaw}; #[derive(Default)]pub struct SessionCapabilities { prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, available_skills: Vec<AvailableSkill>,} impl SessionCapabilities { pub fn new( prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, available_skills: Vec<AvailableSkill>, ) -> Self { Self { prompt_capabilities, available_commands, available_skills, } } pub fn from_acp_commands( prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, ) -> Self { Self::new(prompt_capabilities, available_commands, Vec::new()) } pub fn supports_images(&self) -> bool { self.prompt_capabilities.image } pub fn supports_embedded_context(&self) -> bool { self.prompt_capabilities.embedded_context } pub fn available_commands(&self) -> &[acp::AvailableCommand] { &self.available_commands } pub fn available_skills(&self) -> &[AvailableSkill] { &self.available_skills } pub fn has_slash_completions(&self) -> bool { !self.available_commands.is_empty() || !self.available_skills.is_empty() } fn supported_modes(&self, has_thread_store: bool) -> Vec<PromptContextType> { let mut supported = vec![PromptContextType::File, PromptContextType::Symbol]; if self.prompt_capabilities.embedded_context { if has_thread_store { supported.push(PromptContextType::Thread); } supported.extend(&[ PromptContextType::Diagnostics, PromptContextType::Fetch, PromptContextType::Skill, PromptContextType::BranchDiff, ]); } supported } pub fn completion_commands(&self) -> Vec<AvailableCommand> { self.available_commands .iter() .map(|command| AvailableCommand { name: command.name.clone().into(), description: command.description.clone().into(), requires_argument: command.input.is_some(), source: None, category: acp_thread::command_category_from_meta(&command.meta), }) .collect() } pub fn completion_skills(&self) -> Vec<AvailableSkill> { self.available_skills.clone() } pub fn set_prompt_capabilities(&mut self, prompt_capabilities: acp::PromptCapabilities) { self.prompt_capabilities = prompt_capabilities; } pub fn set_available_commands(&mut self, available_commands: Vec<acp::AvailableCommand>) { self.available_commands = available_commands; } pub fn set_available_skills(&mut self, available_skills: Vec<AvailableSkill>) { self.available_skills = available_skills; }} pub type SharedSessionCapabilities = Arc<RwLock<SessionCapabilities>>; pub type SharedLocalCommands = Arc<RwLock<Vec<PromptLocalCommand>>>; struct MessageEditorCompletionDelegate { session_capabilities: SharedSessionCapabilities, has_thread_store: bool, message_editor: WeakEntity<MessageEditor>, local_commands: SharedLocalCommands,} impl PromptCompletionProviderDelegate for MessageEditorCompletionDelegate { fn supports_images(&self, _cx: &App) -> bool { self.session_capabilities.read().supports_images() } fn supported_modes(&self, _cx: &App) -> Vec<PromptContextType> { self.session_capabilities .read() .supported_modes(self.has_thread_store) } fn available_commands(&self, _cx: &App) -> Vec<AvailableCommand> { self.session_capabilities.read().completion_commands() } fn available_skills(&self, _cx: &App) -> Vec<AvailableSkill> { self.session_capabilities.read().completion_skills() } fn available_local_commands(&self, _cx: &App) -> Vec<PromptLocalCommand> { self.local_commands.read().clone() } fn run_local_command(&self, command: PromptLocalCommand, cx: &mut App) { self.message_editor .update(cx, |_this, cx| { cx.emit(MessageEditorEvent::LocalCommandInvoked(command)); }) .ok(); } fn slash_autocomplete_invoked(&self, cx: &mut App) { // This may be called synchronously from inside a `MessageEditor` // update (e.g. when pasting a slash command triggers completions), // so we defer the emit to avoid a reentrant update panic. let Some(editor) = self.message_editor.upgrade() else { return; }; cx.defer(move |cx| { editor.update(cx, |_editor, cx| { cx.emit(MessageEditorEvent::SlashAutocompleteOpened); }); }); } fn confirm_command(&self, cx: &mut App) { let _ = self.message_editor.update(cx, |this, cx| this.send(cx)); }} pub struct MessageEditor { mention_set: Entity<MentionSet>, editor: Entity<Editor>, workspace: WeakEntity<Workspace>, session_capabilities: SharedSessionCapabilities, local_commands: SharedLocalCommands, agent_id: AgentId, thread_store: Option<Entity<ThreadStore>>, _subscriptions: Vec<Subscription>, _parse_slash_command_task: Task<()>,} #[derive(Clone, Debug)]pub enum InputAttempt { Text(Arc<str>), Paste(ClipboardItem),} #[derive(Clone, Debug)]pub enum MessageEditorEvent { Send, SendImmediately, Cancel, Focus, LostFocus, Edited, /// Emitted when the user opens slash-command autocomplete in this /// editor. Used by `ThreadView` to fire the global-skills scan /// trigger; see `NativeAgent::ensure_skills_scan_started`. SlashAutocompleteOpened, /// Emitted when the user confirms a local slash command (scrolling, /// exporting, feedback) in this editor's completion popup. `ThreadView` /// handles it by running the corresponding action; see /// `handle_message_editor_event`. LocalCommandInvoked(PromptLocalCommand), InputAttempted { attempt: InputAttempt, cursor_offset: usize, },} impl EventEmitter<MessageEditorEvent> for MessageEditor {} const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0); fn insert_mention_for_project_path( project_path: &ProjectPath, editor: &Entity<Editor>, mention_set: &Entity<MentionSet>, project: &Entity<Project>, workspace: &Entity<Workspace>, supports_images: bool, window: &mut Window, cx: &mut App,) -> Option<Task<()>> { let (file_name, mention_uri) = { let project = project.read(cx); let path_style = project.path_style(cx); let entry = project.entry_for_path(project_path, cx)?; let worktree = project.worktree_for_id(project_path.worktree_id, cx)?; let abs_path = worktree.read(cx).absolutize(&project_path.path); let (file_name, _) = crate::completion_provider::extract_file_name_and_directory( &project_path.path, worktree.read(cx).root_name(), path_style, ); let mention_uri = if entry.is_dir() { MentionUri::Directory { abs_path } } else { MentionUri::File { abs_path } }; (file_name, mention_uri) }; let mention_text = mention_uri.as_link().to_string(); let content_len = mention_text.len(); let text_anchor = editor.update(cx, |editor, cx| { let buffer = editor.buffer().read(cx); let snapshot = buffer.snapshot(cx); let buffer_snapshot = snapshot.as_singleton()?; let text_anchor = snapshot .anchor_to_buffer_anchor(editor.selections.newest_anchor().start)? .0 .bias_left(&buffer_snapshot); editor.insert(&mention_text, window, cx); editor.insert(" ", window, cx); Some(text_anchor) })?; Some(mention_set.update(cx, |mention_set, cx| { mention_set.confirm_mention_completion( file_name, text_anchor, content_len, mention_uri, supports_images, editor.clone(), workspace, window, cx, ) }))} enum ResolvedPastedContextItem { Image(gpui::Image, gpui::SharedString), ProjectPath(ProjectPath),} async fn resolve_pasted_context_items( project: Entity<Project>, project_is_local: bool, supports_images: bool, entries: Vec<ClipboardEntry>, cx: &mut gpui::AsyncWindowContext,) -> (Vec<ResolvedPastedContextItem>, Vec<Entity<Worktree>>) { let mut items = Vec::new(); let mut added_worktrees = Vec::new(); let default_image_name: SharedString = "Image".into(); for entry in entries { match entry { ClipboardEntry::String(_) => {} ClipboardEntry::Image(image) => { if supports_images { items.push(ResolvedPastedContextItem::Image( image, default_image_name.clone(), )); } } ClipboardEntry::ExternalPaths(paths) => { for path in paths.paths().iter() { if let Some((image, name)) = cx .background_spawn({ let path = path.clone(); let default_image_name = default_image_name.clone(); async move { crate::mention_set::load_external_image_from_path( &path, &default_image_name, ) } }) .await { if supports_images { items.push(ResolvedPastedContextItem::Image(image, name)); } continue; } if !project_is_local { continue; } let path = path.clone(); let Ok(resolve_task) = cx.update({ let project = project.clone(); move |_, cx| Workspace::project_path_for_path(project, &path, false, cx) }) else { continue; }; if let Some((worktree, project_path)) = resolve_task.await.log_err() { added_worktrees.push(worktree); items.push(ResolvedPastedContextItem::ProjectPath(project_path)); } } } } } (items, added_worktrees)} fn insert_project_path_as_context( project_path: ProjectPath, editor: Entity<Editor>, mention_set: Entity<MentionSet>, workspace: WeakEntity<Workspace>, supports_images: bool, cx: &mut gpui::AsyncWindowContext,) -> Option<Task<()>> { let workspace = workspace.upgrade()?; cx.update(move |window, cx| { let project = workspace.read(cx).project().clone(); insert_mention_for_project_path( &project_path, &editor, &mention_set, &project, &workspace, supports_images, window, cx, ) }) .ok() .flatten()} async fn insert_resolved_pasted_context_items( items: Vec<ResolvedPastedContextItem>, added_worktrees: Vec<Entity<Worktree>>, editor: Entity<Editor>, mention_set: Entity<MentionSet>, workspace: WeakEntity<Workspace>, supports_images: bool, cx: &mut gpui::AsyncWindowContext,) { let mut path_mention_tasks = Vec::new(); for item in items { match item { ResolvedPastedContextItem::Image(image, name) => { crate::mention_set::insert_images_as_context( vec![(image, name)], editor.clone(), mention_set.clone(), workspace.clone(), cx, ) .await; } ResolvedPastedContextItem::ProjectPath(project_path) => { if let Some(task) = insert_project_path_as_context( project_path, editor.clone(), mention_set.clone(), workspace.clone(), supports_images, cx, ) { path_mention_tasks.push(task); } } } } join_all(path_mention_tasks).await; drop(added_worktrees);} impl MessageEditor { pub fn new( workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, session_capabilities: SharedSessionCapabilities, agent_id: AgentId, placeholder: &str, mode: EditorMode, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let language_registry = project .upgrade() .map(|project| project.read(cx).languages().clone()); let editor = cx.new(|cx| { let buffer = cx.new(|cx| { let buffer = Buffer::local("", cx); if let Some(language_registry) = language_registry.as_ref() { buffer.set_language_registry(language_registry.clone()); } buffer }); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let mut editor = Editor::new(mode, buffer, None, window, cx); editor.set_placeholder_text(placeholder, window, cx); editor.set_show_indent_guides(false, cx); editor.set_show_completions_on_input(Some(true)); editor.set_soft_wrap(); editor.disable_mouse_wheel_zoom(); editor.set_use_modal_editing(true); editor.set_context_menu_options(ContextMenuOptions { min_entries_visible: 12, max_entries_visible: 12, placement: None, }); editor.register_addon(MessageEditorAddon::new()); editor.set_custom_context_menu(|editor, _point, window, cx| { let has_selection = editor.has_non_empty_selection(&editor.display_snapshot(cx)); Some(ContextMenu::build(window, cx, |menu, _, _| { menu.action("Cut", Box::new(editor::actions::Cut)) .action_disabled_when( !has_selection, "Copy", Box::new(editor::actions::Copy), ) .action("Paste", Box::new(editor::actions::Paste))Lines 1–500 of 5772. Load more
use crate::DEFAULT_THREAD_TITLE; use crate::SendImmediately; use crate::{ ChatWithFollow, completion_provider::{ AgentContextSelection, AvailableCommand, AvailableSkill, PromptCompletionProvider, PromptCompletionProviderDelegate, PromptContextAction, PromptContextType, PromptLocalCommand, SlashCommandCompletion, }, mention_set::{Mention, MentionImage, MentionSet, insert_crease_for_mention}, }; use acp_thread::MentionUri; use agent::ThreadStore; use agent_client_protocol::schema::v1 as acp; use anyhow::{Result, anyhow}; use base64::Engine as _; use editor::{ Addon, AnchorRangeExt, ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, Inlay, MultiBuffer, MultiBufferOffset, MultiBufferSnapshot, ToOffset, actions::{Copy, Cut, Paste}, code_context_menus::CodeContextMenu, display_map::{CreaseId, CreaseSnapshot}, scroll::Autoscroll, }; use futures::{FutureExt as _, future::join_all}; use gpui::{ AppContext, ClipboardEntry, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, Image, ImageFormat, KeyContext, SharedString, Subscription, Task, TaskExt, TextStyle, WeakEntity, }; use language::{Buffer, language_settings::InlayHintKind}; use parking_lot::RwLock; use project::AgentId; use project::{ CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectPath, Worktree, }; use rope::Point; use settings::Settings; use std::{cmp::min, fmt::Write, ops::Range, rc::Rc, sync::Arc}; use text::LineEnding; use theme_settings::ThemeSettings; use ui::{ContextMenu, prelude::*}; use util::paths::PathStyle; use util::{ResultExt, debug_panic}; use workspace::{CollaboratorId, Workspace}; use zed_actions::agent::{Chat, PasteRaw}; #[derive(Default)] pub struct SessionCapabilities { prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, available_skills: Vec<AvailableSkill>, } impl SessionCapabilities { pub fn new( prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, available_skills: Vec<AvailableSkill>, ) -> Self { Self { prompt_capabilities, available_commands, available_skills, } } pub fn from_acp_commands( prompt_capabilities: acp::PromptCapabilities, available_commands: Vec<acp::AvailableCommand>, ) -> Self { Self::new(prompt_capabilities, available_commands, Vec::new()) } pub fn supports_images(&self) -> bool { self.prompt_capabilities.image } pub fn supports_embedded_context(&self) -> bool { self.prompt_capabilities.embedded_context } pub fn available_commands(&self) -> &[acp::AvailableCommand] { &self.available_commands } pub fn available_skills(&self) -> &[AvailableSkill] { &self.available_skills } pub fn has_slash_completions(&self) -> bool { !self.available_commands.is_empty() || !self.available_skills.is_empty() } fn supported_modes(&self, has_thread_store: bool) -> Vec<PromptContextType> { let mut supported = vec![PromptContextType::File, PromptContextType::Symbol]; if self.prompt_capabilities.embedded_context { if has_thread_store { supported.push(PromptContextType::Thread); } supported.extend(&[ PromptContextType::Diagnostics, PromptContextType::Fetch, PromptContextType::Skill, PromptContextType::BranchDiff, ]); } supported } pub fn completion_commands(&self) -> Vec<AvailableCommand> { self.available_commands .iter() .map(|command| AvailableCommand { name: command.name.clone().into(), description: command.description.clone().into(), requires_argument: command.input.is_some(), source: None, category: acp_thread::command_category_from_meta(&command.meta), }) .collect() } pub fn completion_skills(&self) -> Vec<AvailableSkill> { self.available_skills.clone() } pub fn set_prompt_capabilities(&mut self, prompt_capabilities: acp::PromptCapabilities) { self.prompt_capabilities = prompt_capabilities; } pub fn set_available_commands(&mut self, available_commands: Vec<acp::AvailableCommand>) { self.available_commands = available_commands; } pub fn set_available_skills(&mut self, available_skills: Vec<AvailableSkill>) { self.available_skills = available_skills; } } pub type SharedSessionCapabilities = Arc<RwLock<SessionCapabilities>>; pub type SharedLocalCommands = Arc<RwLock<Vec<PromptLocalCommand>>>; struct MessageEditorCompletionDelegate { session_capabilities: SharedSessionCapabilities, has_thread_store: bool, message_editor: WeakEntity<MessageEditor>, local_commands: SharedLocalCommands, } impl PromptCompletionProviderDelegate for MessageEditorCompletionDelegate { fn supports_images(&self, _cx: &App) -> bool { self.session_capabilities.read().supports_images() } fn supported_modes(&self, _cx: &App) -> Vec<PromptContextType> { self.session_capabilities .read() .supported_modes(self.has_thread_store) } fn available_commands(&self, _cx: &App) -> Vec<AvailableCommand> { self.session_capabilities.read().completion_commands() } fn available_skills(&self, _cx: &App) -> Vec<AvailableSkill> { self.session_capabilities.read().completion_skills() } fn available_local_commands(&self, _cx: &App) -> Vec<PromptLocalCommand> { self.local_commands.read().clone() } fn run_local_command(&self, command: PromptLocalCommand, cx: &mut App) { self.message_editor .update(cx, |_this, cx| { cx.emit(MessageEditorEvent::LocalCommandInvoked(command)); }) .ok(); } fn slash_autocomplete_invoked(&self, cx: &mut App) { // This may be called synchronously from inside a `MessageEditor` // update (e.g. when pasting a slash command triggers completions), // so we defer the emit to avoid a reentrant update panic. let Some(editor) = self.message_editor.upgrade() else { return; }; cx.defer(move |cx| { editor.update(cx, |_editor, cx| { cx.emit(MessageEditorEvent::SlashAutocompleteOpened); }); }); } fn confirm_command(&self, cx: &mut App) { let _ = self.message_editor.update(cx, |this, cx| this.send(cx)); } } pub struct MessageEditor { mention_set: Entity<MentionSet>, editor: Entity<Editor>, workspace: WeakEntity<Workspace>, session_capabilities: SharedSessionCapabilities, local_commands: SharedLocalCommands, agent_id: AgentId, thread_store: Option<Entity<ThreadStore>>, _subscriptions: Vec<Subscription>, _parse_slash_command_task: Task<()>, } #[derive(Clone, Debug)] pub enum InputAttempt { Text(Arc<str>), Paste(ClipboardItem), } #[derive(Clone, Debug)] pub enum MessageEditorEvent { Send, SendImmediately, Cancel, Focus, LostFocus, Edited, /// Emitted when the user opens slash-command autocomplete in this /// editor. Used by `ThreadView` to fire the global-skills scan /// trigger; see `NativeAgent::ensure_skills_scan_started`. SlashAutocompleteOpened, /// Emitted when the user confirms a local slash command (scrolling, /// exporting, feedback) in this editor's completion popup. `ThreadView` /// handles it by running the corresponding action; see /// `handle_message_editor_event`. LocalCommandInvoked(PromptLocalCommand), InputAttempted { attempt: InputAttempt, cursor_offset: usize, }, } impl EventEmitter<MessageEditorEvent> for MessageEditor {} const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0); fn insert_mention_for_project_path( project_path: &ProjectPath, editor: &Entity<Editor>, mention_set: &Entity<MentionSet>, project: &Entity<Project>, workspace: &Entity<Workspace>, supports_images: bool, window: &mut Window, cx: &mut App, ) -> Option<Task<()>> { let (file_name, mention_uri) = { let project = project.read(cx); let path_style = project.path_style(cx); let entry = project.entry_for_path(project_path, cx)?; let worktree = project.worktree_for_id(project_path.worktree_id, cx)?; let abs_path = worktree.read(cx).absolutize(&project_path.path); let (file_name, _) = crate::completion_provider::extract_file_name_and_directory( &project_path.path, worktree.read(cx).root_name(), path_style, ); let mention_uri = if entry.is_dir() { MentionUri::Directory { abs_path } } else { MentionUri::File { abs_path } }; (file_name, mention_uri) }; let mention_text = mention_uri.as_link().to_string(); let content_len = mention_text.len(); let text_anchor = editor.update(cx, |editor, cx| { let buffer = editor.buffer().read(cx); let snapshot = buffer.snapshot(cx); let buffer_snapshot = snapshot.as_singleton()?; let text_anchor = snapshot .anchor_to_buffer_anchor(editor.selections.newest_anchor().start)? .0 .bias_left(&buffer_snapshot); editor.insert(&mention_text, window, cx); editor.insert(" ", window, cx); Some(text_anchor) })?; Some(mention_set.update(cx, |mention_set, cx| { mention_set.confirm_mention_completion( file_name, text_anchor, content_len, mention_uri, supports_images, editor.clone(), workspace, window, cx, ) })) } enum ResolvedPastedContextItem { Image(gpui::Image, gpui::SharedString), ProjectPath(ProjectPath), } async fn resolve_pasted_context_items( project: Entity<Project>, project_is_local: bool, supports_images: bool, entries: Vec<ClipboardEntry>, cx: &mut gpui::AsyncWindowContext, ) -> (Vec<ResolvedPastedContextItem>, Vec<Entity<Worktree>>) { let mut items = Vec::new(); let mut added_worktrees = Vec::new(); let default_image_name: SharedString = "Image".into(); for entry in entries { match entry { ClipboardEntry::String(_) => {} ClipboardEntry::Image(image) => { if supports_images { items.push(ResolvedPastedContextItem::Image( image, default_image_name.clone(), )); } } ClipboardEntry::ExternalPaths(paths) => { for path in paths.paths().iter() { if let Some((image, name)) = cx .background_spawn({ let path = path.clone(); let default_image_name = default_image_name.clone(); async move { crate::mention_set::load_external_image_from_path( &path, &default_image_name, ) } }) .await { if supports_images { items.push(ResolvedPastedContextItem::Image(image, name)); } continue; } if !project_is_local { continue; } let path = path.clone(); let Ok(resolve_task) = cx.update({ let project = project.clone(); move |_, cx| Workspace::project_path_for_path(project, &path, false, cx) }) else { continue; }; if let Some((worktree, project_path)) = resolve_task.await.log_err() { added_worktrees.push(worktree); items.push(ResolvedPastedContextItem::ProjectPath(project_path)); } } } } } (items, added_worktrees) } fn insert_project_path_as_context( project_path: ProjectPath, editor: Entity<Editor>, mention_set: Entity<MentionSet>, workspace: WeakEntity<Workspace>, supports_images: bool, cx: &mut gpui::AsyncWindowContext, ) -> Option<Task<()>> { let workspace = workspace.upgrade()?; cx.update(move |window, cx| { let project = workspace.read(cx).project().clone(); insert_mention_for_project_path( &project_path, &editor, &mention_set, &project, &workspace, supports_images, window, cx, ) }) .ok() .flatten() } async fn insert_resolved_pasted_context_items( items: Vec<ResolvedPastedContextItem>, added_worktrees: Vec<Entity<Worktree>>, editor: Entity<Editor>, mention_set: Entity<MentionSet>, workspace: WeakEntity<Workspace>, supports_images: bool, cx: &mut gpui::AsyncWindowContext, ) { let mut path_mention_tasks = Vec::new(); for item in items { match item { ResolvedPastedContextItem::Image(image, name) => { crate::mention_set::insert_images_as_context( vec![(image, name)], editor.clone(), mention_set.clone(), workspace.clone(), cx, ) .await; } ResolvedPastedContextItem::ProjectPath(project_path) => { if let Some(task) = insert_project_path_as_context( project_path, editor.clone(), mention_set.clone(), workspace.clone(), supports_images, cx, ) { path_mention_tasks.push(task); } } } } join_all(path_mention_tasks).await; drop(added_worktrees); } impl MessageEditor { pub fn new( workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, session_capabilities: SharedSessionCapabilities, agent_id: AgentId, placeholder: &str, mode: EditorMode, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let language_registry = project .upgrade() .map(|project| project.read(cx).languages().clone()); let editor = cx.new(|cx| { let buffer = cx.new(|cx| { let buffer = Buffer::local("", cx); if let Some(language_registry) = language_registry.as_ref() { buffer.set_language_registry(language_registry.clone()); } buffer }); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let mut editor = Editor::new(mode, buffer, None, window, cx); editor.set_placeholder_text(placeholder, window, cx); editor.set_show_indent_guides(false, cx); editor.set_show_completions_on_input(Some(true)); editor.set_soft_wrap(); editor.disable_mouse_wheel_zoom(); editor.set_use_modal_editing(true); editor.set_context_menu_options(ContextMenuOptions { min_entries_visible: 12, max_entries_visible: 12, placement: None, }); editor.register_addon(MessageEditorAddon::new()); editor.set_custom_context_menu(|editor, _point, window, cx| { let has_selection = editor.has_non_empty_selection(&editor.display_snapshot(cx)); Some(ContextMenu::build(window, cx, |menu, _, _| { menu.action("Cut", Box::new(editor::actions::Cut)) .action_disabled_when( !has_selection, "Copy", Box::new(editor::actions::Copy), ) .action("Paste", Box::new(editor::actions::Paste))