1435 lines · 50.1 KB · Rust
RawDownload
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};use crate::{    terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewChat,};use anyhow::{anyhow, Result};use assistant_context_editor::{    make_lsp_adapter_delegate, AssistantContext, AssistantPanelDelegate, ContextEditor,    ContextEditorToolbarItem, ContextEditorToolbarItemEvent, ContextHistory, ContextId,    ContextStore, ContextStoreEvent, InsertDraggedFiles, SlashCommandCompletionProvider,    DEFAULT_TAB_TITLE,};use assistant_settings::{AssistantDockPosition, AssistantSettings};use assistant_slash_command::SlashCommandWorkingSet;use client::{proto, Client, Status};use editor::{Editor, EditorEvent};use fs::Fs;use gpui::{    prelude::*, Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle,    Focusable, InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled,    Subscription, Task, UpdateGlobal, WeakEntity,};use language::LanguageRegistry;use language_model::{    AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,};use project::Project;use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};use search::{buffer_search::DivRegistrar, BufferSearchBar};use settings::{update_settings_file, Settings};use smol::stream::StreamExt;use std::{ops::ControlFlow, path::PathBuf, sync::Arc};use terminal_view::{terminal_panel::TerminalPanel, TerminalView};use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};use util::{maybe, ResultExt};use workspace::DraggedTab;use workspace::{    dock::{DockPosition, Panel, PanelEvent},    pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,};use zed_actions::assistant::{DeployPromptLibrary, InlineAssist, ToggleFocus}; pub fn init(cx: &mut App) {    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);    cx.observe_new(        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {            workspace                .register_action(ContextEditor::quote_selection)                .register_action(ContextEditor::insert_selection)                .register_action(ContextEditor::copy_code)                .register_action(ContextEditor::insert_dragged_files)                .register_action(AssistantPanel::show_configuration)                .register_action(AssistantPanel::create_new_context)                .register_action(AssistantPanel::restart_context_servers);        },    )    .detach();     cx.observe_new(        |terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {            let settings = AssistantSettings::get_global(cx);            terminal_panel.set_assistant_enabled(settings.enabled, cx);        },    )    .detach();} pub enum AssistantPanelEvent {    ContextEdited,} pub struct AssistantPanel {    pane: Entity<Pane>,    workspace: WeakEntity<Workspace>,    width: Option<Pixels>,    height: Option<Pixels>,    project: Entity<Project>,    context_store: Entity<ContextStore>,    languages: Arc<LanguageRegistry>,    fs: Arc<dyn Fs>,    subscriptions: Vec<Subscription>,    model_summary_editor: Entity<Editor>,    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,    configuration_subscription: Option<Subscription>,    client_status: Option<client::Status>,    watch_client_status: Option<Task<()>>,    pub(crate) show_zed_ai_notice: bool,} enum InlineAssistTarget {    Editor(Entity<Editor>, bool),    Terminal(Entity<TerminalView>),} impl AssistantPanel {    pub fn load(        workspace: WeakEntity<Workspace>,        prompt_builder: Arc<PromptBuilder>,        cx: AsyncWindowContext,    ) -> Task<Result<Entity<Self>>> {        cx.spawn(|mut cx| async move {            let slash_commands = Arc::new(SlashCommandWorkingSet::default());            let context_store = workspace                .update(&mut cx, |workspace, cx| {                    let project = workspace.project().clone();                    ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)                })?                .await?;             workspace.update_in(&mut cx, |workspace, window, cx| {                // TODO: deserialize state.                cx.new(|cx| Self::new(workspace, context_store, window, cx))            })        })    }     fn new(        workspace: &Workspace,        context_store: Entity<ContextStore>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx));        let context_editor_toolbar =            cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone()));         let pane = cx.new(|cx| {            let mut pane = Pane::new(                workspace.weak_handle(),                workspace.project().clone(),                Default::default(),                None,                NewChat.boxed_clone(),                window,                cx,            );             let project = workspace.project().clone();            pane.set_custom_drop_handle(cx, move |_, dropped_item, window, cx| {                let action = maybe!({                    if project.read(cx).is_local() {                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));                        }                    }                     let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()                    {                        if tab.pane == cx.entity() {                            return None;                        }                        let item = tab.pane.read(cx).item_for_index(tab.ix);                        Some(                            item.and_then(|item| item.project_path(cx))                                .into_iter()                                .collect::<Vec<_>>(),                        )                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()                    {                        Some(                            selection                                .items()                                .filter_map(|item| {                                    project.read(cx).path_for_entry(item.entry_id, cx)                                })                                .collect::<Vec<_>>(),                        )                    } else {                        None                    }?;                     let paths = project_paths                        .into_iter()                        .filter_map(|project_path| {                            let worktree = project                                .read(cx)                                .worktree_for_id(project_path.worktree_id, cx)?;                             let mut full_path = PathBuf::from(worktree.read(cx).root_name());                            full_path.push(&project_path.path);                            Some(full_path)                        })                        .collect::<Vec<_>>();                     Some(InsertDraggedFiles::ProjectPaths(paths))                });                 if let Some(action) = action {                    window.dispatch_action(action.boxed_clone(), cx);                }                 ControlFlow::Break(())            });             pane.set_can_navigate(true, cx);            pane.display_nav_history_buttons(None);            pane.set_should_display_tab_bar(|_, _| true);            pane.set_render_tab_bar_buttons(cx, move |pane, _window, cx| {                let focus_handle = pane.focus_handle(cx);                let left_children = IconButton::new("history", IconName::HistoryRerun)                    .icon_size(IconSize::Small)                    .on_click(cx.listener({                        let focus_handle = focus_handle.clone();                        move |_, _, window, cx| {                            focus_handle.focus(window);                            window.dispatch_action(DeployHistory.boxed_clone(), cx)                        }                    }))                    .tooltip({                        let focus_handle = focus_handle.clone();                        move |window, cx| {                            Tooltip::for_action_in(                                "Open History",                                &DeployHistory,                                &focus_handle,                                window,                                cx,                            )                        }                    })                    .toggle_state(                        pane.active_item()                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),                    );                let _pane = cx.entity().clone();                let right_children = h_flex()                    .gap(DynamicSpacing::Base02.rems(cx))                    .child(                        IconButton::new("new-chat", IconName::Plus)                            .icon_size(IconSize::Small)                            .on_click(cx.listener(|_, _, window, cx| {                                window.dispatch_action(NewChat.boxed_clone(), cx)                            }))                            .tooltip(move |window, cx| {                                Tooltip::for_action_in(                                    "New Chat",                                    &NewChat,                                    &focus_handle,                                    window,                                    cx,                                )                            }),                    )                    .child(                        PopoverMenu::new("assistant-panel-popover-menu")                            .trigger_with_tooltip(                                IconButton::new("menu", IconName::EllipsisVertical)                                    .icon_size(IconSize::Small),                                Tooltip::text("Toggle Assistant Menu"),                            )                            .menu(move |window, cx| {                                let zoom_label = if _pane.read(cx).is_zoomed() {                                    "Zoom Out"                                } else {                                    "Zoom In"                                };                                let focus_handle = _pane.focus_handle(cx);                                Some(ContextMenu::build(window, cx, move |menu, _, _| {                                    menu.context(focus_handle.clone())                                        .action("New Chat", Box::new(NewChat))                                        .action("History", Box::new(DeployHistory))                                        .action("Prompt Library", Box::new(DeployPromptLibrary))                                        .action("Configure", Box::new(ShowConfiguration))                                        .action(zoom_label, Box::new(ToggleZoom))                                }))                            }),                    )                    .into_any_element()                    .into();                 (Some(left_children.into_any_element()), right_children)            });            pane.toolbar().update(cx, |toolbar, cx| {                toolbar.add_item(context_editor_toolbar.clone(), window, cx);                toolbar.add_item(                    cx.new(|cx| {                        BufferSearchBar::new(                            Some(workspace.project().read(cx).languages().clone()),                            window,                            cx,                        )                    }),                    window,                    cx,                )            });            pane        });         let subscriptions = vec![            cx.observe(&pane, |_, _, cx| cx.notify()),            cx.subscribe_in(&pane, window, Self::handle_pane_event),            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),            cx.subscribe_in(&context_store, window, Self::handle_context_store_event),            cx.subscribe_in(                &LanguageModelRegistry::global(cx),                window,                |this, _, event: &language_model::Event, window, cx| match event {                    language_model::Event::ActiveModelChanged => {                        this.completion_provider_changed(window, cx);                    }                    language_model::Event::ProviderStateChanged => {                        this.ensure_authenticated(window, cx);                        cx.notify()                    }                    language_model::Event::AddedProvider(_)                    | language_model::Event::RemovedProvider(_) => {                        this.ensure_authenticated(window, cx);                    }                },            ),        ];         let watch_client_status = Self::watch_client_status(workspace.client().clone(), window, cx);         let mut this = Self {            pane,            workspace: workspace.weak_handle(),            width: None,            height: None,            project: workspace.project().clone(),            context_store,            languages: workspace.app_state().languages.clone(),            fs: workspace.app_state().fs.clone(),            subscriptions,            model_summary_editor,            authenticate_provider_task: None,            configuration_subscription: None,            client_status: None,            watch_client_status: Some(watch_client_status),            show_zed_ai_notice: false,        };        this.new_context(window, cx);        this    }     pub fn toggle_focus(        workspace: &mut Workspace,        _: &ToggleFocus,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        let settings = AssistantSettings::get_global(cx);        if !settings.enabled {            return;        }         workspace.toggle_panel_focus::<Self>(window, cx);    }     fn watch_client_status(        client: Arc<Client>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Task<()> {        let mut status_rx = client.status();         cx.spawn_in(window, |this, mut cx| async move {            while let Some(status) = status_rx.next().await {                this.update(&mut cx, |this, cx| {                    if this.client_status.is_none()                        || this                            .client_status                            .map_or(false, |old_status| old_status != status)                    {                        this.update_zed_ai_notice_visibility(status, cx);                    }                    this.client_status = Some(status);                })                .log_err();            }            this.update(&mut cx, |this, _cx| this.watch_client_status = None)                .log_err();        })    }     fn handle_pane_event(        &mut self,        pane: &Entity<Pane>,        event: &pane::Event,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let update_model_summary = match event {            pane::Event::Remove { .. } => {                cx.emit(PanelEvent::Close);                false            }            pane::Event::ZoomIn => {                cx.emit(PanelEvent::ZoomIn);                false            }            pane::Event::ZoomOut => {                cx.emit(PanelEvent::ZoomOut);                false            }             pane::Event::AddItem { item } => {                self.workspace                    .update(cx, |workspace, cx| {                        item.added_to_pane(workspace, self.pane.clone(), window, cx)                    })                    .ok();                true            }             pane::Event::ActivateItem { local, .. } => {                if *local {                    self.workspace                        .update(cx, |workspace, cx| {                            workspace.unfollow_in_pane(&pane, window, cx);                        })                        .ok();                }                cx.emit(AssistantPanelEvent::ContextEdited);                true            }            pane::Event::RemovedItem { .. } => {                let has_configuration_view = self                    .pane                    .read(cx)                    .items_of_type::<ConfigurationView>()                    .next()                    .is_some();                 if !has_configuration_view {                    self.configuration_subscription = None;                }                 cx.emit(AssistantPanelEvent::ContextEdited);                true            }             _ => false,        };         if update_model_summary {            if let Some(editor) = self.active_context_editor(cx) {                self.show_updated_summary(&editor, window, cx)            }        }    }     fn handle_summary_editor_event(        &mut self,        model_summary_editor: Entity<Editor>,        event: &EditorEvent,        cx: &mut Context<Self>,    ) {        if matches!(event, EditorEvent::Edited { .. }) {            if let Some(context_editor) = self.active_context_editor(cx) {                let new_summary = model_summary_editor.read(cx).text(cx);                context_editor.update(cx, |context_editor, cx| {                    context_editor.context().update(cx, |context, cx| {                        if context.summary().is_none()                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())                        {                            return;                        }                        context.custom_summary(new_summary, cx)                    });                });            }        }    }     fn update_zed_ai_notice_visibility(&mut self, client_status: Status, cx: &mut Context<Self>) {        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();         // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is        // the provider, we want to show a nudge to sign in.        let show_zed_ai_notice = client_status.is_signed_out()            && active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);         self.show_zed_ai_notice = show_zed_ai_notice;        cx.notify();    }     fn handle_toolbar_event(        &mut self,        _: Entity<ContextEditorToolbarItem>,        _: &ContextEditorToolbarItemEvent,        cx: &mut Context<Self>,    ) {        if let Some(context_editor) = self.active_context_editor(cx) {            context_editor.update(cx, |context_editor, cx| {                context_editor.context().update(cx, |context, cx| {                    context.summarize(true, cx);                })            })        }    }     fn handle_context_store_event(        &mut self,        _context_store: &Entity<ContextStore>,        event: &ContextStoreEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {

Lines 1–500 of 1435.