use anyhow::Result;use assistant_settings::AssistantSettings;use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};use assistant_slash_commands::{ selections_creases, DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,};use client::{proto, zed_urls};use collections::{hash_map, BTreeSet, HashMap, HashSet};use editor::{ actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, display_map::{ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::{Autoscroll, AutoscrollStrategy}, Anchor, Editor, EditorEvent, MenuInlineCompletionsPolicy, ProposedChangeLocation, ProposedChangesEditor, RowExt, ToOffset as _, ToPoint,};use editor::{display_map::CreaseId, FoldPlaceholder};use fs::Fs;use futures::FutureExt;use gpui::{ actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between, size, Animation, AnimationExt, AnyElement, AnyView, App, AsyncWindowContext, ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity,};use indexed_docs::IndexedDocsStore;use language::{ language_settings::{all_language_settings, SoftWrap}, BufferSnapshot, LspAdapterDelegate, ToOffset,};use language_model::{ LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry, Role,};use language_model_selector::{ LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,};use multi_buffer::MultiBufferRow;use picker::Picker;use project::lsp_store::LocalLspAdapterDelegate;use project::{Project, Worktree};use rope::Point;use serde::{Deserialize, Serialize};use settings::{update_settings_file, Settings, SettingsStore};use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration};use text::SelectionGoal;use ui::{ prelude::*, ButtonLike, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip,};use util::{maybe, ResultExt};use workspace::searchable::{Direction, SearchableItemHandle};use workspace::{ item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane::{self, SaveIntent}, searchable::{SearchEvent, SearchableItem}, Save, ShowConfiguration, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,}; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};use crate::{ AssistantContext, AssistantPatch, AssistantPatchStatus, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, RequestType,}; actions!( assistant, [ Assist, ConfirmCommand, CopyCode, CycleMessageRole, Edit, InsertIntoEditor, QuoteSelection, Split, ]); #[derive(PartialEq, Clone)]pub enum InsertDraggedFiles { ProjectPaths(Vec<PathBuf>), ExternalFiles(Vec<PathBuf>),} impl_internal_actions!(assistant, [InsertDraggedFiles]); #[derive(Copy, Clone, Debug, PartialEq)]struct ScrollPosition { offset_before_cursor: gpui::Point<f32>, cursor: Anchor,} struct PatchViewState { crease_id: CreaseId, editor: Option<PatchEditorState>, update_task: Option<Task<()>>,} struct PatchEditorState { editor: WeakEntity<ProposedChangesEditor>, opened_patch: AssistantPatch,} type MessageHeader = MessageMetadata; #[derive(Clone)]enum AssistError { FileRequired, PaymentRequired, MaxMonthlySpendReached, Message(SharedString),} pub trait AssistantPanelDelegate { fn active_context_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<ContextEditor>>; fn open_saved_context( &self, workspace: &mut Workspace, path: PathBuf, 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<ContextEditor>>>; fn quote_selection( &self, workspace: &mut Workspace, creases: Vec<(String, String)>, window: &mut Window, cx: &mut Context<Workspace>, );} impl dyn AssistantPanelDelegate { /// 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 AssistantPanelDelegate>); impl Global for GlobalAssistantPanelDelegate {} pub struct ContextEditor { 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>, 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>, patches: HashMap<Range<language::Anchor>, PatchViewState>, active_patch: Option<Range<language::Anchor>>, 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>,} pub const DEFAULT_TAB_TITLE: &str = "New Chat";const MAX_TAB_TITLE_LEN: usize = 16; impl ContextEditor { 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.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_line_numbers(false, cx); editor.set_show_scrollbars(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_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); editor.set_completion_provider(Some(Box::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_assistant; 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 sections = context.read(cx).slash_command_output_sections().to_vec(); let patch_ranges = context.read(cx).patch_ranges().collect::<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, fs: fs.clone(), workspace, project, pending_slash_command_creases: HashMap::default(), invoked_slash_command_creases: HashMap::default(), _subscriptions, patches: HashMap::default(), active_patch: None, last_error: None, show_accept_terms: false, slash_menu_handle: Default::default(), dragged_file_worktrees: Vec::new(), language_model_selector: cx.new(|cx| { LanguageModelSelector::new( move |model, cx| { update_settings_file::<AssistantSettings>( 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(sections, false, window, cx); this.patches_updated(&Vec::new(), &patch_ranges, 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_assistant; 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>) { self.send_to_model(RequestType::Chat, window, cx); } fn edit(&mut self, _: &Edit, window: &mut Window, cx: &mut Context<Self>) { self.send_to_model(RequestType::SuggestEdits, window, cx); } fn focus_active_patch(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool { if let Some((_range, patch)) = self.active_patch() { if let Some(editor) = patch .editor .as_ref() .and_then(|state| state.editor.upgrade()) { editor.focus_handle(cx).focus(window); return true; } } false } fn send_to_model( &mut self, request_type: RequestType, window: &mut Window, cx: &mut Context<Self>, ) { let provider = LanguageModelRegistry::read_global(cx).active_provider(); if provider .as_ref() .map_or(false, |provider| provider.must_accept_terms(cx)) { self.show_accept_terms = true; cx.notify(); return; } if self.focus_active_patch(window, cx) { return; } self.last_error = None; if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) { self.last_error = Some(AssistError::FileRequired); cx.notify(); } else if let Some(user_message) = self .context .update(cx, |context, cx| context.assist(request_type, cx)) { 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( Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)), 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(Some(Autoscroll::fit()), 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() {Lines 1–500 of 3658. Load more
use anyhow::Result; use assistant_settings::AssistantSettings; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{ selections_creases, DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand, }; use client::{proto, zed_urls}; use collections::{hash_map, BTreeSet, HashMap, HashSet}; use editor::{ actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, display_map::{ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::{Autoscroll, AutoscrollStrategy}, Anchor, Editor, EditorEvent, MenuInlineCompletionsPolicy, ProposedChangeLocation, ProposedChangesEditor, RowExt, ToOffset as _, ToPoint, }; use editor::{display_map::CreaseId, FoldPlaceholder}; use fs::Fs; use futures::FutureExt; use gpui::{ actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between, size, Animation, AnimationExt, AnyElement, AnyView, App, AsyncWindowContext, ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, }; use indexed_docs::IndexedDocsStore; use language::{ language_settings::{all_language_settings, SoftWrap}, BufferSnapshot, LspAdapterDelegate, ToOffset, }; use language_model::{ LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry, Role, }; use language_model_selector::{ LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector, }; use multi_buffer::MultiBufferRow; use picker::Picker; use project::lsp_store::LocalLspAdapterDelegate; use project::{Project, Worktree}; use rope::Point; use serde::{Deserialize, Serialize}; use settings::{update_settings_file, Settings, SettingsStore}; use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration}; use text::SelectionGoal; use ui::{ prelude::*, ButtonLike, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, }; use util::{maybe, ResultExt}; use workspace::searchable::{Direction, SearchableItemHandle}; use workspace::{ item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane::{self, SaveIntent}, searchable::{SearchEvent, SearchableItem}, Save, ShowConfiguration, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker}; use crate::{ AssistantContext, AssistantPatch, AssistantPatchStatus, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, RequestType, }; actions!( assistant, [ Assist, ConfirmCommand, CopyCode, CycleMessageRole, Edit, InsertIntoEditor, QuoteSelection, Split, ] ); #[derive(PartialEq, Clone)] pub enum InsertDraggedFiles { ProjectPaths(Vec<PathBuf>), ExternalFiles(Vec<PathBuf>), } impl_internal_actions!(assistant, [InsertDraggedFiles]); #[derive(Copy, Clone, Debug, PartialEq)] struct ScrollPosition { offset_before_cursor: gpui::Point<f32>, cursor: Anchor, } struct PatchViewState { crease_id: CreaseId, editor: Option<PatchEditorState>, update_task: Option<Task<()>>, } struct PatchEditorState { editor: WeakEntity<ProposedChangesEditor>, opened_patch: AssistantPatch, } type MessageHeader = MessageMetadata; #[derive(Clone)] enum AssistError { FileRequired, PaymentRequired, MaxMonthlySpendReached, Message(SharedString), } pub trait AssistantPanelDelegate { fn active_context_editor( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> Option<Entity<ContextEditor>>; fn open_saved_context( &self, workspace: &mut Workspace, path: PathBuf, 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<ContextEditor>>>; fn quote_selection( &self, workspace: &mut Workspace, creases: Vec<(String, String)>, window: &mut Window, cx: &mut Context<Workspace>, ); } impl dyn AssistantPanelDelegate { /// 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 AssistantPanelDelegate>); impl Global for GlobalAssistantPanelDelegate {} pub struct ContextEditor { 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>, 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>, patches: HashMap<Range<language::Anchor>, PatchViewState>, active_patch: Option<Range<language::Anchor>>, 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>, } pub const DEFAULT_TAB_TITLE: &str = "New Chat"; const MAX_TAB_TITLE_LEN: usize = 16; impl ContextEditor { 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.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_line_numbers(false, cx); editor.set_show_scrollbars(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_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); editor.set_completion_provider(Some(Box::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_assistant; 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 sections = context.read(cx).slash_command_output_sections().to_vec(); let patch_ranges = context.read(cx).patch_ranges().collect::<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, fs: fs.clone(), workspace, project, pending_slash_command_creases: HashMap::default(), invoked_slash_command_creases: HashMap::default(), _subscriptions, patches: HashMap::default(), active_patch: None, last_error: None, show_accept_terms: false, slash_menu_handle: Default::default(), dragged_file_worktrees: Vec::new(), language_model_selector: cx.new(|cx| { LanguageModelSelector::new( move |model, cx| { update_settings_file::<AssistantSettings>( 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(sections, false, window, cx); this.patches_updated(&Vec::new(), &patch_ranges, 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_assistant; 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>) { self.send_to_model(RequestType::Chat, window, cx); } fn edit(&mut self, _: &Edit, window: &mut Window, cx: &mut Context<Self>) { self.send_to_model(RequestType::SuggestEdits, window, cx); } fn focus_active_patch(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool { if let Some((_range, patch)) = self.active_patch() { if let Some(editor) = patch .editor .as_ref() .and_then(|state| state.editor.upgrade()) { editor.focus_handle(cx).focus(window); return true; } } false } fn send_to_model( &mut self, request_type: RequestType, window: &mut Window, cx: &mut Context<Self>, ) { let provider = LanguageModelRegistry::read_global(cx).active_provider(); if provider .as_ref() .map_or(false, |provider| provider.must_accept_terms(cx)) { self.show_accept_terms = true; cx.notify(); return; } if self.focus_active_patch(window, cx) { return; } self.last_error = None; if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) { self.last_error = Some(AssistError::FileRequired); cx.notify(); } else if let Some(user_message) = self .context .update(cx, |context, cx| context.assist(request_type, cx)) { 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( Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)), 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(Some(Autoscroll::fit()), 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() {