1732 lines · 68.5 KB · Rust
RawDownload
use std::path::PathBuf;use std::sync::Arc;use std::time::Duration; use anyhow::{Result, anyhow};use assistant_context_editor::{    AssistantPanelDelegate, ConfigurationError, ContextEditor, SlashCommandCompletionProvider,    make_lsp_adapter_delegate, render_remaining_tokens,};use assistant_settings::{AssistantDockPosition, AssistantSettings};use assistant_slash_command::SlashCommandWorkingSet;use assistant_tool::ToolWorkingSet; use client::zed_urls;use editor::{Editor, EditorEvent, MultiBuffer};use fs::Fs;use gpui::{    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, Corner, Entity,    EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task,    UpdateGlobal, WeakEntity, prelude::*, pulsating_between,};use language::LanguageRegistry;use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};use language_model_selector::ToggleModelSelector;use project::Project;use prompt_library::{PromptLibrary, open_prompt_library};use prompt_store::PromptBuilder;use settings::{Settings, update_settings_file};use time::UtcOffset;use ui::{    Banner, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*,};use util::ResultExt as _;use workspace::Workspace;use workspace::dock::{DockPosition, Panel, PanelEvent};use zed_actions::agent::OpenConfiguration;use zed_actions::assistant::{OpenPromptLibrary, ToggleFocus}; use crate::active_thread::ActiveThread;use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};use crate::history_store::{HistoryEntry, HistoryStore};use crate::message_editor::MessageEditor;use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};use crate::thread_history::{PastContext, PastThread, ThreadHistory};use crate::thread_store::ThreadStore;use crate::{    AgentDiff, ExpandMessageEditor, InlineAssistant, NewTextThread, NewThread,    OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ThreadEvent, ToggleContextPicker,}; pub fn init(cx: &mut App) {    cx.observe_new(        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {            workspace                .register_action(|workspace, action: &NewThread, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));                        workspace.focus_panel::<AssistantPanel>(window, cx);                    }                })                .register_action(|workspace, _: &OpenHistory, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.open_history(window, cx));                    }                })                .register_action(|workspace, _: &OpenConfiguration, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));                    }                })                .register_action(|workspace, _: &NewTextThread, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));                    }                })                .register_action(|workspace, _: &OpenPromptLibrary, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.deploy_prompt_library(&OpenPromptLibrary, window, cx)                        });                    }                })                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        let thread = panel.read(cx).thread.read(cx).thread().clone();                        AgentDiff::deploy_in_workspace(thread, workspace, window, cx);                    }                })                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {                        workspace.focus_panel::<AssistantPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.message_editor.update(cx, |editor, cx| {                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);                            });                        });                    }                });        },    )    .detach();} enum ActiveView {    Thread {        change_title_editor: Entity<Editor>,        _subscriptions: Vec<gpui::Subscription>,    },    PromptEditor,    History,    Configuration,} impl ActiveView {    pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {        let summary = thread.read(cx).summary_or_default();         let editor = cx.new(|cx| {            let mut editor = Editor::single_line(window, cx);            editor.set_text(summary, window, cx);            editor        });         let subscriptions = vec![            window.subscribe(&editor, cx, {                {                    let thread = thread.clone();                    move |editor, event, window, cx| match event {                        EditorEvent::BufferEdited => {                            let new_summary = editor.read(cx).text(cx);                             thread.update(cx, |thread, cx| {                                thread.set_summary(new_summary, cx);                            })                        }                        EditorEvent::Blurred => {                            if editor.read(cx).text(cx).is_empty() {                                let summary = thread.read(cx).summary_or_default();                                 editor.update(cx, |editor, cx| {                                    editor.set_text(summary, window, cx);                                });                            }                        }                        _ => {}                    }                }            }),            window.subscribe(&thread, cx, {                let editor = editor.clone();                move |thread, event, window, cx| match event {                    ThreadEvent::SummaryGenerated => {                        let summary = thread.read(cx).summary_or_default();                         editor.update(cx, |editor, cx| {                            editor.set_text(summary, window, cx);                        })                    }                    _ => {}                }            }),        ];         Self::Thread {            change_title_editor: editor,            _subscriptions: subscriptions,        }    }} pub struct AssistantPanel {    workspace: WeakEntity<Workspace>,    project: Entity<Project>,    fs: Arc<dyn Fs>,    language_registry: Arc<LanguageRegistry>,    thread_store: Entity<ThreadStore>,    thread: Entity<ActiveThread>,    _thread_subscription: Subscription,    message_editor: Entity<MessageEditor>,    context_store: Entity<assistant_context_editor::ContextStore>,    context_editor: Option<Entity<ContextEditor>>,    configuration: Option<Entity<AssistantConfiguration>>,    configuration_subscription: Option<Subscription>,    local_timezone: UtcOffset,    active_view: ActiveView,    previous_view: Option<ActiveView>,    history_store: Entity<HistoryStore>,    history: Entity<ThreadHistory>,    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,    width: Option<Pixels>,    height: Option<Pixels>,} impl AssistantPanel {    pub fn load(        workspace: WeakEntity<Workspace>,        prompt_builder: Arc<PromptBuilder>,        cx: AsyncWindowContext,    ) -> Task<Result<Entity<Self>>> {        cx.spawn(async move |cx| {            let tools = cx.new(|_| ToolWorkingSet::default())?;            let thread_store = workspace                .update(cx, |workspace, cx| {                    let project = workspace.project().clone();                    ThreadStore::load(project, tools.clone(), prompt_builder.clone(), cx)                })?                .await;             let slash_commands = Arc::new(SlashCommandWorkingSet::default());            let context_store = workspace                .update(cx, |workspace, cx| {                    let project = workspace.project().clone();                    assistant_context_editor::ContextStore::new(                        project,                        prompt_builder.clone(),                        slash_commands,                        cx,                    )                })?                .await?;             workspace.update_in(cx, |workspace, window, cx| {                cx.new(|cx| Self::new(workspace, thread_store, context_store, window, cx))            })        })    }     fn new(        workspace: &Workspace,        thread_store: Entity<ThreadStore>,        context_store: Entity<assistant_context_editor::ContextStore>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));        let fs = workspace.app_state().fs.clone();        let project = workspace.project();        let language_registry = project.read(cx).languages().clone();        let workspace = workspace.weak_handle();        let weak_self = cx.entity().downgrade();         let message_editor_context_store = cx.new(|_cx| {            crate::context_store::ContextStore::new(                project.downgrade(),                Some(thread_store.downgrade()),            )        });         let message_editor = cx.new(|cx| {            MessageEditor::new(                fs.clone(),                workspace.clone(),                message_editor_context_store.clone(),                thread_store.downgrade(),                thread.clone(),                window,                cx,            )        });         let history_store =            cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));         cx.observe(&history_store, |_, _, cx| cx.notify()).detach();         let active_view = ActiveView::thread(thread.clone(), window, cx);        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {            if let ThreadEvent::MessageAdded(_) = &event {                // needed to leave empty state                cx.notify();            }        });        let thread = cx.new(|cx| {            ActiveThread::new(                thread.clone(),                thread_store.clone(),                language_registry.clone(),                message_editor_context_store.clone(),                workspace.clone(),                window,                cx,            )        });         Self {            active_view,            workspace,            project: project.clone(),            fs: fs.clone(),            language_registry,            thread_store: thread_store.clone(),            thread,            _thread_subscription: thread_subscription,            message_editor,            context_store,            context_editor: None,            configuration: None,            configuration_subscription: None,            local_timezone: UtcOffset::from_whole_seconds(                chrono::Local::now().offset().local_minus_utc(),            )            .unwrap(),            previous_view: None,            history_store: history_store.clone(),            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),            assistant_dropdown_menu_handle: PopoverMenuHandle::default(),            width: None,            height: None,        }    }     pub fn toggle_focus(        workspace: &mut Workspace,        _: &ToggleFocus,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        if workspace            .panel::<Self>(cx)            .is_some_and(|panel| panel.read(cx).enabled(cx))        {            workspace.toggle_panel_focus::<Self>(window, cx);        }    }     pub(crate) fn local_timezone(&self) -> UtcOffset {        self.local_timezone    }     pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {        &self.thread_store    }     fn cancel(        &mut self,        _: &editor::actions::Cancel,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        self.thread            .update(cx, |thread, cx| thread.cancel_last_completion(cx));    }     fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {        let thread = self            .thread_store            .update(cx, |this, cx| this.create_thread(cx));         let thread_view = ActiveView::thread(thread.clone(), window, cx);        self.set_active_view(thread_view, window, cx);         let message_editor_context_store = cx.new(|_cx| {            crate::context_store::ContextStore::new(                self.project.downgrade(),                Some(self.thread_store.downgrade()),            )        });         if let Some(other_thread_id) = action.from_thread_id.clone() {            let other_thread_task = self                .thread_store                .update(cx, |this, cx| this.open_thread(&other_thread_id, cx));             cx.spawn({                let context_store = message_editor_context_store.clone();                 async move |_panel, cx| {                    let other_thread = other_thread_task.await?;                     context_store.update(cx, |this, cx| {                        this.add_thread(other_thread, false, cx);                    })?;                    anyhow::Ok(())                }            })            .detach_and_log_err(cx);        }         self.thread = cx.new(|cx| {            ActiveThread::new(                thread.clone(),                self.thread_store.clone(),                self.language_registry.clone(),                message_editor_context_store.clone(),                self.workspace.clone(),                window,                cx,            )        });         self._thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {            if let ThreadEvent::MessageAdded(_) = &event {                // needed to leave empty state                cx.notify();            }        });         self.message_editor = cx.new(|cx| {            MessageEditor::new(                self.fs.clone(),                self.workspace.clone(),                message_editor_context_store,                self.thread_store.downgrade(),                thread,                window,                cx,            )        });        self.message_editor.focus_handle(cx).focus(window);    }     fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.set_active_view(ActiveView::PromptEditor, window, cx);         let context = self            .context_store            .update(cx, |context_store, cx| context_store.create(cx));        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)            .log_err()            .flatten();         self.context_editor = Some(cx.new(|cx| {            let mut editor = ContextEditor::for_context(                context,                self.fs.clone(),                self.workspace.clone(),                self.project.clone(),                lsp_adapter_delegate,                window,                cx,            );            editor.insert_default_prompt(window, cx);            editor        }));         if let Some(context_editor) = self.context_editor.as_ref() {            context_editor.focus_handle(cx).focus(window);        }    }     fn deploy_prompt_library(        &mut self,        _: &OpenPromptLibrary,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        open_prompt_library(            self.language_registry.clone(),            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),            Arc::new(|| {                Box::new(SlashCommandCompletionProvider::new(                    Arc::new(SlashCommandWorkingSet::default()),                    None,                    None,                ))            }),            cx,        )        .detach_and_log_err(cx);    }     fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {        if matches!(self.active_view, ActiveView::History) {            if let Some(previous_view) = self.previous_view.take() {                self.set_active_view(previous_view, window, cx);            }        } else {            self.thread_store                .update(cx, |thread_store, cx| thread_store.reload(cx))                .detach_and_log_err(cx);            self.set_active_view(ActiveView::History, window, cx);        }        cx.notify();    }     pub(crate) fn open_saved_prompt_editor(        &mut self,        path: PathBuf,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        let context = self            .context_store            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));        let fs = self.fs.clone();        let project = self.project.clone();        let workspace = self.workspace.clone();         let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();         cx.spawn_in(window, async move |this, cx| {            let context = context.await?;            this.update_in(cx, |this, window, cx| {                let editor = cx.new(|cx| {                    ContextEditor::for_context(

Lines 1–500 of 1732.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t