3375 lines · 125.3 KB · Rust
RawDownload
use crate::language_model_selector::{    LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,};use agent_settings::AgentSettings;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,    },    scroll::Autoscroll,};use editor::{FoldPlaceholder, display_map::CreaseId};use fs::Fs;use futures::FutureExt;use gpui::{    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, impl_internal_actions, percentage, point, prelude::*, pulsating_between, size,};use indexed_docs::IndexedDocsStore;use language::{    BufferSnapshot, LspAdapterDelegate, ToOffset,    language_settings::{SoftWrap, all_language_settings},};use language_model::{    LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,    Role,};use multi_buffer::MultiBufferRow;use picker::Picker;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, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,    item::{self, FollowableItem, Item, ItemHandle},    notifications::NotificationId,    pane,    searchable::{SearchEvent, SearchableItem},}; use crate::{    AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,    InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,    ParsedSlashCommand, PendingSlashCommandStatus,};use crate::{    ThoughtProcessOutputSection, slash_command::SlashCommandCompletionProvider,    slash_command_picker,}; actions!(    assistant,    [        Assist,        ConfirmCommand,        CopyCode,        CycleMessageRole,        InsertIntoEditor,        QuoteSelection,        Split,    ]); #[derive(PartialEq, Clone)]pub enum InsertDraggedFiles {    ProjectPaths(Vec<ProjectPath>),    ExternalFiles(Vec<PathBuf>),} impl_internal_actions!(assistant, [InsertDraggedFiles]); #[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<ContextEditor>>;     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<ContextEditor>>>;     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 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>,    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>,} 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.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| {                LanguageModelSelector::new(                    |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(Some(Autoscroll::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() {            return;        }         let selections = self.editor.read(cx).selections.disjoint_anchors();        let mut commands_by_range = HashMap::default();        let workspace = self.workspace.clone();        self.context.update(cx, |context, cx| {            context.reparse(cx);            for selection in selections.iter() {                if let Some(command) =                    context.pending_command_for_position(selection.head().text_anchor, cx)                {                    commands_by_range                        .entry(command.source_range.clone())                        .or_insert_with(|| command.clone());                }            }        });         if commands_by_range.is_empty() {            cx.propagate();        } else {

Lines 1–500 of 3375.