1011 lines · 44.1 KB · Rust
RawDownload
use std::collections::BTreeMap;use std::sync::Arc; use crate::assistant_model_selector::ModelType;use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};use buffer_diff::BufferDiff;use collections::HashSet;use editor::actions::MoveUp;use editor::{    ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode, EditorStyle,    MultiBuffer,};use file_icons::FileIcons;use fs::Fs;use gpui::{    Animation, AnimationExt, App, Entity, Focusable, Subscription, TextStyle, WeakEntity,    linear_color_stop, linear_gradient, point, pulsating_between,};use language::{Buffer, Language};use language_model::{ConfiguredModel, LanguageModelRegistry};use language_model_selector::ToggleModelSelector;use multi_buffer;use project::Project;use settings::Settings;use std::time::Duration;use theme::ThemeSettings;use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};use util::ResultExt as _;use workspace::Workspace; use crate::assistant_model_selector::AssistantModelSelector;use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};use crate::context_store::{ContextStore, refresh_context_store_text};use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};use crate::profile_selector::ProfileSelector;use crate::thread::{RequestKind, Thread, TokenUsageRatio};use crate::thread_store::ThreadStore;use crate::{    AgentDiff, Chat, ChatMode, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext,    ToggleContextPicker, ToggleProfileSelector,}; pub struct MessageEditor {    thread: Entity<Thread>,    incompatible_tools_state: Entity<IncompatibleToolsState>,    editor: Entity<Editor>,    #[allow(dead_code)]    workspace: WeakEntity<Workspace>,    project: Entity<Project>,    context_store: Entity<ContextStore>,    context_strip: Entity<ContextStrip>,    context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,    model_selector: Entity<AssistantModelSelector>,    profile_selector: Entity<ProfileSelector>,    edits_expanded: bool,    editor_is_expanded: bool,    waiting_for_summaries_to_send: bool,    _subscriptions: Vec<Subscription>,} const MAX_EDITOR_LINES: usize = 8; impl MessageEditor {    pub fn new(        fs: Arc<dyn Fs>,        workspace: WeakEntity<Workspace>,        context_store: Entity<ContextStore>,        thread_store: WeakEntity<ThreadStore>,        thread: Entity<Thread>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let context_picker_menu_handle = PopoverMenuHandle::default();        let model_selector_menu_handle = PopoverMenuHandle::default();         let language = Language::new(            language::LanguageConfig {                completion_query_characters: HashSet::from_iter(['.', '-', '_', '@']),                ..Default::default()            },            None,        );         let editor = cx.new(|cx| {            let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));            let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));            let mut editor = Editor::new(                editor::EditorMode::AutoHeight {                    max_lines: MAX_EDITOR_LINES,                },                buffer,                None,                window,                cx,            );            editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);            editor.set_show_indent_guides(false, cx);            editor.set_soft_wrap();            editor.set_context_menu_options(ContextMenuOptions {                min_entries_visible: 12,                max_entries_visible: 12,                placement: Some(ContextMenuPlacement::Above),            });            editor        });         let editor_entity = editor.downgrade();        editor.update(cx, |editor, _| {            editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(                workspace.clone(),                context_store.downgrade(),                Some(thread_store.clone()),                editor_entity,            ))));        });         let context_strip = cx.new(|cx| {            ContextStrip::new(                context_store.clone(),                workspace.clone(),                Some(thread_store.clone()),                context_picker_menu_handle.clone(),                SuggestContextKind::File,                window,                cx,            )        });         let incompatible_tools =            cx.new(|cx| IncompatibleToolsState::new(thread.read(cx).tools().clone(), cx));         let subscriptions =            vec![cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event)];         Self {            editor: editor.clone(),            project: thread.read(cx).project().clone(),            thread,            incompatible_tools_state: incompatible_tools.clone(),            workspace,            context_store,            context_strip,            context_picker_menu_handle,            model_selector: cx.new(|cx| {                AssistantModelSelector::new(                    fs.clone(),                    model_selector_menu_handle,                    editor.focus_handle(cx),                    ModelType::Default,                    window,                    cx,                )            }),            edits_expanded: false,            editor_is_expanded: false,            waiting_for_summaries_to_send: false,            profile_selector: cx                .new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),            _subscriptions: subscriptions,        }    }     fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {        cx.notify();    }     pub fn expand_message_editor(        &mut self,        _: &ExpandMessageEditor,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        self.set_editor_is_expanded(!self.editor_is_expanded, cx);    }     fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context<Self>) {        self.editor_is_expanded = is_expanded;        self.editor.update(cx, |editor, _| {            if self.editor_is_expanded {                editor.set_mode(EditorMode::Full {                    scale_ui_elements_with_buffer_font_size: false,                    show_active_line_background: false,                })            } else {                editor.set_mode(EditorMode::AutoHeight {                    max_lines: MAX_EDITOR_LINES,                })            }        });        cx.notify();    }     fn toggle_context_picker(        &mut self,        _: &ToggleContextPicker,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.context_picker_menu_handle.toggle(window, cx);    }    pub fn remove_all_context(        &mut self,        _: &RemoveAllContext,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        self.context_store.update(cx, |store, _cx| store.clear());        cx.notify();    }     fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {        if self.is_editor_empty(cx) {            return;        }         if self.thread.read(cx).is_generating() {            self.stop_current_and_send_new_message(window, cx);            return;        }         self.set_editor_is_expanded(false, cx);        self.send_to_model(RequestKind::Chat, window, cx);         cx.notify();    }     fn is_editor_empty(&self, cx: &App) -> bool {        self.editor.read(cx).text(cx).trim().is_empty()    }     fn is_model_selected(&self, cx: &App) -> bool {        LanguageModelRegistry::read_global(cx)            .default_model()            .is_some()    }     fn send_to_model(        &mut self,        request_kind: RequestKind,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let model_registry = LanguageModelRegistry::read_global(cx);        let Some(ConfiguredModel { model, provider }) = model_registry.default_model() else {            return;        };         if provider.must_accept_terms(cx) {            cx.notify();            return;        }         let user_message = self.editor.update(cx, |editor, cx| {            let text = editor.text(cx);            editor.clear(window, cx);            text        });         let refresh_task =            refresh_context_store_text(self.context_store.clone(), &HashSet::default(), cx);         let thread = self.thread.clone();        let context_store = self.context_store.clone();        let git_store = self.project.read(cx).git_store().clone();        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));         cx.spawn(async move |this, cx| {            let checkpoint = checkpoint.await.ok();            refresh_task.await;             thread                .update(cx, |thread, cx| {                    let context = context_store.read(cx).context().clone();                    thread.insert_user_message(user_message, context, checkpoint, cx);                })                .log_err();             if let Some(wait_for_summaries) = context_store                .update(cx, |context_store, cx| context_store.wait_for_summaries(cx))                .log_err()            {                this.update(cx, |this, cx| {                    this.waiting_for_summaries_to_send = true;                    cx.notify();                })                .log_err();                 wait_for_summaries.await;                 this.update(cx, |this, cx| {                    this.waiting_for_summaries_to_send = false;                    cx.notify();                })                .log_err();            }             // Send to model after summaries are done            thread                .update(cx, |thread, cx| {                    thread.send_to_model(model, request_kind, cx);                })                .log_err();        })        .detach();    }     fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {        let cancelled = self            .thread            .update(cx, |thread, cx| thread.cancel_last_completion(cx));         if cancelled {            self.set_editor_is_expanded(false, cx);            self.send_to_model(RequestKind::Chat, window, cx);        }    }     fn handle_context_strip_event(        &mut self,        _context_strip: &Entity<ContextStrip>,        event: &ContextStripEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        match event {            ContextStripEvent::PickerDismissed            | ContextStripEvent::BlurredEmpty            | ContextStripEvent::BlurredDown => {                let editor_focus_handle = self.editor.focus_handle(cx);                window.focus(&editor_focus_handle);            }            ContextStripEvent::BlurredUp => {}        }    }     fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {        if self.context_picker_menu_handle.is_deployed() {            cx.propagate();        } else {            self.context_strip.focus_handle(cx).focus(window);        }    }     fn handle_review_click(&self, window: &mut Window, cx: &mut Context<Self>) {        AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();    }     fn handle_file_click(        &self,        buffer: Entity<Buffer>,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if let Ok(diff) = AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx)        {            let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);            diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));        }    }     fn render_editor(        &self,        font_size: Rems,        line_height: Pixels,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Div {        let thread = self.thread.read(cx);         let editor_bg_color = cx.theme().colors().editor_background;        let is_generating = thread.is_generating();        let focus_handle = self.editor.focus_handle(cx);         let is_model_selected = self.is_model_selected(cx);        let is_editor_empty = self.is_editor_empty(cx);         let model = LanguageModelRegistry::read_global(cx)            .default_model()            .map(|default| default.model.clone());         let incompatible_tools = model            .as_ref()            .map(|model| {                self.incompatible_tools_state.update(cx, |state, cx| {                    state                        .incompatible_tools(model, cx)                        .iter()                        .cloned()                        .collect::<Vec<_>>()                })            })            .unwrap_or_default();         let is_editor_expanded = self.editor_is_expanded;        let expand_icon = if is_editor_expanded {            IconName::Minimize        } else {            IconName::Maximize        };         v_flex()            .key_context("MessageEditor")            .on_action(cx.listener(Self::chat))            .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {                this.profile_selector                    .read(cx)                    .menu_handle()                    .toggle(window, cx);            }))            .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {                this.model_selector                    .update(cx, |model_selector, cx| model_selector.toggle(window, cx));            }))            .on_action(cx.listener(Self::toggle_context_picker))            .on_action(cx.listener(Self::remove_all_context))            .on_action(cx.listener(Self::move_up))            .on_action(cx.listener(Self::toggle_chat_mode))            .on_action(cx.listener(Self::expand_message_editor))            .gap_2()            .p_2()            .bg(editor_bg_color)            .border_t_1()            .border_color(cx.theme().colors().border)            .child(                h_flex()                    .items_start()                    .justify_between()                    .child(self.context_strip.clone())                    .child(                        IconButton::new("toggle-height", expand_icon)                            .icon_size(IconSize::XSmall)                            .icon_color(Color::Muted)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |window, cx| {                                    let expand_label = if is_editor_expanded {                                        "Minimize Message Editor".to_string()                                    } else {                                        "Expand Message Editor".to_string()                                    };                                     Tooltip::for_action_in(                                        expand_label,                                        &ExpandMessageEditor,                                        &focus_handle,                                        window,                                        cx,                                    )                                }                            })                            .on_click(cx.listener(|_, _, window, cx| {                                window.dispatch_action(Box::new(ExpandMessageEditor), cx);                            })),                    ),            )            .child(                v_flex()                    .size_full()                    .gap_4()                    .when(is_editor_expanded, |this| {                        this.h(vh(0.8, window)).justify_between()                    })                    .child(                        div()                            .min_h_16()                            .when(is_editor_expanded, |this| this.h_full())                            .child({                                let settings = ThemeSettings::get_global(cx);                                 let text_style = TextStyle {                                    color: cx.theme().colors().text,                                    font_family: settings.buffer_font.family.clone(),                                    font_fallbacks: settings.buffer_font.fallbacks.clone(),                                    font_features: settings.buffer_font.features.clone(),                                    font_size: font_size.into(),                                    line_height: line_height.into(),                                    ..Default::default()                                };                                 EditorElement::new(                                    &self.editor,                                    EditorStyle {                                        background: editor_bg_color,                                        local_player: cx.theme().players().local(),                                        text: text_style,                                        syntax: cx.theme().syntax().clone(),                                        ..Default::default()                                    },                                )                                .into_any()                            }),                    )                    .child(                        h_flex()                            .flex_none()                            .justify_between()                            .child(h_flex().gap_2().child(self.profile_selector.clone()))                            .child(                                h_flex()                                    .gap_1()

Lines 1–500 of 1011.