3612 lines · 134.6 KB · Rust
RawDownload
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::{    assistant_language_model_selector, LanguageModelSelector, 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: 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,            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: 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() {            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())

Lines 1–500 of 3612.