use crate::{ burn_mode_tooltip::BurnModeTooltip, language_model_selector::{LanguageModelSelector, language_model_selector},};use agent_settings::{AgentSettings, CompletionMode};use anyhow::Result;use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};use assistant_slash_commands::{ DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand, selections_creases,};use client::{proto, zed_urls};use collections::{BTreeSet, HashMap, HashSet, hash_map};use editor::{ Anchor, Editor, EditorEvent, MenuInlineCompletionsPolicy, MultiBuffer, MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint, actions::{MoveToEndOfLine, Newline, ShowCompletions}, display_map::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, },};use editor::{FoldPlaceholder, display_map::CreaseId};use fs::Fs;use futures::FutureExt;use gpui::{ Action, Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions, div, img, percentage, point, prelude::*, pulsating_between, size,};use indexed_docs::IndexedDocsStore;use language::{ BufferSnapshot, LspAdapterDelegate, ToOffset, language_settings::{SoftWrap, all_language_settings},};use language_model::{ ConfigurationError, LanguageModelExt, 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::{Settings, SettingsStore, update_settings_file};use std::{ any::TypeId, cmp, ops::Range, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration,};use text::SelectionGoal;use ui::{ ButtonLike, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, prelude::*,};use util::{ResultExt, maybe};use workspace::{ CollaboratorId, searchable::{Direction, SearchableItemHandle},};use workspace::{ Save, Toast, Workspace, item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane, searchable::{SearchEvent, SearchableItem},};use zed_actions::agent::ToggleModelSelector; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};use assistant_context::{ AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, 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, /// Quotes the current selection in the assistant conversation. QuoteSelection, /// 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<f32>, cursor: Anchor,} type MessageHeader = MessageMetadata; #[derive(Clone)]enum AssistError { PaymentRequired, Message(SharedString),} pub enum ThoughtProcessStatus { Pending, Completed,} pub trait AgentPanelDelegate { fn active_context_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<TextThreadEditor>>; fn open_saved_context( &self, workspace: &mut Workspace, path: Arc<Path>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<()>>; fn open_remote_context( &self, workspace: &mut Workspace, context_id: ContextId, 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>, );} 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 { context: Entity<AssistantContext>, 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>, show_accept_terms: bool, 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_context( context: Entity<AssistantContext>, 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( context.read(cx).slash_commands().clone(), Some(cx.entity().downgrade()), Some(workspace.clone()), ); let editor = cx.new(|cx| { let mut editor = Editor::for_buffer(context.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_inline_completions_policy(MenuInlineCompletionsPolicy::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(&context, |_, _, cx| cx.notify()), cx.subscribe_in(&context, window, Self::handle_context_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 = context.read(cx).slash_command_output_sections().to_vec(); let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec(); let slash_commands = context.read(cx).slash_commands().clone(); let mut this = Self { context, 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, show_accept_terms: false, 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(), move |model, cx| { update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| settings.set_model(model.clone()), ); }, 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 context(&self) -> &Entity<AssistantContext> { &self.context } 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.context.update(cx, |context, cx| { context.reparse(cx); context.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; } self.send_to_model(window, cx); } fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) { let provider = LanguageModelRegistry::read_global(cx) .default_model() .map(|default| default.provider); if provider .as_ref() .map_or(false, |provider| provider.must_accept_terms(cx)) { self.show_accept_terms = true; cx.notify(); return; } self.last_error = None; if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) { let new_selection = { let cursor = user_message .start .to_offset(self.context.read(cx).buffer().read(cx)); cursor..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 .context .update(cx, |context, cx| context.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.context.update(cx, |context, cx| { let messages = context .messages_for_offsets(cursors, cx) .into_iter() .map(|message| message.id) .collect(); context.cycle_message_roles(messages, cx) }); } fn cursors(&self, cx: &mut App) -> Vec<usize> { let selections = self .editor .update(cx, |editor, cx| editor.selections.all::<usize>(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>(cx).head(); if newest_cursor.column > 0 || snapshot .chars_at(newest_cursor) .next() .map_or(false, |ch| ch != '\n') { editor.move_to_end_of_line( &MoveToEndOfLine { stop_at_soft_wraps: false, }, window, cx, ); editor.newline(&Newline, window, cx); } editor.insert(&format!("/{name}"), window, cx); if command.accepts_arguments() { editor.insert(" ", window, cx); editor.show_completions(&ShowCompletions::default(), window, cx); } }); }); if !command.requires_argument() { self.confirm_command(&ConfirmCommand, window, cx); } } } pub fn confirm_command( &mut self, _: &ConfirmCommand, window: &mut Window, cx: &mut Context<Self>, ) { if self.editor.read(cx).has_visible_completions_menu() { return; } let selections = self.editor.read(cx).selections.disjoint_anchors(); let mut commands_by_range = HashMap::default();Lines 1–500 of 3333. Load more
use crate::{ burn_mode_tooltip::BurnModeTooltip, language_model_selector::{LanguageModelSelector, language_model_selector}, }; use agent_settings::{AgentSettings, CompletionMode}; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{ DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand, selections_creases, }; use client::{proto, zed_urls}; use collections::{BTreeSet, HashMap, HashSet, hash_map}; use editor::{ Anchor, Editor, EditorEvent, MenuInlineCompletionsPolicy, MultiBuffer, MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint, actions::{MoveToEndOfLine, Newline, ShowCompletions}, display_map::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, }; use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; use futures::FutureExt; use gpui::{ Action, Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions, div, img, percentage, point, prelude::*, pulsating_between, size, }; use indexed_docs::IndexedDocsStore; use language::{ BufferSnapshot, LspAdapterDelegate, ToOffset, language_settings::{SoftWrap, all_language_settings}, }; use language_model::{ ConfigurationError, LanguageModelExt, 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::{Settings, SettingsStore, update_settings_file}; use std::{ any::TypeId, cmp, ops::Range, path::{Path, PathBuf}, rc::Rc, sync::Arc, time::Duration, }; use text::SelectionGoal; use ui::{ ButtonLike, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, prelude::*, }; use util::{ResultExt, maybe}; use workspace::{ CollaboratorId, searchable::{Direction, SearchableItemHandle}, }; use workspace::{ Save, Toast, Workspace, item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane, searchable::{SearchEvent, SearchableItem}, }; use zed_actions::agent::ToggleModelSelector; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker}; use assistant_context::{ AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, 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, /// Quotes the current selection in the assistant conversation. QuoteSelection, /// 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<f32>, cursor: Anchor, } type MessageHeader = MessageMetadata; #[derive(Clone)] enum AssistError { PaymentRequired, Message(SharedString), } pub enum ThoughtProcessStatus { Pending, Completed, } pub trait AgentPanelDelegate { fn active_context_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<TextThreadEditor>>; fn open_saved_context( &self, workspace: &mut Workspace, path: Arc<Path>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Task<Result<()>>; fn open_remote_context( &self, workspace: &mut Workspace, context_id: ContextId, 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>, ); } 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 { context: Entity<AssistantContext>, 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>, show_accept_terms: bool, 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_context( context: Entity<AssistantContext>, 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( context.read(cx).slash_commands().clone(), Some(cx.entity().downgrade()), Some(workspace.clone()), ); let editor = cx.new(|cx| { let mut editor = Editor::for_buffer(context.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_inline_completions_policy(MenuInlineCompletionsPolicy::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(&context, |_, _, cx| cx.notify()), cx.subscribe_in(&context, window, Self::handle_context_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 = context.read(cx).slash_command_output_sections().to_vec(); let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec(); let slash_commands = context.read(cx).slash_commands().clone(); let mut this = Self { context, 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, show_accept_terms: false, 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(), move |model, cx| { update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| settings.set_model(model.clone()), ); }, 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 context(&self) -> &Entity<AssistantContext> { &self.context } 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.context.update(cx, |context, cx| { context.reparse(cx); context.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; } self.send_to_model(window, cx); } fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) { let provider = LanguageModelRegistry::read_global(cx) .default_model() .map(|default| default.provider); if provider .as_ref() .map_or(false, |provider| provider.must_accept_terms(cx)) { self.show_accept_terms = true; cx.notify(); return; } self.last_error = None; if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) { let new_selection = { let cursor = user_message .start .to_offset(self.context.read(cx).buffer().read(cx)); cursor..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 .context .update(cx, |context, cx| context.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.context.update(cx, |context, cx| { let messages = context .messages_for_offsets(cursors, cx) .into_iter() .map(|message| message.id) .collect(); context.cycle_message_roles(messages, cx) }); } fn cursors(&self, cx: &mut App) -> Vec<usize> { let selections = self .editor .update(cx, |editor, cx| editor.selections.all::<usize>(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>(cx).head(); if newest_cursor.column > 0 || snapshot .chars_at(newest_cursor) .next() .map_or(false, |ch| ch != '\n') { editor.move_to_end_of_line( &MoveToEndOfLine { stop_at_soft_wraps: false, }, window, cx, ); editor.newline(&Newline, window, cx); } editor.insert(&format!("/{name}"), window, cx); if command.accepts_arguments() { editor.insert(" ", window, cx); editor.show_completions(&ShowCompletions::default(), window, cx); } }); }); if !command.requires_argument() { self.confirm_command(&ConfirmCommand, window, cx); } } } pub fn confirm_command( &mut self, _: &ConfirmCommand, window: &mut Window, cx: &mut Context<Self>, ) { if self.editor.read(cx).has_visible_completions_menu() { return; } let selections = self.editor.read(cx).selections.disjoint_anchors(); let mut commands_by_range = HashMap::default();