use crate::{ language_model_selector::{LanguageModelSelector, language_model_selector}, ui::ModelSelectorTooltip,};use anyhow::Result;use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases};use client::{proto, zed_urls};use collections::{BTreeSet, HashMap, HashSet, hash_map};use editor::{ Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset, MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint as _, actions::{MoveToEndOfLine, Newline, ShowCompletions}, display_map::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::ScrollOffset,};use editor::{FoldPlaceholder, display_map::CreaseId};use fs::Fs;use futures::FutureExt;use gpui::{ Action, Animation, AnimationExt, AnyElement, App, ClipboardEntry, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, actions, div, img, point, prelude::*, pulsating_between, size,};use language::{ BufferSnapshot, LspAdapterDelegate, ToOffset, language_settings::{SoftWrap, all_language_settings},};use language_model::{ ConfigurationError, IconOrSvg, LanguageModelImage, LanguageModelRegistry, Role,};use multi_buffer::MultiBufferRow;use picker::{Picker, popover_menu::PickerPopoverMenu};use project::{Project, Worktree};use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};use rope::Point;use serde::{Deserialize, Serialize};use settings::{ LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore, update_settings_file,};use std::{ any::{Any, TypeId}, cmp, ops::Range, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration,};use text::SelectionGoal;use ui::{ ButtonLike, CommonAnimationExt, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, prelude::*,};use util::{ResultExt, maybe};use workspace::{ CollaboratorId, searchable::{Direction, SearchToken, SearchableItemHandle},}; use workspace::{ Save, Toast, Workspace, item::{self, FollowableItem, Item}, notifications::NotificationId, pane, searchable::{SearchEvent, SearchableItem},};use zed_actions::agent::{AddSelectionToThread, PasteRaw, ToggleModelSelector}; use crate::CycleFavoriteModels; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};use assistant_text_thread::{ CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent, TextThreadId, ThoughtProcessOutputSection,}; actions!( assistant, [ /// Sends the current message to the assistant. Assist, /// Confirms and executes the entered slash command. ConfirmCommand, /// Copies code from the assistant's response to the clipboard. CopyCode, /// Cycles between user and assistant message roles. CycleMessageRole, /// Inserts the selected text into the active editor. InsertIntoEditor, /// Splits the conversation at the current cursor position. Split, ]); /// Inserts files that were dragged and dropped into the assistant conversation.#[derive(PartialEq, Clone, Action)]#[action(namespace = assistant, no_json, no_register)]pub enum InsertDraggedFiles { ProjectPaths(Vec<ProjectPath>), ExternalFiles(Vec<PathBuf>),} #[derive(Copy, Clone, Debug, PartialEq)]struct ScrollPosition { offset_before_cursor: gpui::Point<ScrollOffset>, cursor: Anchor,} type MessageHeader = MessageMetadata; #[derive(Clone)]enum AssistError { PaymentRequired, Message(SharedString),} pub enum ThoughtProcessStatus { Pending, Completed,} pub trait AgentPanelDelegate { fn active_text_thread_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<TextThreadEditor>>; fn open_local_text_thread( &self, workspace: &mut Workspace, path: Arc<Path>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<()>>; fn open_remote_text_thread( &self, workspace: &mut Workspace, text_thread_id: TextThreadId, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<Entity<TextThreadEditor>>>; fn quote_selection( &self, workspace: &mut Workspace, selection_ranges: Vec<Range<Anchor>>, buffer: Entity<MultiBuffer>, window: &mut Window, cx: &mut Context<Workspace>, ); fn quote_terminal_text( &self, workspace: &mut Workspace, text: String, window: &mut Window, cx: &mut Context<Workspace>, );} impl dyn AgentPanelDelegate { /// Returns the global [`AssistantPanelDelegate`], if it exists. pub fn try_global(cx: &App) -> Option<Arc<Self>> { cx.try_global::<GlobalAssistantPanelDelegate>() .map(|global| global.0.clone()) } /// Sets the global [`AssistantPanelDelegate`]. pub fn set_global(delegate: Arc<Self>, cx: &mut App) { cx.set_global(GlobalAssistantPanelDelegate(delegate)); }} struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>); impl Global for GlobalAssistantPanelDelegate {} pub struct TextThreadEditor { text_thread: Entity<TextThread>, fs: Arc<dyn Fs>, slash_commands: Arc<SlashCommandWorkingSet>, workspace: WeakEntity<Workspace>, project: Entity<Project>, lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>, editor: Entity<Editor>, pending_thought_process: Option<(CreaseId, language::Anchor)>, blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>, image_blocks: HashSet<CustomBlockId>, scroll_position: Option<ScrollPosition>, remote_id: Option<workspace::ViewId>, pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>, invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>, _subscriptions: Vec<Subscription>, last_error: Option<AssistError>, pub(crate) slash_menu_handle: PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>, // dragged_file_worktrees is used to keep references to worktrees that were added // when the user drag/dropped an external file onto the context editor. Since // the worktree is not part of the project panel, it would be dropped as soon as // the file is opened. In order to keep the worktree alive for the duration of the // context editor, we keep a reference here. dragged_file_worktrees: Vec<Entity<Worktree>>, language_model_selector: Entity<LanguageModelSelector>, language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,} const MAX_TAB_TITLE_LEN: usize = 16; impl TextThreadEditor { pub fn init(cx: &mut App) { workspace::FollowableViewRegistry::register::<TextThreadEditor>(cx); cx.observe_new( |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| { workspace .register_action(TextThreadEditor::quote_selection) .register_action(TextThreadEditor::insert_selection) .register_action(TextThreadEditor::copy_code) .register_action(TextThreadEditor::handle_insert_dragged_files); }, ) .detach(); } pub fn for_text_thread( text_thread: Entity<TextThread>, fs: Arc<dyn Fs>, workspace: WeakEntity<Workspace>, project: Entity<Project>, lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let completion_provider = SlashCommandCompletionProvider::new( text_thread.read(cx).slash_commands().clone(), Some(cx.entity().downgrade()), Some(workspace.clone()), ); let editor = cx.new(|cx| { let mut editor = Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx); editor.disable_scrollbars_and_minimap(window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_line_numbers(false, cx); editor.set_show_git_diff_gutter(false, cx); editor.set_show_code_actions(false, cx); editor.set_show_runnables(false, cx); editor.set_show_breakpoints(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); editor.set_completion_provider(Some(Rc::new(completion_provider))); editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never); editor.set_collaboration_hub(Box::new(project.clone())); let show_edit_predictions = all_language_settings(None, cx) .edit_predictions .enabled_in_text_threads; editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx); editor }); let _subscriptions = vec![ cx.observe(&text_thread, |_, _, cx| cx.notify()), cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event), cx.subscribe_in(&editor, window, Self::handle_editor_event), cx.subscribe_in(&editor, window, Self::handle_editor_search_event), cx.observe_global_in::<SettingsStore>(window, Self::settings_changed), ]; let slash_command_sections = text_thread .read(cx) .slash_command_output_sections() .to_vec(); let thought_process_sections = text_thread .read(cx) .thought_process_output_sections() .to_vec(); let slash_commands = text_thread.read(cx).slash_commands().clone(); let focus_handle = editor.read(cx).focus_handle(cx); let mut this = Self { text_thread, slash_commands, editor, lsp_adapter_delegate, blocks: Default::default(), image_blocks: Default::default(), scroll_position: None, remote_id: None, pending_thought_process: None, fs: fs.clone(), workspace, project, pending_slash_command_creases: HashMap::default(), invoked_slash_command_creases: HashMap::default(), _subscriptions, last_error: None, slash_menu_handle: Default::default(), dragged_file_worktrees: Vec::new(), language_model_selector: cx.new(|cx| { language_model_selector( |cx| LanguageModelRegistry::read_global(cx).default_model(), { let fs = fs.clone(); move |model, cx| { update_settings_file(fs.clone(), cx, move |settings, _| { let provider = model.provider_id().0.to_string(); let model_id = model.id().0.to_string(); settings.agent.get_or_insert_default().set_model( LanguageModelSelection { provider: LanguageModelProviderSetting(provider), model: model_id, enable_thinking: model.supports_thinking(), effort: model .default_effort_level() .map(|effort| effort.value.to_string()), }, ) }); } }, { let fs = fs.clone(); move |model, should_be_favorite, cx| { crate::favorite_models::toggle_in_settings( model, should_be_favorite, fs.clone(), cx, ); } }, true, // Use popover styles for picker focus_handle, window, cx, ) }), language_model_selector_menu_handle: PopoverMenuHandle::default(), }; this.update_message_headers(cx); this.update_image_blocks(cx); this.insert_slash_command_output_sections(slash_command_sections, false, window, cx); this.insert_thought_process_output_sections( thought_process_sections .into_iter() .map(|section| (section, ThoughtProcessStatus::Completed)), window, cx, ); this } fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { let show_edit_predictions = all_language_settings(None, cx) .edit_predictions .enabled_in_text_threads; editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx); }); } pub fn text_thread(&self) -> &Entity<TextThread> { &self.text_thread } pub fn editor(&self) -> &Entity<Editor> { &self.editor } pub fn insert_default_prompt(&mut self, window: &mut Window, cx: &mut Context<Self>) { let command_name = DefaultSlashCommand.name(); self.editor.update(cx, |editor, cx| { editor.insert(&format!("/{command_name}\n\n"), window, cx) }); let command = self.text_thread.update(cx, |text_thread, cx| { text_thread.reparse(cx); text_thread.parsed_slash_commands()[0].clone() }); self.run_command( command.source_range, &command.name, &command.arguments, false, self.workspace.clone(), window, cx, ); } fn assist(&mut self, _: &Assist, window: &mut Window, cx: &mut Context<Self>) { if self.sending_disabled(cx) { return; } telemetry::event!("Agent Message Sent", agent = "zed-text"); self.send_to_model(window, cx); } fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.last_error = None; if let Some(user_message) = self .text_thread .update(cx, |text_thread, cx| text_thread.assist(cx)) { let new_selection = { let cursor = user_message .start .to_offset(self.text_thread.read(cx).buffer().read(cx)); MultiBufferOffset(cursor)..MultiBufferOffset(cursor) }; self.editor.update(cx, |editor, cx| { editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([new_selection]) }); }); // Avoid scrolling to the new cursor position so the assistant's output is stable. cx.defer_in(window, |this, _, _| this.scroll_position = None); } cx.notify(); } fn cancel( &mut self, _: &editor::actions::Cancel, _window: &mut Window, cx: &mut Context<Self>, ) { self.last_error = None; if self .text_thread .update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx)) { return; } cx.propagate(); } fn cycle_message_role( &mut self, _: &CycleMessageRole, _window: &mut Window, cx: &mut Context<Self>, ) { let cursors = self.cursors(cx); self.text_thread.update(cx, |text_thread, cx| { let messages = text_thread .messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx) .into_iter() .map(|message| message.id) .collect(); text_thread.cycle_message_roles(messages, cx) }); } fn cursors(&self, cx: &mut App) -> Vec<MultiBufferOffset> { let selections = self.editor.update(cx, |editor, cx| { editor .selections .all::<MultiBufferOffset>(&editor.display_snapshot(cx)) }); selections .into_iter() .map(|selection| selection.head()) .collect() } pub fn insert_command(&mut self, name: &str, window: &mut Window, cx: &mut Context<Self>) { if let Some(command) = self.slash_commands.command(name, cx) { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { editor.change_selections(Default::default(), window, cx, |s| s.try_cancel()); let snapshot = editor.buffer().read(cx).snapshot(cx); let newest_cursor = editor .selections .newest::<Point>(&editor.display_snapshot(cx)) .head(); if newest_cursor.column > 0 || snapshot .chars_at(newest_cursor) .next() .is_some_and(|ch| ch != '\n') {Lines 1–500 of 3479. Load more
use crate::{ language_model_selector::{LanguageModelSelector, language_model_selector}, ui::ModelSelectorTooltip, }; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases}; use client::{proto, zed_urls}; use collections::{BTreeSet, HashMap, HashSet, hash_map}; use editor::{ Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset, MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint as _, actions::{MoveToEndOfLine, Newline, ShowCompletions}, display_map::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::ScrollOffset, }; use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; use futures::FutureExt; use gpui::{ Action, Animation, AnimationExt, AnyElement, App, ClipboardEntry, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, actions, div, img, point, prelude::*, pulsating_between, size, }; use language::{ BufferSnapshot, LspAdapterDelegate, ToOffset, language_settings::{SoftWrap, all_language_settings}, }; use language_model::{ ConfigurationError, IconOrSvg, LanguageModelImage, LanguageModelRegistry, Role, }; use multi_buffer::MultiBufferRow; use picker::{Picker, popover_menu::PickerPopoverMenu}; use project::{Project, Worktree}; use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate}; use rope::Point; use serde::{Deserialize, Serialize}; use settings::{ LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore, update_settings_file, }; use std::{ any::{Any, TypeId}, cmp, ops::Range, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration, }; use text::SelectionGoal; use ui::{ ButtonLike, CommonAnimationExt, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, prelude::*, }; use util::{ResultExt, maybe}; use workspace::{ CollaboratorId, searchable::{Direction, SearchToken, SearchableItemHandle}, }; use workspace::{ Save, Toast, Workspace, item::{self, FollowableItem, Item}, notifications::NotificationId, pane, searchable::{SearchEvent, SearchableItem}, }; use zed_actions::agent::{AddSelectionToThread, PasteRaw, ToggleModelSelector}; use crate::CycleFavoriteModels; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker}; use assistant_text_thread::{ CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent, TextThreadId, ThoughtProcessOutputSection, }; actions!( assistant, [ /// Sends the current message to the assistant. Assist, /// Confirms and executes the entered slash command. ConfirmCommand, /// Copies code from the assistant's response to the clipboard. CopyCode, /// Cycles between user and assistant message roles. CycleMessageRole, /// Inserts the selected text into the active editor. InsertIntoEditor, /// Splits the conversation at the current cursor position. Split, ] ); /// Inserts files that were dragged and dropped into the assistant conversation. #[derive(PartialEq, Clone, Action)] #[action(namespace = assistant, no_json, no_register)] pub enum InsertDraggedFiles { ProjectPaths(Vec<ProjectPath>), ExternalFiles(Vec<PathBuf>), } #[derive(Copy, Clone, Debug, PartialEq)] struct ScrollPosition { offset_before_cursor: gpui::Point<ScrollOffset>, cursor: Anchor, } type MessageHeader = MessageMetadata; #[derive(Clone)] enum AssistError { PaymentRequired, Message(SharedString), } pub enum ThoughtProcessStatus { Pending, Completed, } pub trait AgentPanelDelegate { fn active_text_thread_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<TextThreadEditor>>; fn open_local_text_thread( &self, workspace: &mut Workspace, path: Arc<Path>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<()>>; fn open_remote_text_thread( &self, workspace: &mut Workspace, text_thread_id: TextThreadId, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<Entity<TextThreadEditor>>>; fn quote_selection( &self, workspace: &mut Workspace, selection_ranges: Vec<Range<Anchor>>, buffer: Entity<MultiBuffer>, window: &mut Window, cx: &mut Context<Workspace>, ); fn quote_terminal_text( &self, workspace: &mut Workspace, text: String, window: &mut Window, cx: &mut Context<Workspace>, ); } impl dyn AgentPanelDelegate { /// Returns the global [`AssistantPanelDelegate`], if it exists. pub fn try_global(cx: &App) -> Option<Arc<Self>> { cx.try_global::<GlobalAssistantPanelDelegate>() .map(|global| global.0.clone()) } /// Sets the global [`AssistantPanelDelegate`]. pub fn set_global(delegate: Arc<Self>, cx: &mut App) { cx.set_global(GlobalAssistantPanelDelegate(delegate)); } } struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>); impl Global for GlobalAssistantPanelDelegate {} pub struct TextThreadEditor { text_thread: Entity<TextThread>, fs: Arc<dyn Fs>, slash_commands: Arc<SlashCommandWorkingSet>, workspace: WeakEntity<Workspace>, project: Entity<Project>, lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>, editor: Entity<Editor>, pending_thought_process: Option<(CreaseId, language::Anchor)>, blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>, image_blocks: HashSet<CustomBlockId>, scroll_position: Option<ScrollPosition>, remote_id: Option<workspace::ViewId>, pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>, invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>, _subscriptions: Vec<Subscription>, last_error: Option<AssistError>, pub(crate) slash_menu_handle: PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>, // dragged_file_worktrees is used to keep references to worktrees that were added // when the user drag/dropped an external file onto the context editor. Since // the worktree is not part of the project panel, it would be dropped as soon as // the file is opened. In order to keep the worktree alive for the duration of the // context editor, we keep a reference here. dragged_file_worktrees: Vec<Entity<Worktree>>, language_model_selector: Entity<LanguageModelSelector>, language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, } const MAX_TAB_TITLE_LEN: usize = 16; impl TextThreadEditor { pub fn init(cx: &mut App) { workspace::FollowableViewRegistry::register::<TextThreadEditor>(cx); cx.observe_new( |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| { workspace .register_action(TextThreadEditor::quote_selection) .register_action(TextThreadEditor::insert_selection) .register_action(TextThreadEditor::copy_code) .register_action(TextThreadEditor::handle_insert_dragged_files); }, ) .detach(); } pub fn for_text_thread( text_thread: Entity<TextThread>, fs: Arc<dyn Fs>, workspace: WeakEntity<Workspace>, project: Entity<Project>, lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let completion_provider = SlashCommandCompletionProvider::new( text_thread.read(cx).slash_commands().clone(), Some(cx.entity().downgrade()), Some(workspace.clone()), ); let editor = cx.new(|cx| { let mut editor = Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx); editor.disable_scrollbars_and_minimap(window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_line_numbers(false, cx); editor.set_show_git_diff_gutter(false, cx); editor.set_show_code_actions(false, cx); editor.set_show_runnables(false, cx); editor.set_show_breakpoints(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); editor.set_completion_provider(Some(Rc::new(completion_provider))); editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never); editor.set_collaboration_hub(Box::new(project.clone())); let show_edit_predictions = all_language_settings(None, cx) .edit_predictions .enabled_in_text_threads; editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx); editor }); let _subscriptions = vec![ cx.observe(&text_thread, |_, _, cx| cx.notify()), cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event), cx.subscribe_in(&editor, window, Self::handle_editor_event), cx.subscribe_in(&editor, window, Self::handle_editor_search_event), cx.observe_global_in::<SettingsStore>(window, Self::settings_changed), ]; let slash_command_sections = text_thread .read(cx) .slash_command_output_sections() .to_vec(); let thought_process_sections = text_thread .read(cx) .thought_process_output_sections() .to_vec(); let slash_commands = text_thread.read(cx).slash_commands().clone(); let focus_handle = editor.read(cx).focus_handle(cx); let mut this = Self { text_thread, slash_commands, editor, lsp_adapter_delegate, blocks: Default::default(), image_blocks: Default::default(), scroll_position: None, remote_id: None, pending_thought_process: None, fs: fs.clone(), workspace, project, pending_slash_command_creases: HashMap::default(), invoked_slash_command_creases: HashMap::default(), _subscriptions, last_error: None, slash_menu_handle: Default::default(), dragged_file_worktrees: Vec::new(), language_model_selector: cx.new(|cx| { language_model_selector( |cx| LanguageModelRegistry::read_global(cx).default_model(), { let fs = fs.clone(); move |model, cx| { update_settings_file(fs.clone(), cx, move |settings, _| { let provider = model.provider_id().0.to_string(); let model_id = model.id().0.to_string(); settings.agent.get_or_insert_default().set_model( LanguageModelSelection { provider: LanguageModelProviderSetting(provider), model: model_id, enable_thinking: model.supports_thinking(), effort: model .default_effort_level() .map(|effort| effort.value.to_string()), }, ) }); } }, { let fs = fs.clone(); move |model, should_be_favorite, cx| { crate::favorite_models::toggle_in_settings( model, should_be_favorite, fs.clone(), cx, ); } }, true, // Use popover styles for picker focus_handle, window, cx, ) }), language_model_selector_menu_handle: PopoverMenuHandle::default(), }; this.update_message_headers(cx); this.update_image_blocks(cx); this.insert_slash_command_output_sections(slash_command_sections, false, window, cx); this.insert_thought_process_output_sections( thought_process_sections .into_iter() .map(|section| (section, ThoughtProcessStatus::Completed)), window, cx, ); this } fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { let show_edit_predictions = all_language_settings(None, cx) .edit_predictions .enabled_in_text_threads; editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx); }); } pub fn text_thread(&self) -> &Entity<TextThread> { &self.text_thread } pub fn editor(&self) -> &Entity<Editor> { &self.editor } pub fn insert_default_prompt(&mut self, window: &mut Window, cx: &mut Context<Self>) { let command_name = DefaultSlashCommand.name(); self.editor.update(cx, |editor, cx| { editor.insert(&format!("/{command_name}\n\n"), window, cx) }); let command = self.text_thread.update(cx, |text_thread, cx| { text_thread.reparse(cx); text_thread.parsed_slash_commands()[0].clone() }); self.run_command( command.source_range, &command.name, &command.arguments, false, self.workspace.clone(), window, cx, ); } fn assist(&mut self, _: &Assist, window: &mut Window, cx: &mut Context<Self>) { if self.sending_disabled(cx) { return; } telemetry::event!("Agent Message Sent", agent = "zed-text"); self.send_to_model(window, cx); } fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.last_error = None; if let Some(user_message) = self .text_thread .update(cx, |text_thread, cx| text_thread.assist(cx)) { let new_selection = { let cursor = user_message .start .to_offset(self.text_thread.read(cx).buffer().read(cx)); MultiBufferOffset(cursor)..MultiBufferOffset(cursor) }; self.editor.update(cx, |editor, cx| { editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([new_selection]) }); }); // Avoid scrolling to the new cursor position so the assistant's output is stable. cx.defer_in(window, |this, _, _| this.scroll_position = None); } cx.notify(); } fn cancel( &mut self, _: &editor::actions::Cancel, _window: &mut Window, cx: &mut Context<Self>, ) { self.last_error = None; if self .text_thread .update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx)) { return; } cx.propagate(); } fn cycle_message_role( &mut self, _: &CycleMessageRole, _window: &mut Window, cx: &mut Context<Self>, ) { let cursors = self.cursors(cx); self.text_thread.update(cx, |text_thread, cx| { let messages = text_thread .messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx) .into_iter() .map(|message| message.id) .collect(); text_thread.cycle_message_roles(messages, cx) }); } fn cursors(&self, cx: &mut App) -> Vec<MultiBufferOffset> { let selections = self.editor.update(cx, |editor, cx| { editor .selections .all::<MultiBufferOffset>(&editor.display_snapshot(cx)) }); selections .into_iter() .map(|selection| selection.head()) .collect() } pub fn insert_command(&mut self, name: &str, window: &mut Window, cx: &mut Context<Self>) { if let Some(command) = self.slash_commands.command(name, cx) { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { editor.change_selections(Default::default(), window, cx, |s| s.try_cancel()); let snapshot = editor.buffer().read(cx).snapshot(cx); let newest_cursor = editor .selections .newest::<Point>(&editor.display_snapshot(cx)) .head(); if newest_cursor.column > 0 || snapshot .chars_at(newest_cursor) .next() .is_some_and(|ch| ch != '\n') {