assistant_panel.rs

   1use crate::{
   2    assistant_settings::{AssistantDockPosition, AssistantSettings},
   3    context_inspector::ContextInspector,
   4    humanize_token_count,
   5    prompt_library::open_prompt_library,
   6    prompts::PromptBuilder,
   7    slash_command::{
   8        default_command::DefaultSlashCommand,
   9        docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
  10        file_command::codeblock_fence_for_path,
  11        SlashCommandCompletionProvider, SlashCommandRegistry,
  12    },
  13    terminal_inline_assistant::TerminalInlineAssistant,
  14    Assist, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore, CycleMessageRole,
  15    DebugWorkflowSteps, DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId,
  16    InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand,
  17    PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, ResolvedWorkflowStep,
  18    SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
  19};
  20use crate::{ContextStoreEvent, ShowConfiguration};
  21use anyhow::{anyhow, Result};
  22use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
  23use client::{proto, Client, Status};
  24use collections::{BTreeSet, HashMap, HashSet};
  25use editor::{
  26    actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
  27    display_map::{
  28        BlockContext, BlockDisposition, BlockProperties, BlockStyle, Crease, CustomBlockId,
  29        RenderBlock, ToDisplayPoint,
  30    },
  31    scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
  32    Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
  33};
  34use editor::{display_map::CreaseId, FoldPlaceholder};
  35use fs::Fs;
  36use gpui::{
  37    canvas, div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView,
  38    AppContext, AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity,
  39    EntityId, EventEmitter, FocusHandle, FocusableView, FontWeight, InteractiveElement,
  40    IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render, SharedString,
  41    StatefulInteractiveElement, Styled, Subscription, Task, Transformation, UpdateGlobal, View,
  42    ViewContext, VisualContext, WeakView, WindowContext,
  43};
  44use indexed_docs::IndexedDocsStore;
  45use language::{
  46    language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
  47};
  48use language_model::{
  49    provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
  50    LanguageModelRegistry, Role,
  51};
  52use multi_buffer::MultiBufferRow;
  53use picker::{Picker, PickerDelegate};
  54use project::{Project, ProjectLspAdapterDelegate};
  55use search::{buffer_search::DivRegistrar, BufferSearchBar};
  56use settings::{update_settings_file, Settings};
  57use smol::stream::StreamExt;
  58use std::{
  59    borrow::Cow,
  60    cmp::{self, Ordering},
  61    fmt::Write,
  62    ops::Range,
  63    path::PathBuf,
  64    sync::Arc,
  65    time::Duration,
  66};
  67use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  68use text::OffsetRangeExt;
  69use ui::TintColor;
  70use ui::{
  71    prelude::*,
  72    utils::{format_distance_from_now, DateTimeType},
  73    Avatar, AvatarShape, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
  74    ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
  75};
  76use util::ResultExt;
  77use workspace::{
  78    dock::{DockPosition, Panel, PanelEvent},
  79    item::{self, FollowableItem, Item, ItemHandle},
  80    notifications::NotifyTaskExt,
  81    pane::{self, SaveIntent},
  82    searchable::{SearchEvent, SearchableItem},
  83    Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
  84};
  85use workspace::{searchable::SearchableItemHandle, NewFile};
  86
  87pub fn init(cx: &mut AppContext) {
  88    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  89    cx.observe_new_views(
  90        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  91            workspace
  92                .register_action(|workspace, _: &ToggleFocus, cx| {
  93                    let settings = AssistantSettings::get_global(cx);
  94                    if !settings.enabled {
  95                        return;
  96                    }
  97
  98                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  99                })
 100                .register_action(AssistantPanel::inline_assist)
 101                .register_action(ContextEditor::quote_selection)
 102                .register_action(ContextEditor::insert_selection)
 103                .register_action(AssistantPanel::show_configuration);
 104        },
 105    )
 106    .detach();
 107
 108    cx.observe_new_views(
 109        |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
 110            let settings = AssistantSettings::get_global(cx);
 111            if !settings.enabled {
 112                return;
 113            }
 114
 115            terminal_panel.register_tab_bar_button(cx.new_view(|_| InlineAssistTabBarButton), cx);
 116        },
 117    )
 118    .detach();
 119}
 120
 121struct InlineAssistTabBarButton;
 122
 123impl Render for InlineAssistTabBarButton {
 124    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 125        IconButton::new("terminal_inline_assistant", IconName::MagicWand)
 126            .icon_size(IconSize::Small)
 127            .on_click(cx.listener(|_, _, cx| {
 128                cx.dispatch_action(InlineAssist::default().boxed_clone());
 129            }))
 130            .tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx))
 131    }
 132}
 133
 134pub enum AssistantPanelEvent {
 135    ContextEdited,
 136}
 137
 138pub struct AssistantPanel {
 139    pane: View<Pane>,
 140    workspace: WeakView<Workspace>,
 141    width: Option<Pixels>,
 142    height: Option<Pixels>,
 143    project: Model<Project>,
 144    context_store: Model<ContextStore>,
 145    languages: Arc<LanguageRegistry>,
 146    fs: Arc<dyn Fs>,
 147    subscriptions: Vec<Subscription>,
 148    model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
 149    model_summary_editor: View<Editor>,
 150    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
 151    configuration_subscription: Option<Subscription>,
 152    client_status: Option<client::Status>,
 153    watch_client_status: Option<Task<()>>,
 154    show_zed_ai_notice: bool,
 155}
 156
 157#[derive(Clone)]
 158enum ContextMetadata {
 159    Remote(RemoteContextMetadata),
 160    Saved(SavedContextMetadata),
 161}
 162
 163struct SavedContextPickerDelegate {
 164    store: Model<ContextStore>,
 165    project: Model<Project>,
 166    matches: Vec<ContextMetadata>,
 167    selected_index: usize,
 168}
 169
 170enum SavedContextPickerEvent {
 171    Confirmed(ContextMetadata),
 172}
 173
 174enum InlineAssistTarget {
 175    Editor(View<Editor>, bool),
 176    Terminal(View<TerminalView>),
 177}
 178
 179impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
 180
 181impl SavedContextPickerDelegate {
 182    fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
 183        Self {
 184            project,
 185            store,
 186            matches: Vec::new(),
 187            selected_index: 0,
 188        }
 189    }
 190}
 191
 192impl PickerDelegate for SavedContextPickerDelegate {
 193    type ListItem = ListItem;
 194
 195    fn match_count(&self) -> usize {
 196        self.matches.len()
 197    }
 198
 199    fn selected_index(&self) -> usize {
 200        self.selected_index
 201    }
 202
 203    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
 204        self.selected_index = ix;
 205    }
 206
 207    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 208        "Search...".into()
 209    }
 210
 211    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 212        let search = self.store.read(cx).search(query, cx);
 213        cx.spawn(|this, mut cx| async move {
 214            let matches = search.await;
 215            this.update(&mut cx, |this, cx| {
 216                let host_contexts = this.delegate.store.read(cx).host_contexts();
 217                this.delegate.matches = host_contexts
 218                    .iter()
 219                    .cloned()
 220                    .map(ContextMetadata::Remote)
 221                    .chain(matches.into_iter().map(ContextMetadata::Saved))
 222                    .collect();
 223                this.delegate.selected_index = 0;
 224                cx.notify();
 225            })
 226            .ok();
 227        })
 228    }
 229
 230    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
 231        if let Some(metadata) = self.matches.get(self.selected_index) {
 232            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
 233        }
 234    }
 235
 236    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 237
 238    fn render_match(
 239        &self,
 240        ix: usize,
 241        selected: bool,
 242        cx: &mut ViewContext<Picker<Self>>,
 243    ) -> Option<Self::ListItem> {
 244        let context = self.matches.get(ix)?;
 245        let item = match context {
 246            ContextMetadata::Remote(context) => {
 247                let host_user = self.project.read(cx).host().and_then(|collaborator| {
 248                    self.project
 249                        .read(cx)
 250                        .user_store()
 251                        .read(cx)
 252                        .get_cached_user(collaborator.user_id)
 253                });
 254                div()
 255                    .flex()
 256                    .w_full()
 257                    .justify_between()
 258                    .gap_2()
 259                    .child(
 260                        h_flex().flex_1().overflow_x_hidden().child(
 261                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
 262                                .size(LabelSize::Small),
 263                        ),
 264                    )
 265                    .child(
 266                        h_flex()
 267                            .gap_2()
 268                            .children(if let Some(host_user) = host_user {
 269                                vec![
 270                                    Avatar::new(host_user.avatar_uri.clone())
 271                                        .shape(AvatarShape::Circle)
 272                                        .into_any_element(),
 273                                    Label::new(format!("Shared by @{}", host_user.github_login))
 274                                        .color(Color::Muted)
 275                                        .size(LabelSize::Small)
 276                                        .into_any_element(),
 277                                ]
 278                            } else {
 279                                vec![Label::new("Shared by host")
 280                                    .color(Color::Muted)
 281                                    .size(LabelSize::Small)
 282                                    .into_any_element()]
 283                            }),
 284                    )
 285            }
 286            ContextMetadata::Saved(context) => div()
 287                .flex()
 288                .w_full()
 289                .justify_between()
 290                .gap_2()
 291                .child(
 292                    h_flex()
 293                        .flex_1()
 294                        .child(Label::new(context.title.clone()).size(LabelSize::Small))
 295                        .overflow_x_hidden(),
 296                )
 297                .child(
 298                    Label::new(format_distance_from_now(
 299                        DateTimeType::Local(context.mtime),
 300                        false,
 301                        true,
 302                        true,
 303                    ))
 304                    .color(Color::Muted)
 305                    .size(LabelSize::Small),
 306                ),
 307        };
 308        Some(
 309            ListItem::new(ix)
 310                .inset(true)
 311                .spacing(ListItemSpacing::Sparse)
 312                .selected(selected)
 313                .child(item),
 314        )
 315    }
 316}
 317
 318impl AssistantPanel {
 319    pub fn load(
 320        workspace: WeakView<Workspace>,
 321        prompt_builder: Arc<PromptBuilder>,
 322        cx: AsyncWindowContext,
 323    ) -> Task<Result<View<Self>>> {
 324        cx.spawn(|mut cx| async move {
 325            let context_store = workspace
 326                .update(&mut cx, |workspace, cx| {
 327                    let project = workspace.project().clone();
 328                    ContextStore::new(project, prompt_builder.clone(), cx)
 329                })?
 330                .await?;
 331
 332            workspace.update(&mut cx, |workspace, cx| {
 333                // TODO: deserialize state.
 334                cx.new_view(|cx| Self::new(workspace, context_store, cx))
 335            })
 336        })
 337    }
 338
 339    fn new(
 340        workspace: &Workspace,
 341        context_store: Model<ContextStore>,
 342        cx: &mut ViewContext<Self>,
 343    ) -> Self {
 344        let model_selector_menu_handle = PopoverMenuHandle::default();
 345        let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
 346        let context_editor_toolbar = cx.new_view(|_| {
 347            ContextEditorToolbarItem::new(
 348                workspace,
 349                model_selector_menu_handle.clone(),
 350                model_summary_editor.clone(),
 351            )
 352        });
 353        let pane = cx.new_view(|cx| {
 354            let mut pane = Pane::new(
 355                workspace.weak_handle(),
 356                workspace.project().clone(),
 357                Default::default(),
 358                None,
 359                NewFile.boxed_clone(),
 360                cx,
 361            );
 362            pane.set_can_split(false, cx);
 363            pane.set_can_navigate(true, cx);
 364            pane.display_nav_history_buttons(None);
 365            pane.set_should_display_tab_bar(|_| true);
 366            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 367                let focus_handle = pane.focus_handle(cx);
 368                let left_children = IconButton::new("history", IconName::TextSearch)
 369                    .on_click(cx.listener({
 370                        let focus_handle = focus_handle.clone();
 371                        move |_, _, cx| {
 372                            focus_handle.focus(cx);
 373                            cx.dispatch_action(DeployHistory.boxed_clone())
 374                        }
 375                    }))
 376                    .tooltip(move |cx| {
 377                        cx.new_view(|cx| {
 378                            let keybind =
 379                                KeyBinding::for_action_in(&DeployHistory, &focus_handle, cx);
 380                            Tooltip::new("History").key_binding(keybind)
 381                        })
 382                        .into()
 383                    })
 384                    .selected(
 385                        pane.active_item()
 386                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 387                    );
 388                let right_children = h_flex()
 389                    .gap(Spacing::Small.rems(cx))
 390                    .child(
 391                        IconButton::new("new-context", IconName::Plus)
 392                            .on_click(
 393                                cx.listener(|_, _, cx| cx.dispatch_action(NewFile.boxed_clone())),
 394                            )
 395                            .tooltip(|cx| Tooltip::for_action("New Context", &NewFile, cx)),
 396                    )
 397                    .child(
 398                        IconButton::new("menu", IconName::Menu)
 399                            .icon_size(IconSize::Small)
 400                            .on_click(cx.listener(|pane, _, cx| {
 401                                let zoom_label = if pane.is_zoomed() {
 402                                    "Zoom Out"
 403                                } else {
 404                                    "Zoom In"
 405                                };
 406                                let weak_pane = cx.view().downgrade();
 407                                let menu = ContextMenu::build(cx, |menu, cx| {
 408                                    let menu = menu
 409                                        .context(pane.focus_handle(cx))
 410                                        .action("New Context", Box::new(NewFile))
 411                                        .action("History", Box::new(DeployHistory))
 412                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 413                                        .action("Configure", Box::new(ShowConfiguration))
 414                                        .action(zoom_label, Box::new(ToggleZoom));
 415
 416                                    if let Some(editor) = pane
 417                                        .active_item()
 418                                        .and_then(|e| e.downcast::<ContextEditor>())
 419                                    {
 420                                        let is_enabled = editor.read(cx).debug_inspector.is_some();
 421                                        menu.separator().toggleable_entry(
 422                                            "Debug Workflows",
 423                                            is_enabled,
 424                                            IconPosition::End,
 425                                            None,
 426                                            move |cx| {
 427                                                weak_pane
 428                                                    .update(cx, |this, cx| {
 429                                                        if let Some(context_editor) =
 430                                                            this.active_item().and_then(|item| {
 431                                                                item.downcast::<ContextEditor>()
 432                                                            })
 433                                                        {
 434                                                            context_editor.update(cx, |this, cx| {
 435                                                                if let Some(mut state) =
 436                                                                    this.debug_inspector.take()
 437                                                                {
 438                                                                    state.deactivate(cx);
 439                                                                } else {
 440                                                                    this.debug_inspector = Some(
 441                                                                        ContextInspector::new(
 442                                                                            this.editor.clone(),
 443                                                                            this.context.clone(),
 444                                                                        ),
 445                                                                    );
 446                                                                }
 447                                                            })
 448                                                        }
 449                                                    })
 450                                                    .ok();
 451                                            },
 452                                        )
 453                                    } else {
 454                                        menu
 455                                    }
 456                                });
 457                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
 458                                    pane.new_item_menu = None;
 459                                })
 460                                .detach();
 461                                pane.new_item_menu = Some(menu);
 462                            })),
 463                    )
 464                    .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
 465                        el.child(Pane::render_menu_overlay(new_item_menu))
 466                    })
 467                    .into_any_element()
 468                    .into();
 469
 470                (Some(left_children.into_any_element()), right_children)
 471            });
 472            pane.toolbar().update(cx, |toolbar, cx| {
 473                toolbar.add_item(context_editor_toolbar.clone(), cx);
 474                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 475            });
 476            pane
 477        });
 478
 479        let subscriptions = vec![
 480            cx.observe(&pane, |_, _, cx| cx.notify()),
 481            cx.subscribe(&pane, Self::handle_pane_event),
 482            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 483            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 484            cx.subscribe(&context_store, Self::handle_context_store_event),
 485            cx.subscribe(
 486                &LanguageModelRegistry::global(cx),
 487                |this, _, event: &language_model::Event, cx| match event {
 488                    language_model::Event::ActiveModelChanged => {
 489                        this.completion_provider_changed(cx);
 490                    }
 491                    language_model::Event::ProviderStateChanged => {
 492                        this.ensure_authenticated(cx);
 493                        cx.notify()
 494                    }
 495                    language_model::Event::AddedProvider(_)
 496                    | language_model::Event::RemovedProvider(_) => {
 497                        this.ensure_authenticated(cx);
 498                    }
 499                },
 500            ),
 501        ];
 502
 503        let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
 504
 505        let mut this = Self {
 506            pane,
 507            workspace: workspace.weak_handle(),
 508            width: None,
 509            height: None,
 510            project: workspace.project().clone(),
 511            context_store,
 512            languages: workspace.app_state().languages.clone(),
 513            fs: workspace.app_state().fs.clone(),
 514            subscriptions,
 515            model_selector_menu_handle,
 516            model_summary_editor,
 517            authenticate_provider_task: None,
 518            configuration_subscription: None,
 519            client_status: None,
 520            watch_client_status: Some(watch_client_status),
 521            show_zed_ai_notice: false,
 522        };
 523        this.new_context(cx);
 524        this
 525    }
 526
 527    fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
 528        let mut status_rx = client.status();
 529
 530        cx.spawn(|this, mut cx| async move {
 531            while let Some(status) = status_rx.next().await {
 532                this.update(&mut cx, |this, cx| {
 533                    if this.client_status.is_none()
 534                        || this
 535                            .client_status
 536                            .map_or(false, |old_status| old_status != status)
 537                    {
 538                        this.update_zed_ai_notice_visibility(status, cx);
 539                    }
 540                    this.client_status = Some(status);
 541                })
 542                .log_err();
 543            }
 544            this.update(&mut cx, |this, _cx| this.watch_client_status = None)
 545                .log_err();
 546        })
 547    }
 548
 549    fn handle_pane_event(
 550        &mut self,
 551        pane: View<Pane>,
 552        event: &pane::Event,
 553        cx: &mut ViewContext<Self>,
 554    ) {
 555        let update_model_summary = match event {
 556            pane::Event::Remove => {
 557                cx.emit(PanelEvent::Close);
 558                false
 559            }
 560            pane::Event::ZoomIn => {
 561                cx.emit(PanelEvent::ZoomIn);
 562                false
 563            }
 564            pane::Event::ZoomOut => {
 565                cx.emit(PanelEvent::ZoomOut);
 566                false
 567            }
 568
 569            pane::Event::AddItem { item } => {
 570                self.workspace
 571                    .update(cx, |workspace, cx| {
 572                        item.added_to_pane(workspace, self.pane.clone(), cx)
 573                    })
 574                    .ok();
 575                true
 576            }
 577
 578            pane::Event::ActivateItem { local } => {
 579                if *local {
 580                    self.workspace
 581                        .update(cx, |workspace, cx| {
 582                            workspace.unfollow_in_pane(&pane, cx);
 583                        })
 584                        .ok();
 585                }
 586                cx.emit(AssistantPanelEvent::ContextEdited);
 587                true
 588            }
 589
 590            pane::Event::RemoveItem { idx } => {
 591                if self
 592                    .pane
 593                    .read(cx)
 594                    .item_for_index(*idx)
 595                    .map_or(false, |item| item.downcast::<ConfigurationView>().is_some())
 596                {
 597                    self.configuration_subscription = None;
 598                }
 599                false
 600            }
 601            pane::Event::RemovedItem { .. } => {
 602                cx.emit(AssistantPanelEvent::ContextEdited);
 603                true
 604            }
 605
 606            _ => false,
 607        };
 608
 609        if update_model_summary {
 610            if let Some(editor) = self.active_context_editor(cx) {
 611                self.show_updated_summary(&editor, cx)
 612            }
 613        }
 614    }
 615
 616    fn handle_summary_editor_event(
 617        &mut self,
 618        model_summary_editor: View<Editor>,
 619        event: &EditorEvent,
 620        cx: &mut ViewContext<Self>,
 621    ) {
 622        if matches!(event, EditorEvent::Edited { .. }) {
 623            if let Some(context_editor) = self.active_context_editor(cx) {
 624                let new_summary = model_summary_editor.read(cx).text(cx);
 625                context_editor.update(cx, |context_editor, cx| {
 626                    context_editor.context.update(cx, |context, cx| {
 627                        if context.summary().is_none()
 628                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 629                        {
 630                            return;
 631                        }
 632                        context.custom_summary(new_summary, cx)
 633                    });
 634                });
 635            }
 636        }
 637    }
 638
 639    fn update_zed_ai_notice_visibility(
 640        &mut self,
 641        client_status: Status,
 642        cx: &mut ViewContext<Self>,
 643    ) {
 644        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
 645
 646        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 647        // the provider, we want to show a nudge to sign in.
 648        let show_zed_ai_notice = client_status.is_signed_out()
 649            && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
 650
 651        self.show_zed_ai_notice = show_zed_ai_notice;
 652        cx.notify();
 653    }
 654
 655    fn handle_toolbar_event(
 656        &mut self,
 657        _: View<ContextEditorToolbarItem>,
 658        _: &ContextEditorToolbarItemEvent,
 659        cx: &mut ViewContext<Self>,
 660    ) {
 661        if let Some(context_editor) = self.active_context_editor(cx) {
 662            context_editor.update(cx, |context_editor, cx| {
 663                context_editor.context.update(cx, |context, cx| {
 664                    context.summarize(true, cx);
 665                })
 666            })
 667        }
 668    }
 669
 670    fn handle_context_store_event(
 671        &mut self,
 672        _context_store: Model<ContextStore>,
 673        event: &ContextStoreEvent,
 674        cx: &mut ViewContext<Self>,
 675    ) {
 676        let ContextStoreEvent::ContextCreated(context_id) = event;
 677        let Some(context) = self
 678            .context_store
 679            .read(cx)
 680            .loaded_context_for_id(&context_id, cx)
 681        else {
 682            log::error!("no context found with ID: {}", context_id.to_proto());
 683            return;
 684        };
 685        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
 686
 687        let assistant_panel = cx.view().downgrade();
 688        let editor = cx.new_view(|cx| {
 689            let mut editor = ContextEditor::for_context(
 690                context,
 691                self.fs.clone(),
 692                self.workspace.clone(),
 693                self.project.clone(),
 694                lsp_adapter_delegate,
 695                assistant_panel,
 696                cx,
 697            );
 698            editor.insert_default_prompt(cx);
 699            editor
 700        });
 701
 702        self.show_context(editor.clone(), cx);
 703    }
 704
 705    fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
 706        if let Some(editor) = self.active_context_editor(cx) {
 707            editor.update(cx, |active_context, cx| {
 708                active_context
 709                    .context
 710                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 711            })
 712        }
 713
 714        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 715            .active_provider()
 716            .map(|p| p.id())
 717        else {
 718            return;
 719        };
 720
 721        if self
 722            .authenticate_provider_task
 723            .as_ref()
 724            .map_or(true, |(old_provider_id, _)| {
 725                *old_provider_id != new_provider_id
 726            })
 727        {
 728            self.authenticate_provider_task = None;
 729            self.ensure_authenticated(cx);
 730        }
 731
 732        if let Some(status) = self.client_status {
 733            self.update_zed_ai_notice_visibility(status, cx);
 734        }
 735    }
 736
 737    fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
 738        if self.is_authenticated(cx) {
 739            return;
 740        }
 741
 742        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 743            return;
 744        };
 745
 746        let load_credentials = self.authenticate(cx);
 747
 748        if self.authenticate_provider_task.is_none() {
 749            self.authenticate_provider_task = Some((
 750                provider.id(),
 751                cx.spawn(|this, mut cx| async move {
 752                    let _ = load_credentials.await;
 753                    this.update(&mut cx, |this, _cx| {
 754                        this.authenticate_provider_task = None;
 755                    })
 756                    .log_err();
 757                }),
 758            ));
 759        }
 760    }
 761
 762    pub fn inline_assist(
 763        workspace: &mut Workspace,
 764        action: &InlineAssist,
 765        cx: &mut ViewContext<Workspace>,
 766    ) {
 767        let settings = AssistantSettings::get_global(cx);
 768        if !settings.enabled {
 769            return;
 770        }
 771
 772        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 773            return;
 774        };
 775
 776        let Some(inline_assist_target) =
 777            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 778        else {
 779            return;
 780        };
 781
 782        let initial_prompt = action.prompt.clone();
 783        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 784            match inline_assist_target {
 785                InlineAssistTarget::Editor(active_editor, include_context) => {
 786                    InlineAssistant::update_global(cx, |assistant, cx| {
 787                        assistant.assist(
 788                            &active_editor,
 789                            Some(cx.view().downgrade()),
 790                            include_context.then_some(&assistant_panel),
 791                            initial_prompt,
 792                            cx,
 793                        )
 794                    })
 795                }
 796                InlineAssistTarget::Terminal(active_terminal) => {
 797                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 798                        assistant.assist(
 799                            &active_terminal,
 800                            Some(cx.view().downgrade()),
 801                            Some(&assistant_panel),
 802                            initial_prompt,
 803                            cx,
 804                        )
 805                    })
 806                }
 807            }
 808        } else {
 809            let assistant_panel = assistant_panel.downgrade();
 810            cx.spawn(|workspace, mut cx| async move {
 811                assistant_panel
 812                    .update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 813                    .await?;
 814                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 815                    cx.update(|cx| match inline_assist_target {
 816                        InlineAssistTarget::Editor(active_editor, include_context) => {
 817                            let assistant_panel = if include_context {
 818                                assistant_panel.upgrade()
 819                            } else {
 820                                None
 821                            };
 822                            InlineAssistant::update_global(cx, |assistant, cx| {
 823                                assistant.assist(
 824                                    &active_editor,
 825                                    Some(workspace),
 826                                    assistant_panel.as_ref(),
 827                                    initial_prompt,
 828                                    cx,
 829                                )
 830                            })
 831                        }
 832                        InlineAssistTarget::Terminal(active_terminal) => {
 833                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 834                                assistant.assist(
 835                                    &active_terminal,
 836                                    Some(workspace),
 837                                    assistant_panel.upgrade().as_ref(),
 838                                    initial_prompt,
 839                                    cx,
 840                                )
 841                            })
 842                        }
 843                    })?
 844                } else {
 845                    workspace.update(&mut cx, |workspace, cx| {
 846                        workspace.focus_panel::<AssistantPanel>(cx)
 847                    })?;
 848                }
 849
 850                anyhow::Ok(())
 851            })
 852            .detach_and_log_err(cx)
 853        }
 854    }
 855
 856    fn resolve_inline_assist_target(
 857        workspace: &mut Workspace,
 858        assistant_panel: &View<AssistantPanel>,
 859        cx: &mut WindowContext,
 860    ) -> Option<InlineAssistTarget> {
 861        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 862            if terminal_panel
 863                .read(cx)
 864                .focus_handle(cx)
 865                .contains_focused(cx)
 866            {
 867                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 868                    pane.read(cx)
 869                        .active_item()
 870                        .and_then(|t| t.downcast::<TerminalView>())
 871                }) {
 872                    return Some(InlineAssistTarget::Terminal(terminal_view));
 873                }
 874            }
 875        }
 876        let context_editor =
 877            assistant_panel
 878                .read(cx)
 879                .active_context_editor(cx)
 880                .and_then(|editor| {
 881                    let editor = &editor.read(cx).editor;
 882                    if editor.read(cx).is_focused(cx) {
 883                        Some(editor.clone())
 884                    } else {
 885                        None
 886                    }
 887                });
 888
 889        if let Some(context_editor) = context_editor {
 890            Some(InlineAssistTarget::Editor(context_editor, false))
 891        } else if let Some(workspace_editor) = workspace
 892            .active_item(cx)
 893            .and_then(|item| item.act_as::<Editor>(cx))
 894        {
 895            Some(InlineAssistTarget::Editor(workspace_editor, true))
 896        } else if let Some(terminal_view) = workspace
 897            .active_item(cx)
 898            .and_then(|item| item.act_as::<TerminalView>(cx))
 899        {
 900            Some(InlineAssistTarget::Terminal(terminal_view))
 901        } else {
 902            None
 903        }
 904    }
 905
 906    fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 907        if self.project.read(cx).is_remote() {
 908            let task = self
 909                .context_store
 910                .update(cx, |store, cx| store.create_remote_context(cx));
 911
 912            cx.spawn(|this, mut cx| async move {
 913                let context = task.await?;
 914
 915                this.update(&mut cx, |this, cx| {
 916                    let workspace = this.workspace.clone();
 917                    let project = this.project.clone();
 918                    let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
 919
 920                    let fs = this.fs.clone();
 921                    let project = this.project.clone();
 922                    let weak_assistant_panel = cx.view().downgrade();
 923
 924                    let editor = cx.new_view(|cx| {
 925                        ContextEditor::for_context(
 926                            context,
 927                            fs,
 928                            workspace,
 929                            project,
 930                            lsp_adapter_delegate,
 931                            weak_assistant_panel,
 932                            cx,
 933                        )
 934                    });
 935
 936                    this.show_context(editor, cx);
 937
 938                    anyhow::Ok(())
 939                })??;
 940
 941                anyhow::Ok(())
 942            })
 943            .detach_and_log_err(cx);
 944
 945            None
 946        } else {
 947            let context = self.context_store.update(cx, |store, cx| store.create(cx));
 948            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
 949
 950            let assistant_panel = cx.view().downgrade();
 951            let editor = cx.new_view(|cx| {
 952                let mut editor = ContextEditor::for_context(
 953                    context,
 954                    self.fs.clone(),
 955                    self.workspace.clone(),
 956                    self.project.clone(),
 957                    lsp_adapter_delegate,
 958                    assistant_panel,
 959                    cx,
 960                );
 961                editor.insert_default_prompt(cx);
 962                editor
 963            });
 964
 965            self.show_context(editor.clone(), cx);
 966            Some(editor)
 967        }
 968    }
 969
 970    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
 971        let focus = self.focus_handle(cx).contains_focused(cx);
 972        let prev_len = self.pane.read(cx).items_len();
 973        self.pane.update(cx, |pane, cx| {
 974            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
 975        });
 976
 977        if prev_len != self.pane.read(cx).items_len() {
 978            self.subscriptions
 979                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
 980        }
 981
 982        self.show_updated_summary(&context_editor, cx);
 983
 984        cx.emit(AssistantPanelEvent::ContextEdited);
 985        cx.notify();
 986    }
 987
 988    fn show_updated_summary(
 989        &self,
 990        context_editor: &View<ContextEditor>,
 991        cx: &mut ViewContext<Self>,
 992    ) {
 993        context_editor.update(cx, |context_editor, cx| {
 994            let new_summary = context_editor.title(cx).to_string();
 995            self.model_summary_editor.update(cx, |summary_editor, cx| {
 996                if summary_editor.text(cx) != new_summary {
 997                    summary_editor.set_text(new_summary, cx);
 998                }
 999            });
1000        });
1001    }
1002
1003    fn handle_context_editor_event(
1004        &mut self,
1005        context_editor: View<ContextEditor>,
1006        event: &EditorEvent,
1007        cx: &mut ViewContext<Self>,
1008    ) {
1009        match event {
1010            EditorEvent::TitleChanged => {
1011                self.show_updated_summary(&context_editor, cx);
1012                cx.notify()
1013            }
1014            EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
1015            _ => {}
1016        }
1017    }
1018
1019    fn show_configuration(
1020        workspace: &mut Workspace,
1021        _: &ShowConfiguration,
1022        cx: &mut ViewContext<Workspace>,
1023    ) {
1024        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1025            return;
1026        };
1027
1028        if !panel.focus_handle(cx).contains_focused(cx) {
1029            workspace.toggle_panel_focus::<AssistantPanel>(cx);
1030        }
1031
1032        panel.update(cx, |this, cx| {
1033            this.show_configuration_tab(cx);
1034        })
1035    }
1036
1037    fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
1038        let configuration_item_ix = self
1039            .pane
1040            .read(cx)
1041            .items()
1042            .position(|item| item.downcast::<ConfigurationView>().is_some());
1043
1044        if let Some(configuration_item_ix) = configuration_item_ix {
1045            self.pane.update(cx, |pane, cx| {
1046                pane.activate_item(configuration_item_ix, true, true, cx);
1047            });
1048        } else {
1049            let configuration = cx.new_view(|cx| ConfigurationView::new(cx));
1050            self.configuration_subscription = Some(cx.subscribe(
1051                &configuration,
1052                |this, _, event: &ConfigurationViewEvent, cx| match event {
1053                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
1054                        if LanguageModelRegistry::read_global(cx)
1055                            .active_provider()
1056                            .map_or(true, |p| p.id() != provider.id())
1057                        {
1058                            if let Some(model) = provider.provided_models(cx).first().cloned() {
1059                                update_settings_file::<AssistantSettings>(
1060                                    this.fs.clone(),
1061                                    cx,
1062                                    move |settings, _| settings.set_model(model),
1063                                );
1064                            }
1065                        }
1066
1067                        this.new_context(cx);
1068                    }
1069                },
1070            ));
1071            self.pane.update(cx, |pane, cx| {
1072                pane.add_item(Box::new(configuration), true, true, None, cx);
1073            });
1074        }
1075    }
1076
1077    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
1078        let history_item_ix = self
1079            .pane
1080            .read(cx)
1081            .items()
1082            .position(|item| item.downcast::<ContextHistory>().is_some());
1083
1084        if let Some(history_item_ix) = history_item_ix {
1085            self.pane.update(cx, |pane, cx| {
1086                pane.activate_item(history_item_ix, true, true, cx);
1087            });
1088        } else {
1089            let assistant_panel = cx.view().downgrade();
1090            let history = cx.new_view(|cx| {
1091                ContextHistory::new(
1092                    self.project.clone(),
1093                    self.context_store.clone(),
1094                    assistant_panel,
1095                    cx,
1096                )
1097            });
1098            self.pane.update(cx, |pane, cx| {
1099                pane.add_item(Box::new(history), true, true, None, cx);
1100            });
1101        }
1102    }
1103
1104    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
1105        open_prompt_library(self.languages.clone(), cx).detach_and_log_err(cx);
1106    }
1107
1108    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1109        self.model_selector_menu_handle.toggle(cx);
1110    }
1111
1112    fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1113        self.pane
1114            .read(cx)
1115            .active_item()?
1116            .downcast::<ContextEditor>()
1117    }
1118
1119    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1120        Some(self.active_context_editor(cx)?.read(cx).context.clone())
1121    }
1122
1123    fn open_saved_context(
1124        &mut self,
1125        path: PathBuf,
1126        cx: &mut ViewContext<Self>,
1127    ) -> Task<Result<()>> {
1128        let existing_context = self.pane.read(cx).items().find_map(|item| {
1129            item.downcast::<ContextEditor>()
1130                .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1131        });
1132        if let Some(existing_context) = existing_context {
1133            return cx.spawn(|this, mut cx| async move {
1134                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1135            });
1136        }
1137
1138        let context = self
1139            .context_store
1140            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1141        let fs = self.fs.clone();
1142        let project = self.project.clone();
1143        let workspace = self.workspace.clone();
1144
1145        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
1146
1147        cx.spawn(|this, mut cx| async move {
1148            let context = context.await?;
1149            let assistant_panel = this.clone();
1150            this.update(&mut cx, |this, cx| {
1151                let editor = cx.new_view(|cx| {
1152                    ContextEditor::for_context(
1153                        context,
1154                        fs,
1155                        workspace,
1156                        project,
1157                        lsp_adapter_delegate,
1158                        assistant_panel,
1159                        cx,
1160                    )
1161                });
1162                this.show_context(editor, cx);
1163                anyhow::Ok(())
1164            })??;
1165            Ok(())
1166        })
1167    }
1168
1169    fn open_remote_context(
1170        &mut self,
1171        id: ContextId,
1172        cx: &mut ViewContext<Self>,
1173    ) -> Task<Result<View<ContextEditor>>> {
1174        let existing_context = self.pane.read(cx).items().find_map(|item| {
1175            item.downcast::<ContextEditor>()
1176                .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1177        });
1178        if let Some(existing_context) = existing_context {
1179            return cx.spawn(|this, mut cx| async move {
1180                this.update(&mut cx, |this, cx| {
1181                    this.show_context(existing_context.clone(), cx)
1182                })?;
1183                Ok(existing_context)
1184            });
1185        }
1186
1187        let context = self
1188            .context_store
1189            .update(cx, |store, cx| store.open_remote_context(id, cx));
1190        let fs = self.fs.clone();
1191        let workspace = self.workspace.clone();
1192        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
1193
1194        cx.spawn(|this, mut cx| async move {
1195            let context = context.await?;
1196            let assistant_panel = this.clone();
1197            this.update(&mut cx, |this, cx| {
1198                let editor = cx.new_view(|cx| {
1199                    ContextEditor::for_context(
1200                        context,
1201                        fs,
1202                        workspace,
1203                        this.project.clone(),
1204                        lsp_adapter_delegate,
1205                        assistant_panel,
1206                        cx,
1207                    )
1208                });
1209                this.show_context(editor.clone(), cx);
1210                anyhow::Ok(editor)
1211            })?
1212        })
1213    }
1214
1215    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1216        LanguageModelRegistry::read_global(cx)
1217            .active_provider()
1218            .map_or(false, |provider| provider.is_authenticated(cx))
1219    }
1220
1221    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
1222        LanguageModelRegistry::read_global(cx)
1223            .active_provider()
1224            .map_or(
1225                Task::ready(Err(anyhow!("no active language model provider"))),
1226                |provider| provider.authenticate(cx),
1227            )
1228    }
1229}
1230
1231impl Render for AssistantPanel {
1232    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1233        let mut registrar = DivRegistrar::new(
1234            |panel, cx| {
1235                panel
1236                    .pane
1237                    .read(cx)
1238                    .toolbar()
1239                    .read(cx)
1240                    .item_of_type::<BufferSearchBar>()
1241            },
1242            cx,
1243        );
1244        BufferSearchBar::register(&mut registrar);
1245        let registrar = registrar.into_div();
1246
1247        v_flex()
1248            .key_context("AssistantPanel")
1249            .size_full()
1250            .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
1251                this.new_context(cx);
1252            }))
1253            .on_action(
1254                cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1255            )
1256            .on_action(cx.listener(AssistantPanel::deploy_history))
1257            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1258            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1259            .child(registrar.size_full().child(self.pane.clone()))
1260            .into_any_element()
1261    }
1262}
1263
1264impl Panel for AssistantPanel {
1265    fn persistent_name() -> &'static str {
1266        "AssistantPanel"
1267    }
1268
1269    fn position(&self, cx: &WindowContext) -> DockPosition {
1270        match AssistantSettings::get_global(cx).dock {
1271            AssistantDockPosition::Left => DockPosition::Left,
1272            AssistantDockPosition::Bottom => DockPosition::Bottom,
1273            AssistantDockPosition::Right => DockPosition::Right,
1274        }
1275    }
1276
1277    fn position_is_valid(&self, _: DockPosition) -> bool {
1278        true
1279    }
1280
1281    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1282        settings::update_settings_file::<AssistantSettings>(
1283            self.fs.clone(),
1284            cx,
1285            move |settings, _| {
1286                let dock = match position {
1287                    DockPosition::Left => AssistantDockPosition::Left,
1288                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1289                    DockPosition::Right => AssistantDockPosition::Right,
1290                };
1291                settings.set_dock(dock);
1292            },
1293        );
1294    }
1295
1296    fn size(&self, cx: &WindowContext) -> Pixels {
1297        let settings = AssistantSettings::get_global(cx);
1298        match self.position(cx) {
1299            DockPosition::Left | DockPosition::Right => {
1300                self.width.unwrap_or(settings.default_width)
1301            }
1302            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1303        }
1304    }
1305
1306    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1307        match self.position(cx) {
1308            DockPosition::Left | DockPosition::Right => self.width = size,
1309            DockPosition::Bottom => self.height = size,
1310        }
1311        cx.notify();
1312    }
1313
1314    fn is_zoomed(&self, cx: &WindowContext) -> bool {
1315        self.pane.read(cx).is_zoomed()
1316    }
1317
1318    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1319        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1320    }
1321
1322    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1323        if active {
1324            if self.pane.read(cx).items_len() == 0 {
1325                self.new_context(cx);
1326            }
1327
1328            self.ensure_authenticated(cx);
1329        }
1330    }
1331
1332    fn pane(&self) -> Option<View<Pane>> {
1333        Some(self.pane.clone())
1334    }
1335
1336    fn remote_id() -> Option<proto::PanelId> {
1337        Some(proto::PanelId::AssistantPanel)
1338    }
1339
1340    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1341        let settings = AssistantSettings::get_global(cx);
1342        if !settings.enabled || !settings.button {
1343            return None;
1344        }
1345
1346        Some(IconName::ZedAssistant)
1347    }
1348
1349    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1350        Some("Assistant Panel")
1351    }
1352
1353    fn toggle_action(&self) -> Box<dyn Action> {
1354        Box::new(ToggleFocus)
1355    }
1356}
1357
1358impl EventEmitter<PanelEvent> for AssistantPanel {}
1359impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1360
1361impl FocusableView for AssistantPanel {
1362    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1363        self.pane.focus_handle(cx)
1364    }
1365}
1366
1367pub enum ContextEditorEvent {
1368    Edited,
1369    TabContentChanged,
1370}
1371
1372#[derive(Copy, Clone, Debug, PartialEq)]
1373struct ScrollPosition {
1374    offset_before_cursor: gpui::Point<f32>,
1375    cursor: Anchor,
1376}
1377
1378struct WorkflowStep {
1379    range: Range<language::Anchor>,
1380    header_block_id: CustomBlockId,
1381    footer_block_id: CustomBlockId,
1382    resolved_step: Option<Result<ResolvedWorkflowStep, Arc<anyhow::Error>>>,
1383    assist: Option<WorkflowAssist>,
1384}
1385
1386impl WorkflowStep {
1387    fn status(&self, cx: &AppContext) -> WorkflowStepStatus {
1388        match self.resolved_step.as_ref() {
1389            Some(Ok(_)) => {
1390                if let Some(assist) = self.assist.as_ref() {
1391                    let assistant = InlineAssistant::global(cx);
1392                    if assist
1393                        .assist_ids
1394                        .iter()
1395                        .any(|assist_id| assistant.assist_status(*assist_id, cx).is_pending())
1396                    {
1397                        WorkflowStepStatus::Pending
1398                    } else if assist
1399                        .assist_ids
1400                        .iter()
1401                        .all(|assist_id| assistant.assist_status(*assist_id, cx).is_confirmed())
1402                    {
1403                        WorkflowStepStatus::Confirmed
1404                    } else if assist
1405                        .assist_ids
1406                        .iter()
1407                        .all(|assist_id| assistant.assist_status(*assist_id, cx).is_done())
1408                    {
1409                        WorkflowStepStatus::Done
1410                    } else {
1411                        WorkflowStepStatus::Idle
1412                    }
1413                } else {
1414                    WorkflowStepStatus::Idle
1415                }
1416            }
1417            Some(Err(error)) => WorkflowStepStatus::Error(error.clone()),
1418            None => WorkflowStepStatus::Resolving,
1419        }
1420    }
1421}
1422
1423enum WorkflowStepStatus {
1424    Resolving,
1425    Error(Arc<anyhow::Error>),
1426    Idle,
1427    Pending,
1428    Done,
1429    Confirmed,
1430}
1431
1432impl WorkflowStepStatus {
1433    pub(crate) fn is_confirmed(&self) -> bool {
1434        matches!(self, Self::Confirmed)
1435    }
1436
1437    pub(crate) fn into_element(
1438        &self,
1439        step_range: Range<language::Anchor>,
1440        focus_handle: FocusHandle,
1441        editor: WeakView<ContextEditor>,
1442        cx: &mut BlockContext<'_, '_>,
1443    ) -> AnyElement {
1444        let id = EntityId::from(cx.block_id);
1445        fn display_keybind_in_tooltip(
1446            step_range: &Range<language::Anchor>,
1447            editor: &WeakView<ContextEditor>,
1448            cx: &mut WindowContext<'_>,
1449        ) -> bool {
1450            editor
1451                .update(cx, |this, _| {
1452                    this.active_workflow_step
1453                        .as_ref()
1454                        .map(|step| &step.range == step_range)
1455                })
1456                .ok()
1457                .flatten()
1458                .unwrap_or_default()
1459        }
1460        match self {
1461            WorkflowStepStatus::Resolving => Icon::new(IconName::ArrowCircle)
1462                .size(IconSize::Small)
1463                .with_animation(
1464                    ("resolving-suggestion-label", id),
1465                    Animation::new(Duration::from_secs(2)).repeat(),
1466                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
1467                )
1468                .into_any_element(),
1469
1470            WorkflowStepStatus::Error(error) => {
1471                let error = error.clone();
1472                h_flex()
1473                    .gap_2()
1474                    .child(
1475                        div()
1476                            .id("step-resolution-failure")
1477                            .child(
1478                                Label::new("Step Resolution Failed")
1479                                    .size(LabelSize::Small)
1480                                    .color(Color::Error),
1481                            )
1482                            .tooltip(move |cx| Tooltip::text(error.to_string(), cx)),
1483                    )
1484                    .child(
1485                        Button::new(("transform", id), "Retry")
1486                            .icon(IconName::Update)
1487                            .icon_position(IconPosition::Start)
1488                            .icon_size(IconSize::Small)
1489                            .label_size(LabelSize::Small)
1490                            .on_click({
1491                                let editor = editor.clone();
1492                                let step_range = step_range.clone();
1493                                move |_, cx| {
1494                                    editor
1495                                        .update(cx, |this, cx| {
1496                                            this.resolve_workflow_step(step_range.clone(), cx)
1497                                        })
1498                                        .ok();
1499                                }
1500                            }),
1501                    )
1502                    .into_any()
1503            }
1504
1505            WorkflowStepStatus::Idle => Button::new(("transform", id), "Transform")
1506                .icon(IconName::Sparkle)
1507                .icon_position(IconPosition::Start)
1508                .icon_size(IconSize::Small)
1509                .label_size(LabelSize::Small)
1510                .style(ButtonStyle::Tinted(TintColor::Accent))
1511                .tooltip({
1512                    let step_range = step_range.clone();
1513                    let editor = editor.clone();
1514                    move |cx| {
1515                        cx.new_view(|cx| {
1516                            let tooltip = Tooltip::new("Transform");
1517                            if display_keybind_in_tooltip(&step_range, &editor, cx) {
1518                                tooltip.key_binding(KeyBinding::for_action_in(
1519                                    &Assist,
1520                                    &focus_handle,
1521                                    cx,
1522                                ))
1523                            } else {
1524                                tooltip
1525                            }
1526                        })
1527                        .into()
1528                    }
1529                })
1530                .on_click({
1531                    let editor = editor.clone();
1532                    let step_range = step_range.clone();
1533                    move |_, cx| {
1534                        editor
1535                            .update(cx, |this, cx| {
1536                                this.apply_workflow_step(step_range.clone(), cx)
1537                            })
1538                            .ok();
1539                    }
1540                })
1541                .into_any_element(),
1542            WorkflowStepStatus::Pending => Button::new(("stop-transformation", id), "Stop")
1543                .icon(IconName::Stop)
1544                .icon_position(IconPosition::Start)
1545                .icon_size(IconSize::Small)
1546                .label_size(LabelSize::Small)
1547                .style(ButtonStyle::Tinted(TintColor::Negative))
1548                .tooltip({
1549                    let step_range = step_range.clone();
1550                    let editor = editor.clone();
1551                    move |cx| {
1552                        cx.new_view(|cx| {
1553                            let tooltip = Tooltip::new("Stop Transformation");
1554                            if display_keybind_in_tooltip(&step_range, &editor, cx) {
1555                                tooltip.key_binding(KeyBinding::for_action_in(
1556                                    &editor::actions::Cancel,
1557                                    &focus_handle,
1558                                    cx,
1559                                ))
1560                            } else {
1561                                tooltip
1562                            }
1563                        })
1564                        .into()
1565                    }
1566                })
1567                .on_click({
1568                    let editor = editor.clone();
1569                    let step_range = step_range.clone();
1570                    move |_, cx| {
1571                        editor
1572                            .update(cx, |this, cx| {
1573                                this.stop_workflow_step(step_range.clone(), cx)
1574                            })
1575                            .ok();
1576                    }
1577                })
1578                .into_any_element(),
1579            WorkflowStepStatus::Done => h_flex()
1580                .gap_1()
1581                .child(
1582                    Button::new(("stop-transformation", id), "Reject")
1583                        .icon(IconName::Close)
1584                        .icon_position(IconPosition::Start)
1585                        .icon_size(IconSize::Small)
1586                        .label_size(LabelSize::Small)
1587                        .style(ButtonStyle::Tinted(TintColor::Negative))
1588                        .tooltip({
1589                            let focus_handle = focus_handle.clone();
1590                            let editor = editor.clone();
1591                            let step_range = step_range.clone();
1592                            move |cx| {
1593                                cx.new_view(|cx| {
1594                                    let tooltip = Tooltip::new("Reject Transformation");
1595                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
1596                                        tooltip.key_binding(KeyBinding::for_action_in(
1597                                            &editor::actions::Cancel,
1598                                            &focus_handle,
1599                                            cx,
1600                                        ))
1601                                    } else {
1602                                        tooltip
1603                                    }
1604                                })
1605                                .into()
1606                            }
1607                        })
1608                        .on_click({
1609                            let editor = editor.clone();
1610                            let step_range = step_range.clone();
1611                            move |_, cx| {
1612                                editor
1613                                    .update(cx, |this, cx| {
1614                                        this.reject_workflow_step(step_range.clone(), cx);
1615                                    })
1616                                    .ok();
1617                            }
1618                        }),
1619                )
1620                .child(
1621                    Button::new(("confirm-workflow-step", id), "Accept")
1622                        .icon(IconName::Check)
1623                        .icon_position(IconPosition::Start)
1624                        .icon_size(IconSize::Small)
1625                        .label_size(LabelSize::Small)
1626                        .style(ButtonStyle::Tinted(TintColor::Positive))
1627                        .tooltip({
1628                            let editor = editor.clone();
1629                            let step_range = step_range.clone();
1630                            move |cx| {
1631                                cx.new_view(|cx| {
1632                                    let tooltip = Tooltip::new("Accept Transformation");
1633                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
1634                                        tooltip.key_binding(KeyBinding::for_action_in(
1635                                            &Assist,
1636                                            &focus_handle,
1637                                            cx,
1638                                        ))
1639                                    } else {
1640                                        tooltip
1641                                    }
1642                                })
1643                                .into()
1644                            }
1645                        })
1646                        .on_click({
1647                            let editor = editor.clone();
1648                            let step_range = step_range.clone();
1649                            move |_, cx| {
1650                                editor
1651                                    .update(cx, |this, cx| {
1652                                        this.confirm_workflow_step(step_range.clone(), cx);
1653                                    })
1654                                    .ok();
1655                            }
1656                        }),
1657                )
1658                .into_any_element(),
1659            WorkflowStepStatus::Confirmed => h_flex()
1660                .child(
1661                    Button::new(("revert-workflow-step", id), "Undo")
1662                        .style(ButtonStyle::Filled)
1663                        .icon(Some(IconName::Undo))
1664                        .icon_position(IconPosition::Start)
1665                        .icon_size(IconSize::Small)
1666                        .label_size(LabelSize::Small)
1667                        .tooltip(|cx| Tooltip::text("Undo Transformation", cx))
1668                        .on_click({
1669                            let editor = editor.clone();
1670                            let step_range = step_range.clone();
1671                            move |_, cx| {
1672                                editor
1673                                    .update(cx, |this, cx| {
1674                                        this.undo_workflow_step(step_range.clone(), cx);
1675                                    })
1676                                    .ok();
1677                            }
1678                        }),
1679                )
1680                .into_any_element(),
1681        }
1682    }
1683}
1684
1685#[derive(Debug, Eq, PartialEq)]
1686struct ActiveWorkflowStep {
1687    range: Range<language::Anchor>,
1688    resolved: bool,
1689}
1690
1691struct WorkflowAssist {
1692    editor: WeakView<Editor>,
1693    editor_was_open: bool,
1694    assist_ids: Vec<InlineAssistId>,
1695    _observe_assist_status: Task<()>,
1696}
1697
1698pub struct ContextEditor {
1699    context: Model<Context>,
1700    fs: Arc<dyn Fs>,
1701    workspace: WeakView<Workspace>,
1702    project: Model<Project>,
1703    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1704    editor: View<Editor>,
1705    blocks: HashSet<CustomBlockId>,
1706    scroll_position: Option<ScrollPosition>,
1707    remote_id: Option<workspace::ViewId>,
1708    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1709    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1710    _subscriptions: Vec<Subscription>,
1711    workflow_steps: HashMap<Range<language::Anchor>, WorkflowStep>,
1712    active_workflow_step: Option<ActiveWorkflowStep>,
1713    assistant_panel: WeakView<AssistantPanel>,
1714    error_message: Option<SharedString>,
1715    debug_inspector: Option<ContextInspector>,
1716    show_accept_terms: bool,
1717}
1718
1719const DEFAULT_TAB_TITLE: &str = "New Context";
1720const MAX_TAB_TITLE_LEN: usize = 16;
1721
1722impl ContextEditor {
1723    fn for_context(
1724        context: Model<Context>,
1725        fs: Arc<dyn Fs>,
1726        workspace: WeakView<Workspace>,
1727        project: Model<Project>,
1728        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1729        assistant_panel: WeakView<AssistantPanel>,
1730        cx: &mut ViewContext<Self>,
1731    ) -> Self {
1732        let completion_provider = SlashCommandCompletionProvider::new(
1733            Some(cx.view().downgrade()),
1734            Some(workspace.clone()),
1735        );
1736
1737        let editor = cx.new_view(|cx| {
1738            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1739            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1740            editor.set_show_line_numbers(false, cx);
1741            editor.set_show_git_diff_gutter(false, cx);
1742            editor.set_show_code_actions(false, cx);
1743            editor.set_show_runnables(false, cx);
1744            editor.set_show_wrap_guides(false, cx);
1745            editor.set_show_indent_guides(false, cx);
1746            editor.set_completion_provider(Box::new(completion_provider));
1747            editor.set_collaboration_hub(Box::new(project.clone()));
1748            editor
1749        });
1750
1751        let _subscriptions = vec![
1752            cx.observe(&context, |_, _, cx| cx.notify()),
1753            cx.subscribe(&context, Self::handle_context_event),
1754            cx.subscribe(&editor, Self::handle_editor_event),
1755            cx.subscribe(&editor, Self::handle_editor_search_event),
1756        ];
1757
1758        let sections = context.read(cx).slash_command_output_sections().to_vec();
1759        let mut this = Self {
1760            context,
1761            editor,
1762            lsp_adapter_delegate,
1763            blocks: Default::default(),
1764            scroll_position: None,
1765            remote_id: None,
1766            fs,
1767            workspace,
1768            project,
1769            pending_slash_command_creases: HashMap::default(),
1770            pending_slash_command_blocks: HashMap::default(),
1771            _subscriptions,
1772            workflow_steps: HashMap::default(),
1773            active_workflow_step: None,
1774            assistant_panel,
1775            error_message: None,
1776            debug_inspector: None,
1777            show_accept_terms: false,
1778        };
1779        this.update_message_headers(cx);
1780        this.insert_slash_command_output_sections(sections, cx);
1781        this
1782    }
1783
1784    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1785        let command_name = DefaultSlashCommand.name();
1786        self.editor.update(cx, |editor, cx| {
1787            editor.insert(&format!("/{command_name}"), cx)
1788        });
1789        self.split(&Split, cx);
1790        let command = self.context.update(cx, |context, cx| {
1791            let first_message_id = context.messages(cx).next().unwrap().id;
1792            context.update_metadata(first_message_id, cx, |metadata| {
1793                metadata.role = Role::System;
1794            });
1795            context.reparse_slash_commands(cx);
1796            context.pending_slash_commands()[0].clone()
1797        });
1798
1799        self.run_command(
1800            command.source_range,
1801            &command.name,
1802            command.argument.as_deref(),
1803            false,
1804            self.workspace.clone(),
1805            cx,
1806        );
1807    }
1808
1809    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1810        let provider = LanguageModelRegistry::read_global(cx).active_provider();
1811        if provider
1812            .as_ref()
1813            .map_or(false, |provider| provider.must_accept_terms(cx))
1814        {
1815            self.show_accept_terms = true;
1816            cx.notify();
1817            return;
1818        }
1819
1820        if !self.apply_active_workflow_step(cx) {
1821            self.error_message = None;
1822            self.send_to_model(cx);
1823            cx.notify();
1824        }
1825    }
1826
1827    fn apply_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1828        self.show_workflow_step(range.clone(), cx);
1829
1830        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1831            if let Some(assist) = workflow_step.assist.as_ref() {
1832                let assist_ids = assist.assist_ids.clone();
1833                cx.window_context().defer(|cx| {
1834                    InlineAssistant::update_global(cx, |assistant, cx| {
1835                        for assist_id in assist_ids {
1836                            assistant.start_assist(assist_id, cx);
1837                        }
1838                    })
1839                });
1840            }
1841        }
1842    }
1843
1844    fn apply_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
1845        let Some(step) = self.active_workflow_step() else {
1846            return false;
1847        };
1848
1849        let range = step.range.clone();
1850        match step.status(cx) {
1851            WorkflowStepStatus::Resolving | WorkflowStepStatus::Pending => true,
1852            WorkflowStepStatus::Idle => {
1853                self.apply_workflow_step(range, cx);
1854                true
1855            }
1856            WorkflowStepStatus::Done => {
1857                self.confirm_workflow_step(range, cx);
1858                true
1859            }
1860            WorkflowStepStatus::Error(_) => {
1861                self.resolve_workflow_step(range, cx);
1862                true
1863            }
1864            WorkflowStepStatus::Confirmed => false,
1865        }
1866    }
1867
1868    fn resolve_workflow_step(
1869        &mut self,
1870        range: Range<language::Anchor>,
1871        cx: &mut ViewContext<Self>,
1872    ) {
1873        self.context.update(cx, |context, cx| {
1874            context.resolve_workflow_step(range, self.project.clone(), cx)
1875        });
1876    }
1877
1878    fn stop_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1879        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1880            if let Some(assist) = workflow_step.assist.as_ref() {
1881                let assist_ids = assist.assist_ids.clone();
1882                cx.window_context().defer(|cx| {
1883                    InlineAssistant::update_global(cx, |assistant, cx| {
1884                        for assist_id in assist_ids {
1885                            assistant.stop_assist(assist_id, cx);
1886                        }
1887                    })
1888                });
1889            }
1890        }
1891    }
1892
1893    fn undo_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1894        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1895            if let Some(assist) = workflow_step.assist.take() {
1896                cx.window_context().defer(|cx| {
1897                    InlineAssistant::update_global(cx, |assistant, cx| {
1898                        for assist_id in assist.assist_ids {
1899                            assistant.undo_assist(assist_id, cx);
1900                        }
1901                    })
1902                });
1903            }
1904        }
1905    }
1906
1907    fn confirm_workflow_step(
1908        &mut self,
1909        range: Range<language::Anchor>,
1910        cx: &mut ViewContext<Self>,
1911    ) {
1912        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1913            if let Some(assist) = workflow_step.assist.as_ref() {
1914                let assist_ids = assist.assist_ids.clone();
1915                cx.window_context().defer(move |cx| {
1916                    InlineAssistant::update_global(cx, |assistant, cx| {
1917                        for assist_id in assist_ids {
1918                            assistant.finish_assist(assist_id, false, cx);
1919                        }
1920                    })
1921                });
1922            }
1923        }
1924    }
1925
1926    fn reject_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1927        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1928            if let Some(assist) = workflow_step.assist.take() {
1929                cx.window_context().defer(move |cx| {
1930                    InlineAssistant::update_global(cx, |assistant, cx| {
1931                        for assist_id in assist.assist_ids {
1932                            assistant.finish_assist(assist_id, true, cx);
1933                        }
1934                    })
1935                });
1936            }
1937        }
1938    }
1939
1940    fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
1941        if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
1942            let new_selection = {
1943                let cursor = user_message
1944                    .start
1945                    .to_offset(self.context.read(cx).buffer().read(cx));
1946                cursor..cursor
1947            };
1948            self.editor.update(cx, |editor, cx| {
1949                editor.change_selections(
1950                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1951                    cx,
1952                    |selections| selections.select_ranges([new_selection]),
1953                );
1954            });
1955            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1956            cx.defer(|this, _| this.scroll_position = None);
1957        }
1958    }
1959
1960    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1961        if self
1962            .context
1963            .update(cx, |context, _| context.cancel_last_assist())
1964        {
1965            return;
1966        }
1967
1968        if let Some(active_step) = self.active_workflow_step() {
1969            match active_step.status(cx) {
1970                WorkflowStepStatus::Pending => {
1971                    self.stop_workflow_step(active_step.range.clone(), cx);
1972                    return;
1973                }
1974                WorkflowStepStatus::Done => {
1975                    self.reject_workflow_step(active_step.range.clone(), cx);
1976                    return;
1977                }
1978                _ => {}
1979            }
1980        }
1981        cx.propagate();
1982    }
1983
1984    fn debug_workflow_steps(&mut self, _: &DebugWorkflowSteps, cx: &mut ViewContext<Self>) {
1985        let mut output = String::new();
1986        for (i, step) in self.context.read(cx).workflow_steps().iter().enumerate() {
1987            output.push_str(&format!("Step {}:\n", i + 1));
1988            output.push_str(&format!(
1989                "Content: {}\n",
1990                self.context
1991                    .read(cx)
1992                    .buffer()
1993                    .read(cx)
1994                    .text_for_range(step.tagged_range.clone())
1995                    .collect::<String>()
1996            ));
1997            match &step.status {
1998                crate::WorkflowStepStatus::Resolved(ResolvedWorkflowStep {
1999                    title,
2000                    suggestions,
2001                }) => {
2002                    output.push_str("Resolution:\n");
2003                    output.push_str(&format!("  {:?}\n", title));
2004                    output.push_str(&format!("  {:?}\n", suggestions));
2005                }
2006                crate::WorkflowStepStatus::Pending(_) => {
2007                    output.push_str("Resolution: Pending\n");
2008                }
2009                crate::WorkflowStepStatus::Error(error) => {
2010                    writeln!(output, "Resolution: Error\n{:?}", error).unwrap();
2011                }
2012            }
2013            output.push('\n');
2014        }
2015
2016        let editor = self
2017            .workspace
2018            .update(cx, |workspace, cx| Editor::new_in_workspace(workspace, cx));
2019
2020        if let Ok(editor) = editor {
2021            cx.spawn(|_, mut cx| async move {
2022                let editor = editor.await?;
2023                editor.update(&mut cx, |editor, cx| editor.set_text(output, cx))
2024            })
2025            .detach_and_notify_err(cx);
2026        }
2027    }
2028
2029    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2030        let cursors = self.cursors(cx);
2031        self.context.update(cx, |context, cx| {
2032            let messages = context
2033                .messages_for_offsets(cursors, cx)
2034                .into_iter()
2035                .map(|message| message.id)
2036                .collect();
2037            context.cycle_message_roles(messages, cx)
2038        });
2039    }
2040
2041    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2042        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2043        selections
2044            .into_iter()
2045            .map(|selection| selection.head())
2046            .collect()
2047    }
2048
2049    fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
2050        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
2051            self.editor.update(cx, |editor, cx| {
2052                editor.transact(cx, |editor, cx| {
2053                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
2054                    let snapshot = editor.buffer().read(cx).snapshot(cx);
2055                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
2056                    if newest_cursor.column > 0
2057                        || snapshot
2058                            .chars_at(newest_cursor)
2059                            .next()
2060                            .map_or(false, |ch| ch != '\n')
2061                    {
2062                        editor.move_to_end_of_line(
2063                            &MoveToEndOfLine {
2064                                stop_at_soft_wraps: false,
2065                            },
2066                            cx,
2067                        );
2068                        editor.newline(&Newline, cx);
2069                    }
2070
2071                    editor.insert(&format!("/{name}"), cx);
2072                    if command.requires_argument() {
2073                        editor.insert(" ", cx);
2074                        editor.show_completions(&ShowCompletions::default(), cx);
2075                    }
2076                });
2077            });
2078            if !command.requires_argument() {
2079                self.confirm_command(&ConfirmCommand, cx);
2080            }
2081        }
2082    }
2083
2084    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
2085        let selections = self.editor.read(cx).selections.disjoint_anchors();
2086        let mut commands_by_range = HashMap::default();
2087        let workspace = self.workspace.clone();
2088        self.context.update(cx, |context, cx| {
2089            context.reparse_slash_commands(cx);
2090            for selection in selections.iter() {
2091                if let Some(command) =
2092                    context.pending_command_for_position(selection.head().text_anchor, cx)
2093                {
2094                    commands_by_range
2095                        .entry(command.source_range.clone())
2096                        .or_insert_with(|| command.clone());
2097                }
2098            }
2099        });
2100
2101        if commands_by_range.is_empty() {
2102            cx.propagate();
2103        } else {
2104            for command in commands_by_range.into_values() {
2105                self.run_command(
2106                    command.source_range,
2107                    &command.name,
2108                    command.argument.as_deref(),
2109                    true,
2110                    workspace.clone(),
2111                    cx,
2112                );
2113            }
2114            cx.stop_propagation();
2115        }
2116    }
2117
2118    pub fn run_command(
2119        &mut self,
2120        command_range: Range<language::Anchor>,
2121        name: &str,
2122        argument: Option<&str>,
2123        insert_trailing_newline: bool,
2124        workspace: WeakView<Workspace>,
2125        cx: &mut ViewContext<Self>,
2126    ) {
2127        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
2128            let argument = argument.map(ToString::to_string);
2129            let output = command.run(
2130                argument.as_deref(),
2131                workspace,
2132                self.lsp_adapter_delegate.clone(),
2133                cx,
2134            );
2135            self.context.update(cx, |context, cx| {
2136                context.insert_command_output(command_range, output, insert_trailing_newline, cx)
2137            });
2138        }
2139    }
2140
2141    fn handle_context_event(
2142        &mut self,
2143        _: Model<Context>,
2144        event: &ContextEvent,
2145        cx: &mut ViewContext<Self>,
2146    ) {
2147        let context_editor = cx.view().downgrade();
2148
2149        match event {
2150            ContextEvent::MessagesEdited => {
2151                self.update_message_headers(cx);
2152                self.context.update(cx, |context, cx| {
2153                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2154                });
2155            }
2156            ContextEvent::WorkflowStepsRemoved(removed) => {
2157                self.remove_workflow_steps(removed, cx);
2158                cx.notify();
2159            }
2160            ContextEvent::WorkflowStepUpdated(updated) => {
2161                self.update_workflow_step(updated.clone(), cx);
2162                cx.notify();
2163            }
2164            ContextEvent::SummaryChanged => {
2165                cx.emit(EditorEvent::TitleChanged);
2166                self.context.update(cx, |context, cx| {
2167                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2168                });
2169            }
2170            ContextEvent::StreamedCompletion => {
2171                self.editor.update(cx, |editor, cx| {
2172                    if let Some(scroll_position) = self.scroll_position {
2173                        let snapshot = editor.snapshot(cx);
2174                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2175                        let scroll_top =
2176                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2177                        editor.set_scroll_position(
2178                            point(scroll_position.offset_before_cursor.x, scroll_top),
2179                            cx,
2180                        );
2181                    }
2182                });
2183            }
2184            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2185                self.editor.update(cx, |editor, cx| {
2186                    let buffer = editor.buffer().read(cx).snapshot(cx);
2187                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
2188                    let excerpt_id = *excerpt_id;
2189
2190                    editor.remove_creases(
2191                        removed
2192                            .iter()
2193                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
2194                        cx,
2195                    );
2196
2197                    editor.remove_blocks(
2198                        HashSet::from_iter(
2199                            removed.iter().filter_map(|range| {
2200                                self.pending_slash_command_blocks.remove(range)
2201                            }),
2202                        ),
2203                        None,
2204                        cx,
2205                    );
2206
2207                    let crease_ids = editor.insert_creases(
2208                        updated.iter().map(|command| {
2209                            let workspace = self.workspace.clone();
2210                            let confirm_command = Arc::new({
2211                                let context_editor = context_editor.clone();
2212                                let command = command.clone();
2213                                move |cx: &mut WindowContext| {
2214                                    context_editor
2215                                        .update(cx, |context_editor, cx| {
2216                                            context_editor.run_command(
2217                                                command.source_range.clone(),
2218                                                &command.name,
2219                                                command.argument.as_deref(),
2220                                                false,
2221                                                workspace.clone(),
2222                                                cx,
2223                                            );
2224                                        })
2225                                        .ok();
2226                                }
2227                            });
2228                            let placeholder = FoldPlaceholder {
2229                                render: Arc::new(move |_, _, _| Empty.into_any()),
2230                                constrain_width: false,
2231                                merge_adjacent: false,
2232                            };
2233                            let render_toggle = {
2234                                let confirm_command = confirm_command.clone();
2235                                let command = command.clone();
2236                                move |row, _, _, _cx: &mut WindowContext| {
2237                                    render_pending_slash_command_gutter_decoration(
2238                                        row,
2239                                        &command.status,
2240                                        confirm_command.clone(),
2241                                    )
2242                                }
2243                            };
2244                            let render_trailer = {
2245                                let command = command.clone();
2246                                move |row, _unfold, cx: &mut WindowContext| {
2247                                    // TODO: In the future we should investigate how we can expose
2248                                    // this as a hook on the `SlashCommand` trait so that we don't
2249                                    // need to special-case it here.
2250                                    if command.name == DocsSlashCommand::NAME {
2251                                        return render_docs_slash_command_trailer(
2252                                            row,
2253                                            command.clone(),
2254                                            cx,
2255                                        );
2256                                    }
2257
2258                                    Empty.into_any()
2259                                }
2260                            };
2261
2262                            let start = buffer
2263                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2264                                .unwrap();
2265                            let end = buffer
2266                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2267                                .unwrap();
2268                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2269                        }),
2270                        cx,
2271                    );
2272
2273                    let block_ids = editor.insert_blocks(
2274                        updated
2275                            .iter()
2276                            .filter_map(|command| match &command.status {
2277                                PendingSlashCommandStatus::Error(error) => {
2278                                    Some((command, error.clone()))
2279                                }
2280                                _ => None,
2281                            })
2282                            .map(|(command, error_message)| BlockProperties {
2283                                style: BlockStyle::Fixed,
2284                                position: Anchor {
2285                                    buffer_id: Some(buffer_id),
2286                                    excerpt_id,
2287                                    text_anchor: command.source_range.start,
2288                                },
2289                                height: 1,
2290                                disposition: BlockDisposition::Below,
2291                                render: slash_command_error_block_renderer(error_message),
2292                                priority: 0,
2293                            }),
2294                        None,
2295                        cx,
2296                    );
2297
2298                    self.pending_slash_command_creases.extend(
2299                        updated
2300                            .iter()
2301                            .map(|command| command.source_range.clone())
2302                            .zip(crease_ids),
2303                    );
2304
2305                    self.pending_slash_command_blocks.extend(
2306                        updated
2307                            .iter()
2308                            .map(|command| command.source_range.clone())
2309                            .zip(block_ids),
2310                    );
2311                })
2312            }
2313            ContextEvent::SlashCommandFinished {
2314                output_range,
2315                sections,
2316                run_commands_in_output,
2317            } => {
2318                self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
2319
2320                if *run_commands_in_output {
2321                    let commands = self.context.update(cx, |context, cx| {
2322                        context.reparse_slash_commands(cx);
2323                        context
2324                            .pending_commands_for_range(output_range.clone(), cx)
2325                            .to_vec()
2326                    });
2327
2328                    for command in commands {
2329                        self.run_command(
2330                            command.source_range,
2331                            &command.name,
2332                            command.argument.as_deref(),
2333                            false,
2334                            self.workspace.clone(),
2335                            cx,
2336                        );
2337                    }
2338                }
2339            }
2340            ContextEvent::Operation(_) => {}
2341            ContextEvent::AssistError(error_message) => {
2342                self.error_message = Some(SharedString::from(error_message.clone()));
2343            }
2344        }
2345    }
2346
2347    fn insert_slash_command_output_sections(
2348        &mut self,
2349        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2350        cx: &mut ViewContext<Self>,
2351    ) {
2352        self.editor.update(cx, |editor, cx| {
2353            let buffer = editor.buffer().read(cx).snapshot(cx);
2354            let excerpt_id = *buffer.as_singleton().unwrap().0;
2355            let mut buffer_rows_to_fold = BTreeSet::new();
2356            let mut creases = Vec::new();
2357            for section in sections {
2358                let start = buffer
2359                    .anchor_in_excerpt(excerpt_id, section.range.start)
2360                    .unwrap();
2361                let end = buffer
2362                    .anchor_in_excerpt(excerpt_id, section.range.end)
2363                    .unwrap();
2364                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2365                buffer_rows_to_fold.insert(buffer_row);
2366                creases.push(Crease::new(
2367                    start..end,
2368                    FoldPlaceholder {
2369                        render: Arc::new({
2370                            let editor = cx.view().downgrade();
2371                            let icon = section.icon;
2372                            let label = section.label.clone();
2373                            move |fold_id, fold_range, _cx| {
2374                                let editor = editor.clone();
2375                                ButtonLike::new(fold_id)
2376                                    .style(ButtonStyle::Filled)
2377                                    .layer(ElevationIndex::ElevatedSurface)
2378                                    .child(Icon::new(icon))
2379                                    .child(Label::new(label.clone()).single_line())
2380                                    .on_click(move |_, cx| {
2381                                        editor
2382                                            .update(cx, |editor, cx| {
2383                                                let buffer_start = fold_range
2384                                                    .start
2385                                                    .to_point(&editor.buffer().read(cx).read(cx));
2386                                                let buffer_row = MultiBufferRow(buffer_start.row);
2387                                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
2388                                            })
2389                                            .ok();
2390                                    })
2391                                    .into_any_element()
2392                            }
2393                        }),
2394                        constrain_width: false,
2395                        merge_adjacent: false,
2396                    },
2397                    render_slash_command_output_toggle,
2398                    |_, _, _| Empty.into_any_element(),
2399                ));
2400            }
2401
2402            editor.insert_creases(creases, cx);
2403
2404            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2405                editor.fold_at(&FoldAt { buffer_row }, cx);
2406            }
2407        });
2408    }
2409
2410    fn handle_editor_event(
2411        &mut self,
2412        _: View<Editor>,
2413        event: &EditorEvent,
2414        cx: &mut ViewContext<Self>,
2415    ) {
2416        match event {
2417            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2418                let cursor_scroll_position = self.cursor_scroll_position(cx);
2419                if *autoscroll {
2420                    self.scroll_position = cursor_scroll_position;
2421                } else if self.scroll_position != cursor_scroll_position {
2422                    self.scroll_position = None;
2423                }
2424            }
2425            EditorEvent::SelectionsChanged { .. } => {
2426                self.scroll_position = self.cursor_scroll_position(cx);
2427                self.update_active_workflow_step(cx);
2428            }
2429            _ => {}
2430        }
2431        cx.emit(event.clone());
2432    }
2433
2434    fn active_workflow_step(&self) -> Option<&WorkflowStep> {
2435        let step = self.active_workflow_step.as_ref()?;
2436        self.workflow_steps.get(&step.range)
2437    }
2438
2439    fn remove_workflow_steps(
2440        &mut self,
2441        removed_steps: &[Range<language::Anchor>],
2442        cx: &mut ViewContext<Self>,
2443    ) {
2444        let mut blocks_to_remove = HashSet::default();
2445        for step_range in removed_steps {
2446            self.hide_workflow_step(step_range.clone(), cx);
2447            if let Some(step) = self.workflow_steps.remove(step_range) {
2448                blocks_to_remove.insert(step.header_block_id);
2449                blocks_to_remove.insert(step.footer_block_id);
2450            }
2451            if let Some(debug) = self.debug_inspector.as_mut() {
2452                debug.deactivate_for(step_range, cx);
2453            }
2454        }
2455        self.editor.update(cx, |editor, cx| {
2456            editor.remove_blocks(blocks_to_remove, None, cx)
2457        });
2458        self.update_active_workflow_step(cx);
2459    }
2460
2461    fn update_workflow_step(
2462        &mut self,
2463        step_range: Range<language::Anchor>,
2464        cx: &mut ViewContext<Self>,
2465    ) {
2466        let buffer_snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2467        let (&excerpt_id, _, _) = buffer_snapshot.as_singleton().unwrap();
2468
2469        let Some(step) = self
2470            .context
2471            .read(cx)
2472            .workflow_step_for_range(step_range.clone())
2473        else {
2474            return;
2475        };
2476
2477        let resolved_step = step.status.into_resolved();
2478        if let Some(existing_step) = self.workflow_steps.get_mut(&step_range) {
2479            existing_step.resolved_step = resolved_step;
2480            if let Some(debug) = self.debug_inspector.as_mut() {
2481                debug.refresh(&step_range, cx);
2482            }
2483        } else {
2484            let start = buffer_snapshot
2485                .anchor_in_excerpt(excerpt_id, step_range.start)
2486                .unwrap();
2487            let end = buffer_snapshot
2488                .anchor_in_excerpt(excerpt_id, step_range.end)
2489                .unwrap();
2490            let weak_self = cx.view().downgrade();
2491            let block_ids = self.editor.update(cx, |editor, cx| {
2492                let step_range = step_range.clone();
2493                let editor_focus_handle = editor.focus_handle(cx);
2494                editor.insert_blocks(
2495                    vec![
2496                        BlockProperties {
2497                            position: start,
2498                            height: 1,
2499                            style: BlockStyle::Sticky,
2500                            render: Box::new({
2501                                let weak_self = weak_self.clone();
2502                                let step_range = step_range.clone();
2503                                move |cx| {
2504                                    let current_status = weak_self
2505                                        .update(&mut **cx, |context_editor, cx| {
2506                                            let step =
2507                                                context_editor.workflow_steps.get(&step_range)?;
2508                                            Some(step.status(cx))
2509                                        })
2510                                        .ok()
2511                                        .flatten();
2512
2513                                    let theme = cx.theme().status();
2514                                    let border_color = if current_status
2515                                        .as_ref()
2516                                        .map_or(false, |status| status.is_confirmed())
2517                                    {
2518                                        theme.ignored_border
2519                                    } else {
2520                                        theme.info_border
2521                                    };
2522                                    let debug_header = weak_self
2523                                        .update(&mut **cx, |this, _| {
2524                                            if let Some(inspector) = this.debug_inspector.as_mut() {
2525                                                Some(inspector.is_active(&step_range))
2526                                            } else {
2527                                                None
2528                                            }
2529                                        })
2530                                        .unwrap_or_default();
2531                                    div()
2532                                        .w_full()
2533                                        .px(cx.gutter_dimensions.full_width())
2534                                        .child(
2535                                            h_flex()
2536                                                .w_full()
2537                                                .border_b_1()
2538                                                .border_color(border_color)
2539                                                .pb_1()
2540                                                .justify_between()
2541                                                .gap_2()
2542                                                .children(debug_header.map(|is_active| {
2543                                                    h_flex().justify_start().child(
2544                                                        Button::new("debug-workflows-toggle", "Debug")
2545                                                            .icon_color(Color::Hidden)
2546                                                            .color(Color::Hidden)
2547                                                            .selected_icon_color(Color::Default)
2548                                                            .selected_label_color(Color::Default)
2549                                                            .icon(IconName::Microscope)
2550                                                            .icon_position(IconPosition::Start)
2551                                                            .icon_size(IconSize::Small)
2552                                                            .label_size(LabelSize::Small)
2553                                                            .selected(is_active)
2554                                                            .on_click({
2555                                                                let weak_self = weak_self.clone();
2556                                                                let step_range = step_range.clone();
2557                                                                move |_, cx| {
2558                                                                    weak_self
2559                                                                        .update(cx, |this, cx| {
2560                                                                            if let Some(inspector) =
2561                                                                                this.debug_inspector
2562                                                                                    .as_mut()
2563                                                                            {
2564                                                                                if is_active {
2565
2566                                                                                    inspector.deactivate_for(&step_range, cx);
2567                                                                                } else {
2568                                                                                    inspector.activate_for_step(step_range.clone(), cx);
2569                                                                                }
2570                                                                            }
2571                                                                        })
2572                                                                        .ok();
2573                                                                }
2574                                                            }),
2575                                                    )
2576                                                    // .child(h_flex().w_full())
2577                                                }))
2578                                                .children(current_status.as_ref().map(|status| {
2579                                                    h_flex().w_full().justify_end().child(
2580                                                        status.into_element(
2581                                                            step_range.clone(),
2582                                                            editor_focus_handle.clone(),
2583                                                            weak_self.clone(),
2584                                                            cx,
2585                                                        ),
2586                                                    )
2587                                                })),
2588                                        )
2589                                        .into_any()
2590                                }
2591                            }),
2592                            disposition: BlockDisposition::Above,
2593                            priority: 0,
2594                        },
2595                        BlockProperties {
2596                            position: end,
2597                            height: 0,
2598                            style: BlockStyle::Sticky,
2599                            render: Box::new(move |cx| {
2600                                let current_status = weak_self
2601                                    .update(&mut **cx, |context_editor, cx| {
2602                                        let step =
2603                                            context_editor.workflow_steps.get(&step_range)?;
2604                                        Some(step.status(cx))
2605                                    })
2606                                    .ok()
2607                                    .flatten();
2608                                let theme = cx.theme().status();
2609                                let border_color = if current_status
2610                                    .as_ref()
2611                                    .map_or(false, |status| status.is_confirmed())
2612                                {
2613                                    theme.ignored_border
2614                                } else {
2615                                    theme.info_border
2616                                };
2617
2618                                div()
2619                                    .w_full()
2620                                    .px(cx.gutter_dimensions.full_width())
2621                                    .child(
2622                                        h_flex().w_full().border_t_1().border_color(border_color),
2623                                    )
2624                                    .into_any()
2625                            }),
2626                            disposition: BlockDisposition::Below,
2627                            priority: 0,
2628                        },
2629                    ],
2630                    None,
2631                    cx,
2632                )
2633            });
2634            self.workflow_steps.insert(
2635                step_range.clone(),
2636                WorkflowStep {
2637                    range: step_range.clone(),
2638                    header_block_id: block_ids[0],
2639                    footer_block_id: block_ids[1],
2640                    resolved_step,
2641                    assist: None,
2642                },
2643            );
2644        }
2645
2646        self.update_active_workflow_step(cx);
2647    }
2648
2649    fn update_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) {
2650        let new_step = self.active_workflow_step_for_cursor(cx);
2651        if new_step.as_ref() != self.active_workflow_step.as_ref() {
2652            if let Some(old_step) = self.active_workflow_step.take() {
2653                self.hide_workflow_step(old_step.range, cx);
2654            }
2655
2656            if let Some(new_step) = new_step {
2657                self.show_workflow_step(new_step.range.clone(), cx);
2658                self.active_workflow_step = Some(new_step);
2659            }
2660        }
2661    }
2662
2663    fn hide_workflow_step(
2664        &mut self,
2665        step_range: Range<language::Anchor>,
2666        cx: &mut ViewContext<Self>,
2667    ) {
2668        let Some(step) = self.workflow_steps.get_mut(&step_range) else {
2669            return;
2670        };
2671        let Some(assist) = step.assist.as_ref() else {
2672            return;
2673        };
2674        let Some(editor) = assist.editor.upgrade() else {
2675            return;
2676        };
2677
2678        if matches!(step.status(cx), WorkflowStepStatus::Idle) {
2679            let assist = step.assist.take().unwrap();
2680            InlineAssistant::update_global(cx, |assistant, cx| {
2681                for assist_id in assist.assist_ids {
2682                    assistant.finish_assist(assist_id, true, cx)
2683                }
2684            });
2685
2686            self.workspace
2687                .update(cx, |workspace, cx| {
2688                    if let Some(pane) = workspace.pane_for(&editor) {
2689                        pane.update(cx, |pane, cx| {
2690                            let item_id = editor.entity_id();
2691                            if !assist.editor_was_open && pane.is_active_preview_item(item_id) {
2692                                pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
2693                                    .detach_and_log_err(cx);
2694                            }
2695                        });
2696                    }
2697                })
2698                .ok();
2699        }
2700    }
2701
2702    fn show_workflow_step(
2703        &mut self,
2704        step_range: Range<language::Anchor>,
2705        cx: &mut ViewContext<Self>,
2706    ) {
2707        let Some(step) = self.workflow_steps.get_mut(&step_range) else {
2708            return;
2709        };
2710
2711        let mut scroll_to_assist_id = None;
2712        match step.status(cx) {
2713            WorkflowStepStatus::Idle => {
2714                if let Some(assist) = step.assist.as_ref() {
2715                    scroll_to_assist_id = assist.assist_ids.first().copied();
2716                } else if let Some(Ok(resolved)) = step.resolved_step.as_ref() {
2717                    step.assist = Self::open_assists_for_step(
2718                        resolved,
2719                        &self.project,
2720                        &self.assistant_panel,
2721                        &self.workspace,
2722                        cx,
2723                    );
2724                }
2725            }
2726            WorkflowStepStatus::Pending => {
2727                if let Some(assist) = step.assist.as_ref() {
2728                    let assistant = InlineAssistant::global(cx);
2729                    scroll_to_assist_id = assist
2730                        .assist_ids
2731                        .iter()
2732                        .copied()
2733                        .find(|assist_id| assistant.assist_status(*assist_id, cx).is_pending());
2734                }
2735            }
2736            WorkflowStepStatus::Done => {
2737                if let Some(assist) = step.assist.as_ref() {
2738                    scroll_to_assist_id = assist.assist_ids.first().copied();
2739                }
2740            }
2741            _ => {}
2742        }
2743
2744        if let Some(assist_id) = scroll_to_assist_id {
2745            if let Some(editor) = step
2746                .assist
2747                .as_ref()
2748                .and_then(|assists| assists.editor.upgrade())
2749            {
2750                self.workspace
2751                    .update(cx, |workspace, cx| {
2752                        workspace.activate_item(&editor, false, false, cx);
2753                    })
2754                    .ok();
2755                InlineAssistant::update_global(cx, |assistant, cx| {
2756                    assistant.scroll_to_assist(assist_id, cx)
2757                });
2758            }
2759        }
2760    }
2761
2762    fn open_assists_for_step(
2763        resolved_step: &ResolvedWorkflowStep,
2764        project: &Model<Project>,
2765        assistant_panel: &WeakView<AssistantPanel>,
2766        workspace: &WeakView<Workspace>,
2767        cx: &mut ViewContext<Self>,
2768    ) -> Option<WorkflowAssist> {
2769        let assistant_panel = assistant_panel.upgrade()?;
2770        if resolved_step.suggestions.is_empty() {
2771            return None;
2772        }
2773
2774        let editor;
2775        let mut editor_was_open = false;
2776        let mut suggestion_groups = Vec::new();
2777        if resolved_step.suggestions.len() == 1
2778            && resolved_step.suggestions.values().next().unwrap().len() == 1
2779        {
2780            // If there's only one buffer and one suggestion group, open it directly
2781            let (buffer, groups) = resolved_step.suggestions.iter().next().unwrap();
2782            let group = groups.into_iter().next().unwrap();
2783            editor = workspace
2784                .update(cx, |workspace, cx| {
2785                    let active_pane = workspace.active_pane().clone();
2786                    editor_was_open =
2787                        workspace.is_project_item_open::<Editor>(&active_pane, buffer, cx);
2788                    workspace.open_project_item::<Editor>(
2789                        active_pane,
2790                        buffer.clone(),
2791                        false,
2792                        false,
2793                        cx,
2794                    )
2795                })
2796                .log_err()?;
2797
2798            let (&excerpt_id, _, _) = editor
2799                .read(cx)
2800                .buffer()
2801                .read(cx)
2802                .read(cx)
2803                .as_singleton()
2804                .unwrap();
2805
2806            // Scroll the editor to the suggested assist
2807            editor.update(cx, |editor, cx| {
2808                let multibuffer = editor.buffer().read(cx).snapshot(cx);
2809                let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2810                let anchor = if group.context_range.start.to_offset(buffer) == 0 {
2811                    Anchor::min()
2812                } else {
2813                    multibuffer
2814                        .anchor_in_excerpt(excerpt_id, group.context_range.start)
2815                        .unwrap()
2816                };
2817
2818                editor.set_scroll_anchor(
2819                    ScrollAnchor {
2820                        offset: gpui::Point::default(),
2821                        anchor,
2822                    },
2823                    cx,
2824                );
2825            });
2826
2827            suggestion_groups.push((excerpt_id, group));
2828        } else {
2829            // If there are multiple buffers or suggestion groups, create a multibuffer
2830            let multibuffer = cx.new_model(|cx| {
2831                let replica_id = project.read(cx).replica_id();
2832                let mut multibuffer = MultiBuffer::new(replica_id, Capability::ReadWrite)
2833                    .with_title(resolved_step.title.clone());
2834                for (buffer, groups) in &resolved_step.suggestions {
2835                    let excerpt_ids = multibuffer.push_excerpts(
2836                        buffer.clone(),
2837                        groups.iter().map(|suggestion_group| ExcerptRange {
2838                            context: suggestion_group.context_range.clone(),
2839                            primary: None,
2840                        }),
2841                        cx,
2842                    );
2843                    suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
2844                }
2845                multibuffer
2846            });
2847
2848            editor = cx.new_view(|cx| {
2849                Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx)
2850            });
2851            workspace
2852                .update(cx, |workspace, cx| {
2853                    workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2854                })
2855                .log_err()?;
2856        }
2857
2858        let mut assist_ids = Vec::new();
2859        for (excerpt_id, suggestion_group) in suggestion_groups {
2860            for suggestion in &suggestion_group.suggestions {
2861                assist_ids.extend(suggestion.show(
2862                    &editor,
2863                    excerpt_id,
2864                    workspace,
2865                    &assistant_panel,
2866                    cx,
2867                ));
2868            }
2869        }
2870
2871        let mut observations = Vec::new();
2872        InlineAssistant::update_global(cx, |assistant, _cx| {
2873            for assist_id in &assist_ids {
2874                observations.push(assistant.observe_assist(*assist_id));
2875            }
2876        });
2877
2878        Some(WorkflowAssist {
2879            assist_ids,
2880            editor: editor.downgrade(),
2881            editor_was_open,
2882            _observe_assist_status: cx.spawn(|this, mut cx| async move {
2883                while !observations.is_empty() {
2884                    let (result, ix, _) = futures::future::select_all(
2885                        observations
2886                            .iter_mut()
2887                            .map(|observation| Box::pin(observation.changed())),
2888                    )
2889                    .await;
2890
2891                    if result.is_err() {
2892                        observations.remove(ix);
2893                    }
2894
2895                    if this.update(&mut cx, |_, cx| cx.notify()).is_err() {
2896                        break;
2897                    }
2898                }
2899            }),
2900        })
2901    }
2902
2903    fn handle_editor_search_event(
2904        &mut self,
2905        _: View<Editor>,
2906        event: &SearchEvent,
2907        cx: &mut ViewContext<Self>,
2908    ) {
2909        cx.emit(event.clone());
2910    }
2911
2912    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2913        self.editor.update(cx, |editor, cx| {
2914            let snapshot = editor.snapshot(cx);
2915            let cursor = editor.selections.newest_anchor().head();
2916            let cursor_row = cursor
2917                .to_display_point(&snapshot.display_snapshot)
2918                .row()
2919                .as_f32();
2920            let scroll_position = editor
2921                .scroll_manager
2922                .anchor()
2923                .scroll_position(&snapshot.display_snapshot);
2924
2925            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2926            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2927                Some(ScrollPosition {
2928                    cursor,
2929                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2930                })
2931            } else {
2932                None
2933            }
2934        })
2935    }
2936
2937    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2938        self.editor.update(cx, |editor, cx| {
2939            let buffer = editor.buffer().read(cx).snapshot(cx);
2940            let excerpt_id = *buffer.as_singleton().unwrap().0;
2941            let old_blocks = std::mem::take(&mut self.blocks);
2942            let new_blocks = self
2943                .context
2944                .read(cx)
2945                .messages(cx)
2946                .map(|message| BlockProperties {
2947                    position: buffer
2948                        .anchor_in_excerpt(excerpt_id, message.anchor)
2949                        .unwrap(),
2950                    height: 2,
2951                    style: BlockStyle::Sticky,
2952                    render: Box::new({
2953                        let context = self.context.clone();
2954                        move |cx| {
2955                            let message_id = message.id;
2956                            let sender = ButtonLike::new("role")
2957                                .style(ButtonStyle::Filled)
2958                                .child(match message.role {
2959                                    Role::User => Label::new("You").color(Color::Default),
2960                                    Role::Assistant => Label::new("Assistant").color(Color::Info),
2961                                    Role::System => Label::new("System").color(Color::Warning),
2962                                })
2963                                .tooltip(|cx| {
2964                                    Tooltip::with_meta(
2965                                        "Toggle message role",
2966                                        None,
2967                                        "Available roles: You (User), Assistant, System",
2968                                        cx,
2969                                    )
2970                                })
2971                                .on_click({
2972                                    let context = context.clone();
2973                                    move |_, cx| {
2974                                        context.update(cx, |context, cx| {
2975                                            context.cycle_message_roles(
2976                                                HashSet::from_iter(Some(message_id)),
2977                                                cx,
2978                                            )
2979                                        })
2980                                    }
2981                                });
2982
2983                            let trigger = Button::new("show-error", "Error")
2984                                .color(Color::Error)
2985                                .selected_label_color(Color::Error)
2986                                .selected_icon_color(Color::Error)
2987                                .icon(IconName::XCircle)
2988                                .icon_color(Color::Error)
2989                                .icon_size(IconSize::Small)
2990                                .icon_position(IconPosition::Start)
2991                                .tooltip(move |cx| {
2992                                    Tooltip::with_meta(
2993                                        "Error interacting with language model",
2994                                        None,
2995                                        "Click for more details",
2996                                        cx,
2997                                    )
2998                                });
2999                            h_flex()
3000                                .id(("message_header", message_id.as_u64()))
3001                                .pl(cx.gutter_dimensions.full_width())
3002                                .h_11()
3003                                .w_full()
3004                                .relative()
3005                                .gap_1()
3006                                .child(sender)
3007                                .children(
3008                                    if let MessageStatus::Error(error) = message.status.clone() {
3009                                        Some(
3010                                            PopoverMenu::new("show-error-popover")
3011                                                .menu(move |cx| {
3012                                                    Some(cx.new_view(|cx| ErrorPopover {
3013                                                        error: error.clone(),
3014                                                        focus_handle: cx.focus_handle(),
3015                                                    }))
3016                                                })
3017                                                .trigger(trigger),
3018                                        )
3019                                    } else {
3020                                        None
3021                                    },
3022                                )
3023                                .into_any_element()
3024                        }
3025                    }),
3026                    disposition: BlockDisposition::Above,
3027                    priority: usize::MAX,
3028                })
3029                .collect::<Vec<_>>();
3030
3031            editor.remove_blocks(old_blocks, None, cx);
3032            let ids = editor.insert_blocks(new_blocks, None, cx);
3033            self.blocks = HashSet::from_iter(ids);
3034        });
3035    }
3036
3037    fn insert_selection(
3038        workspace: &mut Workspace,
3039        _: &InsertIntoEditor,
3040        cx: &mut ViewContext<Workspace>,
3041    ) {
3042        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3043            return;
3044        };
3045        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3046            return;
3047        };
3048        let Some(active_editor_view) = workspace
3049            .active_item(cx)
3050            .and_then(|item| item.act_as::<Editor>(cx))
3051        else {
3052            return;
3053        };
3054
3055        let context_editor = context_editor_view.read(cx).editor.read(cx);
3056        let anchor = context_editor.selections.newest_anchor();
3057        let text = context_editor
3058            .buffer()
3059            .read(cx)
3060            .read(cx)
3061            .text_for_range(anchor.range())
3062            .collect::<String>();
3063
3064        // If nothing is selected, don't delete the current selection; instead, be a no-op.
3065        if !text.is_empty() {
3066            active_editor_view.update(cx, |editor, cx| {
3067                editor.insert(&text, cx);
3068                editor.focus(cx);
3069            })
3070        }
3071    }
3072
3073    fn quote_selection(
3074        workspace: &mut Workspace,
3075        _: &QuoteSelection,
3076        cx: &mut ViewContext<Workspace>,
3077    ) {
3078        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3079            return;
3080        };
3081        let Some(editor) = workspace
3082            .active_item(cx)
3083            .and_then(|item| item.act_as::<Editor>(cx))
3084        else {
3085            return;
3086        };
3087
3088        let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
3089        let editor = editor.read(cx);
3090        let buffer = editor.buffer().read(cx).snapshot(cx);
3091        let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3092            ..editor::ToOffset::to_offset(&selection.end, &buffer);
3093        let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3094        if selected_text.is_empty() {
3095            return;
3096        }
3097
3098        let start_language = buffer.language_at(range.start);
3099        let end_language = buffer.language_at(range.end);
3100        let language_name = if start_language == end_language {
3101            start_language.map(|language| language.code_fence_block_name())
3102        } else {
3103            None
3104        };
3105        let language_name = language_name.as_deref().unwrap_or("");
3106
3107        let filename = buffer
3108            .file_at(selection.start)
3109            .map(|file| file.full_path(cx));
3110
3111        let text = if language_name == "markdown" {
3112            selected_text
3113                .lines()
3114                .map(|line| format!("> {}", line))
3115                .collect::<Vec<_>>()
3116                .join("\n")
3117        } else {
3118            let start_symbols = buffer
3119                .symbols_containing(selection.start, None)
3120                .map(|(_, symbols)| symbols);
3121            let end_symbols = buffer
3122                .symbols_containing(selection.end, None)
3123                .map(|(_, symbols)| symbols);
3124
3125            let outline_text =
3126                if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
3127                    Some(
3128                        start_symbols
3129                            .into_iter()
3130                            .zip(end_symbols)
3131                            .take_while(|(a, b)| a == b)
3132                            .map(|(a, _)| a.text)
3133                            .collect::<Vec<_>>()
3134                            .join(" > "),
3135                    )
3136                } else {
3137                    None
3138                };
3139
3140            let line_comment_prefix = start_language
3141                .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3142
3143            let fence = codeblock_fence_for_path(
3144                filename.as_deref(),
3145                Some(selection.start.row..selection.end.row),
3146            );
3147
3148            if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
3149            {
3150                let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
3151                format!("{fence}{breadcrumb}{selected_text}\n```")
3152            } else {
3153                format!("{fence}{selected_text}\n```")
3154            }
3155        };
3156
3157        let crease_title = if let Some(path) = filename {
3158            let start_line = selection.start.row + 1;
3159            let end_line = selection.end.row + 1;
3160            if start_line == end_line {
3161                format!("{}, Line {}", path.display(), start_line)
3162            } else {
3163                format!("{}, Lines {} to {}", path.display(), start_line, end_line)
3164            }
3165        } else {
3166            "Quoted selection".to_string()
3167        };
3168
3169        // Activate the panel
3170        if !panel.focus_handle(cx).contains_focused(cx) {
3171            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3172        }
3173
3174        panel.update(cx, |_, cx| {
3175            // Wait to create a new context until the workspace is no longer
3176            // being updated.
3177            cx.defer(move |panel, cx| {
3178                if let Some(context) = panel
3179                    .active_context_editor(cx)
3180                    .or_else(|| panel.new_context(cx))
3181                {
3182                    context.update(cx, |context, cx| {
3183                        context.editor.update(cx, |editor, cx| {
3184                            editor.insert("\n", cx);
3185
3186                            let point = editor.selections.newest::<Point>(cx).head();
3187                            let start_row = MultiBufferRow(point.row);
3188
3189                            editor.insert(&text, cx);
3190
3191                            let snapshot = editor.buffer().read(cx).snapshot(cx);
3192                            let anchor_before = snapshot.anchor_after(point);
3193                            let anchor_after = editor
3194                                .selections
3195                                .newest_anchor()
3196                                .head()
3197                                .bias_left(&snapshot);
3198
3199                            editor.insert("\n", cx);
3200
3201                            let fold_placeholder = quote_selection_fold_placeholder(
3202                                crease_title,
3203                                cx.view().downgrade(),
3204                            );
3205                            let crease = Crease::new(
3206                                anchor_before..anchor_after,
3207                                fold_placeholder,
3208                                render_quote_selection_output_toggle,
3209                                |_, _, _| Empty.into_any(),
3210                            );
3211                            editor.insert_creases(vec![crease], cx);
3212                            editor.fold_at(
3213                                &FoldAt {
3214                                    buffer_row: start_row,
3215                                },
3216                                cx,
3217                            );
3218                        })
3219                    });
3220                };
3221            });
3222        });
3223    }
3224
3225    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3226        let editor = self.editor.read(cx);
3227        let context = self.context.read(cx);
3228        if editor.selections.count() == 1 {
3229            let selection = editor.selections.newest::<usize>(cx);
3230            let mut copied_text = String::new();
3231            let mut spanned_messages = 0;
3232            for message in context.messages(cx) {
3233                if message.offset_range.start >= selection.range().end {
3234                    break;
3235                } else if message.offset_range.end >= selection.range().start {
3236                    let range = cmp::max(message.offset_range.start, selection.range().start)
3237                        ..cmp::min(message.offset_range.end, selection.range().end);
3238                    if !range.is_empty() {
3239                        spanned_messages += 1;
3240                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
3241                        for chunk in context.buffer().read(cx).text_for_range(range) {
3242                            copied_text.push_str(chunk);
3243                        }
3244                        copied_text.push('\n');
3245                    }
3246                }
3247            }
3248
3249            if spanned_messages > 1 {
3250                cx.write_to_clipboard(ClipboardItem::new(copied_text));
3251                return;
3252            }
3253        }
3254
3255        cx.propagate();
3256    }
3257
3258    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3259        self.context.update(cx, |context, cx| {
3260            let selections = self.editor.read(cx).selections.disjoint_anchors();
3261            for selection in selections.as_ref() {
3262                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3263                let range = selection
3264                    .map(|endpoint| endpoint.to_offset(&buffer))
3265                    .range();
3266                context.split_message(range, cx);
3267            }
3268        });
3269    }
3270
3271    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3272        self.context.update(cx, |context, cx| {
3273            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3274        });
3275    }
3276
3277    fn title(&self, cx: &AppContext) -> Cow<str> {
3278        self.context
3279            .read(cx)
3280            .summary()
3281            .map(|summary| summary.text.clone())
3282            .map(Cow::Owned)
3283            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3284    }
3285
3286    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3287        use feature_flags::FeatureFlagAppExt;
3288        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
3289            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
3290        });
3291
3292        if nudge.map_or(false, |value| value) {
3293            Some(
3294                h_flex()
3295                    .p_3()
3296                    .border_b_1()
3297                    .border_color(cx.theme().colors().border_variant)
3298                    .bg(cx.theme().colors().editor_background)
3299                    .justify_between()
3300                    .child(
3301                        h_flex()
3302                            .gap_3()
3303                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
3304                            .child(Label::new("Zed AI is here! Get started by signing in →")),
3305                    )
3306                    .child(
3307                        Button::new("sign-in", "Sign in")
3308                            .size(ButtonSize::Compact)
3309                            .style(ButtonStyle::Filled)
3310                            .on_click(cx.listener(|this, _event, cx| {
3311                                let client = this
3312                                    .workspace
3313                                    .update(cx, |workspace, _| workspace.client().clone())
3314                                    .log_err();
3315
3316                                if let Some(client) = client {
3317                                    cx.spawn(|this, mut cx| async move {
3318                                        client.authenticate_and_connect(true, &mut cx).await?;
3319                                        this.update(&mut cx, |_, cx| cx.notify())
3320                                    })
3321                                    .detach_and_log_err(cx)
3322                                }
3323                            })),
3324                    )
3325                    .into_any_element(),
3326            )
3327        } else if let Some(configuration_error) = configuration_error(cx) {
3328            let label = match configuration_error {
3329                ConfigurationError::NoProvider => "No LLM provider selected.",
3330                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
3331            };
3332            Some(
3333                h_flex()
3334                    .p_3()
3335                    .border_b_1()
3336                    .border_color(cx.theme().colors().border_variant)
3337                    .bg(cx.theme().colors().editor_background)
3338                    .justify_between()
3339                    .child(
3340                        h_flex()
3341                            .gap_3()
3342                            .child(
3343                                Icon::new(IconName::ExclamationTriangle)
3344                                    .size(IconSize::Small)
3345                                    .color(Color::Warning),
3346                            )
3347                            .child(Label::new(label)),
3348                    )
3349                    .child(
3350                        Button::new("open-configuration", "Open configuration")
3351                            .size(ButtonSize::Compact)
3352                            .icon_size(IconSize::Small)
3353                            .style(ButtonStyle::Filled)
3354                            .on_click({
3355                                let focus_handle = self.focus_handle(cx).clone();
3356                                move |_event, cx| {
3357                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
3358                                }
3359                            }),
3360                    )
3361                    .into_any_element(),
3362            )
3363        } else {
3364            None
3365        }
3366    }
3367
3368    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3369        let focus_handle = self.focus_handle(cx).clone();
3370        let button_text = match self.active_workflow_step() {
3371            Some(step) => match step.status(cx) {
3372                WorkflowStepStatus::Resolving => "Resolving Step...",
3373                WorkflowStepStatus::Error(_) => "Retry Step Resolution",
3374                WorkflowStepStatus::Idle => "Transform",
3375                WorkflowStepStatus::Pending => "Transforming...",
3376                WorkflowStepStatus::Done => "Accept Transformation",
3377                WorkflowStepStatus::Confirmed => "Send",
3378            },
3379            None => "Send",
3380        };
3381
3382        let (style, tooltip) = match token_state(&self.context, cx) {
3383            Some(TokenState::NoTokensLeft { .. }) => (
3384                ButtonStyle::Tinted(TintColor::Negative),
3385                Some(Tooltip::text("Token limit reached", cx)),
3386            ),
3387            Some(TokenState::HasMoreTokens {
3388                over_warn_threshold,
3389                ..
3390            }) => {
3391                let (style, tooltip) = if over_warn_threshold {
3392                    (
3393                        ButtonStyle::Tinted(TintColor::Warning),
3394                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3395                    )
3396                } else {
3397                    (ButtonStyle::Filled, None)
3398                };
3399                (style, tooltip)
3400            }
3401            None => (ButtonStyle::Filled, None),
3402        };
3403
3404        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3405        let disabled = self.show_accept_terms
3406            && provider
3407                .as_ref()
3408                .map_or(false, |provider| provider.must_accept_terms(cx));
3409
3410        ButtonLike::new("send_button")
3411            .disabled(disabled)
3412            .style(style)
3413            .when_some(tooltip, |button, tooltip| {
3414                button.tooltip(move |_| tooltip.clone())
3415            })
3416            .layer(ElevationIndex::ModalSurface)
3417            .children(
3418                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
3419                    .map(|binding| binding.into_any_element()),
3420            )
3421            .child(Label::new(button_text))
3422            .on_click(move |_event, cx| {
3423                focus_handle.dispatch_action(&Assist, cx);
3424            })
3425    }
3426
3427    fn active_workflow_step_for_cursor(&self, cx: &AppContext) -> Option<ActiveWorkflowStep> {
3428        let newest_cursor = self.editor.read(cx).selections.newest::<usize>(cx).head();
3429        let context = self.context.read(cx);
3430        let buffer = context.buffer().read(cx);
3431
3432        let workflow_steps = context.workflow_steps();
3433        workflow_steps
3434            .binary_search_by(|step| {
3435                let step_range = step.tagged_range.to_offset(&buffer);
3436                if newest_cursor < step_range.start {
3437                    Ordering::Greater
3438                } else if newest_cursor > step_range.end {
3439                    Ordering::Less
3440                } else {
3441                    Ordering::Equal
3442                }
3443            })
3444            .ok()
3445            .and_then(|index| {
3446                let range = workflow_steps[index].tagged_range.clone();
3447                Some(ActiveWorkflowStep {
3448                    resolved: self.workflow_steps.get(&range)?.resolved_step.is_some(),
3449                    range,
3450                })
3451            })
3452    }
3453}
3454
3455impl EventEmitter<EditorEvent> for ContextEditor {}
3456impl EventEmitter<SearchEvent> for ContextEditor {}
3457
3458impl Render for ContextEditor {
3459    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3460        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3461        let accept_terms = if self.show_accept_terms {
3462            provider
3463                .as_ref()
3464                .and_then(|provider| provider.render_accept_terms(cx))
3465        } else {
3466            None
3467        };
3468
3469        v_flex()
3470            .key_context("ContextEditor")
3471            .capture_action(cx.listener(ContextEditor::cancel))
3472            .capture_action(cx.listener(ContextEditor::save))
3473            .capture_action(cx.listener(ContextEditor::copy))
3474            .capture_action(cx.listener(ContextEditor::cycle_message_role))
3475            .capture_action(cx.listener(ContextEditor::confirm_command))
3476            .on_action(cx.listener(ContextEditor::assist))
3477            .on_action(cx.listener(ContextEditor::split))
3478            .on_action(cx.listener(ContextEditor::debug_workflow_steps))
3479            .size_full()
3480            .children(self.render_notice(cx))
3481            .child(
3482                div()
3483                    .flex_grow()
3484                    .bg(cx.theme().colors().editor_background)
3485                    .child(self.editor.clone()),
3486            )
3487            .when_some(accept_terms, |this, element| {
3488                this.child(
3489                    div()
3490                        .absolute()
3491                        .right_4()
3492                        .bottom_10()
3493                        .max_w_96()
3494                        .py_2()
3495                        .px_3()
3496                        .elevation_2(cx)
3497                        .bg(cx.theme().colors().surface_background)
3498                        .occlude()
3499                        .child(element),
3500                )
3501            })
3502            .child(
3503                h_flex().flex_none().relative().child(
3504                    h_flex()
3505                        .w_full()
3506                        .absolute()
3507                        .right_4()
3508                        .bottom_2()
3509                        .justify_end()
3510                        .child(self.render_send_button(cx)),
3511                ),
3512            )
3513    }
3514}
3515
3516struct ErrorPopover {
3517    error: SharedString,
3518    focus_handle: FocusHandle,
3519}
3520
3521impl EventEmitter<DismissEvent> for ErrorPopover {}
3522
3523impl FocusableView for ErrorPopover {
3524    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
3525        self.focus_handle.clone()
3526    }
3527}
3528
3529impl Render for ErrorPopover {
3530    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3531        v_flex()
3532            .mt_2()
3533            .max_w_96()
3534            .py_2()
3535            .px_3()
3536            .gap_0p5()
3537            .elevation_2(cx)
3538            .bg(cx.theme().colors().surface_background)
3539            .occlude()
3540            .child(Label::new("Error interacting with language model").weight(FontWeight::SEMIBOLD))
3541            .child(Label::new(self.error.clone()))
3542            .child(
3543                h_flex().justify_end().mt_1().child(
3544                    Button::new("dismiss", "Dismiss")
3545                        .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
3546                ),
3547            )
3548    }
3549}
3550
3551impl FocusableView for ContextEditor {
3552    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3553        self.editor.focus_handle(cx)
3554    }
3555}
3556
3557impl Item for ContextEditor {
3558    type Event = editor::EditorEvent;
3559
3560    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
3561        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
3562    }
3563
3564    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
3565        match event {
3566            EditorEvent::Edited { .. } => {
3567                f(item::ItemEvent::Edit);
3568            }
3569            EditorEvent::TitleChanged => {
3570                f(item::ItemEvent::UpdateTab);
3571            }
3572            _ => {}
3573        }
3574    }
3575
3576    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
3577        Some(self.title(cx).to_string().into())
3578    }
3579
3580    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
3581        Some(Box::new(handle.clone()))
3582    }
3583
3584    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
3585        self.editor.update(cx, |editor, cx| {
3586            Item::set_nav_history(editor, nav_history, cx)
3587        })
3588    }
3589
3590    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
3591        self.editor
3592            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
3593    }
3594
3595    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3596        self.editor
3597            .update(cx, |editor, cx| Item::deactivated(editor, cx))
3598    }
3599}
3600
3601impl SearchableItem for ContextEditor {
3602    type Match = <Editor as SearchableItem>::Match;
3603
3604    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
3605        self.editor.update(cx, |editor, cx| {
3606            editor.clear_matches(cx);
3607        });
3608    }
3609
3610    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3611        self.editor
3612            .update(cx, |editor, cx| editor.update_matches(matches, cx));
3613    }
3614
3615    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
3616        self.editor
3617            .update(cx, |editor, cx| editor.query_suggestion(cx))
3618    }
3619
3620    fn activate_match(
3621        &mut self,
3622        index: usize,
3623        matches: &[Self::Match],
3624        cx: &mut ViewContext<Self>,
3625    ) {
3626        self.editor.update(cx, |editor, cx| {
3627            editor.activate_match(index, matches, cx);
3628        });
3629    }
3630
3631    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3632        self.editor
3633            .update(cx, |editor, cx| editor.select_matches(matches, cx));
3634    }
3635
3636    fn replace(
3637        &mut self,
3638        identifier: &Self::Match,
3639        query: &project::search::SearchQuery,
3640        cx: &mut ViewContext<Self>,
3641    ) {
3642        self.editor
3643            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
3644    }
3645
3646    fn find_matches(
3647        &mut self,
3648        query: Arc<project::search::SearchQuery>,
3649        cx: &mut ViewContext<Self>,
3650    ) -> Task<Vec<Self::Match>> {
3651        self.editor
3652            .update(cx, |editor, cx| editor.find_matches(query, cx))
3653    }
3654
3655    fn active_match_index(
3656        &mut self,
3657        matches: &[Self::Match],
3658        cx: &mut ViewContext<Self>,
3659    ) -> Option<usize> {
3660        self.editor
3661            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
3662    }
3663}
3664
3665impl FollowableItem for ContextEditor {
3666    fn remote_id(&self) -> Option<workspace::ViewId> {
3667        self.remote_id
3668    }
3669
3670    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
3671        let context = self.context.read(cx);
3672        Some(proto::view::Variant::ContextEditor(
3673            proto::view::ContextEditor {
3674                context_id: context.id().to_proto(),
3675                editor: if let Some(proto::view::Variant::Editor(proto)) =
3676                    self.editor.read(cx).to_state_proto(cx)
3677                {
3678                    Some(proto)
3679                } else {
3680                    None
3681                },
3682            },
3683        ))
3684    }
3685
3686    fn from_state_proto(
3687        workspace: View<Workspace>,
3688        id: workspace::ViewId,
3689        state: &mut Option<proto::view::Variant>,
3690        cx: &mut WindowContext,
3691    ) -> Option<Task<Result<View<Self>>>> {
3692        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
3693            return None;
3694        };
3695        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
3696            unreachable!()
3697        };
3698
3699        let context_id = ContextId::from_proto(state.context_id);
3700        let editor_state = state.editor?;
3701
3702        let (project, panel) = workspace.update(cx, |workspace, cx| {
3703            Some((
3704                workspace.project().clone(),
3705                workspace.panel::<AssistantPanel>(cx)?,
3706            ))
3707        })?;
3708
3709        let context_editor =
3710            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
3711
3712        Some(cx.spawn(|mut cx| async move {
3713            let context_editor = context_editor.await?;
3714            context_editor
3715                .update(&mut cx, |context_editor, cx| {
3716                    context_editor.remote_id = Some(id);
3717                    context_editor.editor.update(cx, |editor, cx| {
3718                        editor.apply_update_proto(
3719                            &project,
3720                            proto::update_view::Variant::Editor(proto::update_view::Editor {
3721                                selections: editor_state.selections,
3722                                pending_selection: editor_state.pending_selection,
3723                                scroll_top_anchor: editor_state.scroll_top_anchor,
3724                                scroll_x: editor_state.scroll_y,
3725                                scroll_y: editor_state.scroll_y,
3726                                ..Default::default()
3727                            }),
3728                            cx,
3729                        )
3730                    })
3731                })?
3732                .await?;
3733            Ok(context_editor)
3734        }))
3735    }
3736
3737    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
3738        Editor::to_follow_event(event)
3739    }
3740
3741    fn add_event_to_update_proto(
3742        &self,
3743        event: &Self::Event,
3744        update: &mut Option<proto::update_view::Variant>,
3745        cx: &WindowContext,
3746    ) -> bool {
3747        self.editor
3748            .read(cx)
3749            .add_event_to_update_proto(event, update, cx)
3750    }
3751
3752    fn apply_update_proto(
3753        &mut self,
3754        project: &Model<Project>,
3755        message: proto::update_view::Variant,
3756        cx: &mut ViewContext<Self>,
3757    ) -> Task<Result<()>> {
3758        self.editor.update(cx, |editor, cx| {
3759            editor.apply_update_proto(project, message, cx)
3760        })
3761    }
3762
3763    fn is_project_item(&self, _cx: &WindowContext) -> bool {
3764        true
3765    }
3766
3767    fn set_leader_peer_id(
3768        &mut self,
3769        leader_peer_id: Option<proto::PeerId>,
3770        cx: &mut ViewContext<Self>,
3771    ) {
3772        self.editor.update(cx, |editor, cx| {
3773            editor.set_leader_peer_id(leader_peer_id, cx)
3774        })
3775    }
3776
3777    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
3778        if existing.context.read(cx).id() == self.context.read(cx).id() {
3779            Some(item::Dedup::KeepExisting)
3780        } else {
3781            None
3782        }
3783    }
3784}
3785
3786pub struct ContextEditorToolbarItem {
3787    fs: Arc<dyn Fs>,
3788    workspace: WeakView<Workspace>,
3789    active_context_editor: Option<WeakView<ContextEditor>>,
3790    model_summary_editor: View<Editor>,
3791}
3792
3793impl ContextEditorToolbarItem {
3794    pub fn new(
3795        workspace: &Workspace,
3796        _model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
3797        model_summary_editor: View<Editor>,
3798    ) -> Self {
3799        Self {
3800            fs: workspace.app_state().fs.clone(),
3801            workspace: workspace.weak_handle(),
3802            active_context_editor: None,
3803            model_summary_editor,
3804        }
3805    }
3806
3807    fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
3808        let commands = SlashCommandRegistry::global(cx);
3809        let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
3810            Some(
3811                workspace
3812                    .read(cx)
3813                    .active_item_as::<Editor>(cx)?
3814                    .focus_handle(cx),
3815            )
3816        });
3817        let active_context_editor = self.active_context_editor.clone();
3818
3819        PopoverMenu::new("inject-context-menu")
3820            .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
3821                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
3822            }))
3823            .menu(move |cx| {
3824                let active_context_editor = active_context_editor.clone()?;
3825                ContextMenu::build(cx, |mut menu, _cx| {
3826                    for command_name in commands.featured_command_names() {
3827                        if let Some(command) = commands.command(&command_name) {
3828                            let menu_text = SharedString::from(Arc::from(command.menu_text()));
3829                            menu = menu.custom_entry(
3830                                {
3831                                    let command_name = command_name.clone();
3832                                    move |_cx| {
3833                                        h_flex()
3834                                            .gap_4()
3835                                            .w_full()
3836                                            .justify_between()
3837                                            .child(Label::new(menu_text.clone()))
3838                                            .child(
3839                                                Label::new(format!("/{command_name}"))
3840                                                    .color(Color::Muted),
3841                                            )
3842                                            .into_any()
3843                                    }
3844                                },
3845                                {
3846                                    let active_context_editor = active_context_editor.clone();
3847                                    move |cx| {
3848                                        active_context_editor
3849                                            .update(cx, |context_editor, cx| {
3850                                                context_editor.insert_command(&command_name, cx)
3851                                            })
3852                                            .ok();
3853                                    }
3854                                },
3855                            )
3856                        }
3857                    }
3858
3859                    if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
3860                        menu = menu
3861                            .context(active_editor_focus_handle)
3862                            .action("Quote Selection", Box::new(QuoteSelection));
3863                    }
3864
3865                    menu
3866                })
3867                .into()
3868            })
3869    }
3870
3871    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
3872        let context = &self
3873            .active_context_editor
3874            .as_ref()?
3875            .upgrade()?
3876            .read(cx)
3877            .context;
3878        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
3879            TokenState::NoTokensLeft {
3880                max_token_count,
3881                token_count,
3882            } => (Color::Error, token_count, max_token_count),
3883            TokenState::HasMoreTokens {
3884                max_token_count,
3885                token_count,
3886                over_warn_threshold,
3887            } => {
3888                let color = if over_warn_threshold {
3889                    Color::Warning
3890                } else {
3891                    Color::Muted
3892                };
3893                (color, token_count, max_token_count)
3894            }
3895        };
3896        Some(
3897            h_flex()
3898                .gap_0p5()
3899                .child(
3900                    Label::new(humanize_token_count(token_count))
3901                        .size(LabelSize::Small)
3902                        .color(token_count_color),
3903                )
3904                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
3905                .child(
3906                    Label::new(humanize_token_count(max_token_count))
3907                        .size(LabelSize::Small)
3908                        .color(Color::Muted),
3909                ),
3910        )
3911    }
3912}
3913
3914impl Render for ContextEditorToolbarItem {
3915    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3916        let left_side = h_flex()
3917            .gap_2()
3918            .flex_1()
3919            .min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
3920            .when(self.active_context_editor.is_some(), |left_side| {
3921                left_side
3922                    .child(
3923                        IconButton::new("regenerate-context", IconName::ArrowCircle)
3924                            .visible_on_hover("toolbar")
3925                            .tooltip(|cx| Tooltip::text("Regenerate Summary", cx))
3926                            .on_click(cx.listener(move |_, _, cx| {
3927                                cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
3928                            })),
3929                    )
3930                    .child(self.model_summary_editor.clone())
3931            });
3932        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
3933        let active_model = LanguageModelRegistry::read_global(cx).active_model();
3934
3935        let right_side = h_flex()
3936            .gap_2()
3937            .child(ModelSelector::new(
3938                self.fs.clone(),
3939                ButtonLike::new("active-model")
3940                    .style(ButtonStyle::Subtle)
3941                    .child(
3942                        h_flex()
3943                            .w_full()
3944                            .gap_0p5()
3945                            .child(
3946                                div()
3947                                    .overflow_x_hidden()
3948                                    .flex_grow()
3949                                    .whitespace_nowrap()
3950                                    .child(match (active_provider, active_model) {
3951                                        (Some(provider), Some(model)) => h_flex()
3952                                            .gap_1()
3953                                            .child(
3954                                                Icon::new(provider.icon())
3955                                                    .color(Color::Muted)
3956                                                    .size(IconSize::XSmall),
3957                                            )
3958                                            .child(
3959                                                Label::new(model.name().0)
3960                                                    .size(LabelSize::Small)
3961                                                    .color(Color::Muted),
3962                                            )
3963                                            .into_any_element(),
3964                                        _ => Label::new("No model selected")
3965                                            .size(LabelSize::Small)
3966                                            .color(Color::Muted)
3967                                            .into_any_element(),
3968                                    }),
3969                            )
3970                            .child(
3971                                Icon::new(IconName::ChevronDown)
3972                                    .color(Color::Muted)
3973                                    .size(IconSize::XSmall),
3974                            ),
3975                    )
3976                    .tooltip(move |cx| {
3977                        Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
3978                    }),
3979            ))
3980            .children(self.render_remaining_tokens(cx))
3981            .child(self.render_inject_context_menu(cx));
3982
3983        h_flex()
3984            .size_full()
3985            .justify_between()
3986            .child(left_side)
3987            .child(right_side)
3988    }
3989}
3990
3991impl ToolbarItemView for ContextEditorToolbarItem {
3992    fn set_active_pane_item(
3993        &mut self,
3994        active_pane_item: Option<&dyn ItemHandle>,
3995        cx: &mut ViewContext<Self>,
3996    ) -> ToolbarItemLocation {
3997        self.active_context_editor = active_pane_item
3998            .and_then(|item| item.act_as::<ContextEditor>(cx))
3999            .map(|editor| editor.downgrade());
4000        cx.notify();
4001        if self.active_context_editor.is_none() {
4002            ToolbarItemLocation::Hidden
4003        } else {
4004            ToolbarItemLocation::PrimaryRight
4005        }
4006    }
4007
4008    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
4009        cx.notify();
4010    }
4011}
4012
4013impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
4014
4015enum ContextEditorToolbarItemEvent {
4016    RegenerateSummary,
4017}
4018impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
4019
4020pub struct ContextHistory {
4021    picker: View<Picker<SavedContextPickerDelegate>>,
4022    _subscriptions: Vec<Subscription>,
4023    assistant_panel: WeakView<AssistantPanel>,
4024}
4025
4026impl ContextHistory {
4027    fn new(
4028        project: Model<Project>,
4029        context_store: Model<ContextStore>,
4030        assistant_panel: WeakView<AssistantPanel>,
4031        cx: &mut ViewContext<Self>,
4032    ) -> Self {
4033        let picker = cx.new_view(|cx| {
4034            Picker::uniform_list(
4035                SavedContextPickerDelegate::new(project, context_store.clone()),
4036                cx,
4037            )
4038            .modal(false)
4039            .max_height(None)
4040        });
4041
4042        let _subscriptions = vec![
4043            cx.observe(&context_store, |this, _, cx| {
4044                this.picker.update(cx, |picker, cx| picker.refresh(cx));
4045            }),
4046            cx.subscribe(&picker, Self::handle_picker_event),
4047        ];
4048
4049        Self {
4050            picker,
4051            _subscriptions,
4052            assistant_panel,
4053        }
4054    }
4055
4056    fn handle_picker_event(
4057        &mut self,
4058        _: View<Picker<SavedContextPickerDelegate>>,
4059        event: &SavedContextPickerEvent,
4060        cx: &mut ViewContext<Self>,
4061    ) {
4062        let SavedContextPickerEvent::Confirmed(context) = event;
4063        self.assistant_panel
4064            .update(cx, |assistant_panel, cx| match context {
4065                ContextMetadata::Remote(metadata) => {
4066                    assistant_panel
4067                        .open_remote_context(metadata.id.clone(), cx)
4068                        .detach_and_log_err(cx);
4069                }
4070                ContextMetadata::Saved(metadata) => {
4071                    assistant_panel
4072                        .open_saved_context(metadata.path.clone(), cx)
4073                        .detach_and_log_err(cx);
4074                }
4075            })
4076            .ok();
4077    }
4078}
4079
4080impl Render for ContextHistory {
4081    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
4082        div().size_full().child(self.picker.clone())
4083    }
4084}
4085
4086impl FocusableView for ContextHistory {
4087    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4088        self.picker.focus_handle(cx)
4089    }
4090}
4091
4092impl EventEmitter<()> for ContextHistory {}
4093
4094impl Item for ContextHistory {
4095    type Event = ();
4096
4097    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4098        Some("History".into())
4099    }
4100}
4101
4102pub struct ConfigurationView {
4103    focus_handle: FocusHandle,
4104    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
4105    _registry_subscription: Subscription,
4106}
4107
4108impl ConfigurationView {
4109    fn new(cx: &mut ViewContext<Self>) -> Self {
4110        let focus_handle = cx.focus_handle();
4111
4112        let registry_subscription = cx.subscribe(
4113            &LanguageModelRegistry::global(cx),
4114            |this, _, event: &language_model::Event, cx| match event {
4115                language_model::Event::AddedProvider(provider_id) => {
4116                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
4117                    if let Some(provider) = provider {
4118                        this.add_configuration_view(&provider, cx);
4119                    }
4120                }
4121                language_model::Event::RemovedProvider(provider_id) => {
4122                    this.remove_configuration_view(provider_id);
4123                }
4124                _ => {}
4125            },
4126        );
4127
4128        let mut this = Self {
4129            focus_handle,
4130            configuration_views: HashMap::default(),
4131            _registry_subscription: registry_subscription,
4132        };
4133        this.build_configuration_views(cx);
4134        this
4135    }
4136
4137    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
4138        let providers = LanguageModelRegistry::read_global(cx).providers();
4139        for provider in providers {
4140            self.add_configuration_view(&provider, cx);
4141        }
4142    }
4143
4144    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
4145        self.configuration_views.remove(provider_id);
4146    }
4147
4148    fn add_configuration_view(
4149        &mut self,
4150        provider: &Arc<dyn LanguageModelProvider>,
4151        cx: &mut ViewContext<Self>,
4152    ) {
4153        let configuration_view = provider.configuration_view(cx);
4154        self.configuration_views
4155            .insert(provider.id(), configuration_view);
4156    }
4157
4158    fn render_provider_view(
4159        &mut self,
4160        provider: &Arc<dyn LanguageModelProvider>,
4161        cx: &mut ViewContext<Self>,
4162    ) -> Div {
4163        let provider_name = provider.name().0.clone();
4164        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
4165
4166        let open_new_context = cx.listener({
4167            let provider = provider.clone();
4168            move |_, _, cx| {
4169                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
4170                    provider.clone(),
4171                ))
4172            }
4173        });
4174
4175        v_flex()
4176            .gap_2()
4177            .child(
4178                h_flex()
4179                    .justify_between()
4180                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
4181                    .when(provider.is_authenticated(cx), move |this| {
4182                        this.child(
4183                            h_flex().justify_end().child(
4184                                Button::new("new-context", "Open new context")
4185                                    .icon_position(IconPosition::Start)
4186                                    .icon(IconName::Plus)
4187                                    .style(ButtonStyle::Filled)
4188                                    .layer(ElevationIndex::ModalSurface)
4189                                    .on_click(open_new_context),
4190                            ),
4191                        )
4192                    }),
4193            )
4194            .child(
4195                div()
4196                    .p(Spacing::Large.rems(cx))
4197                    .bg(cx.theme().colors().surface_background)
4198                    .border_1()
4199                    .border_color(cx.theme().colors().border_variant)
4200                    .rounded_md()
4201                    .when(configuration_view.is_none(), |this| {
4202                        this.child(div().child(Label::new(format!(
4203                            "No configuration view for {}",
4204                            provider_name
4205                        ))))
4206                    })
4207                    .when_some(configuration_view, |this, configuration_view| {
4208                        this.child(configuration_view)
4209                    }),
4210            )
4211    }
4212}
4213
4214impl Render for ConfigurationView {
4215    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4216        let providers = LanguageModelRegistry::read_global(cx).providers();
4217        let provider_views = providers
4218            .into_iter()
4219            .map(|provider| self.render_provider_view(&provider, cx))
4220            .collect::<Vec<_>>();
4221
4222        let mut element = v_flex()
4223            .id("assistant-configuration-view")
4224            .track_focus(&self.focus_handle)
4225            .bg(cx.theme().colors().editor_background)
4226            .size_full()
4227            .overflow_y_scroll()
4228            .child(
4229                v_flex()
4230                    .p(Spacing::XXLarge.rems(cx))
4231                    .border_b_1()
4232                    .border_color(cx.theme().colors().border)
4233                    .gap_1()
4234                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
4235                    .child(
4236                        Label::new(
4237                            "At least one LLM provider must be configured to use the Assistant.",
4238                        )
4239                        .color(Color::Muted),
4240                    ),
4241            )
4242            .child(
4243                v_flex()
4244                    .p(Spacing::XXLarge.rems(cx))
4245                    .mt_1()
4246                    .gap_6()
4247                    .flex_1()
4248                    .children(provider_views),
4249            )
4250            .into_any();
4251
4252        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
4253        // because we couldn't the element to take up the size of the parent.
4254        canvas(
4255            move |bounds, cx| {
4256                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
4257                element
4258            },
4259            |_, mut element, cx| {
4260                element.paint(cx);
4261            },
4262        )
4263        .flex_1()
4264        .w_full()
4265    }
4266}
4267
4268pub enum ConfigurationViewEvent {
4269    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
4270}
4271
4272impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
4273
4274impl FocusableView for ConfigurationView {
4275    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
4276        self.focus_handle.clone()
4277    }
4278}
4279
4280impl Item for ConfigurationView {
4281    type Event = ConfigurationViewEvent;
4282
4283    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4284        Some("Configuration".into())
4285    }
4286}
4287
4288type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
4289
4290fn render_slash_command_output_toggle(
4291    row: MultiBufferRow,
4292    is_folded: bool,
4293    fold: ToggleFold,
4294    _cx: &mut WindowContext,
4295) -> AnyElement {
4296    Disclosure::new(
4297        ("slash-command-output-fold-indicator", row.0 as u64),
4298        !is_folded,
4299    )
4300    .selected(is_folded)
4301    .on_click(move |_e, cx| fold(!is_folded, cx))
4302    .into_any_element()
4303}
4304
4305fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
4306    FoldPlaceholder {
4307        render: Arc::new({
4308            move |fold_id, fold_range, _cx| {
4309                let editor = editor.clone();
4310                ButtonLike::new(fold_id)
4311                    .style(ButtonStyle::Filled)
4312                    .layer(ElevationIndex::ElevatedSurface)
4313                    .child(Icon::new(IconName::FileText))
4314                    .child(Label::new(title.clone()).single_line())
4315                    .on_click(move |_, cx| {
4316                        editor
4317                            .update(cx, |editor, cx| {
4318                                let buffer_start = fold_range
4319                                    .start
4320                                    .to_point(&editor.buffer().read(cx).read(cx));
4321                                let buffer_row = MultiBufferRow(buffer_start.row);
4322                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4323                            })
4324                            .ok();
4325                    })
4326                    .into_any_element()
4327            }
4328        }),
4329        constrain_width: false,
4330        merge_adjacent: false,
4331    }
4332}
4333
4334fn render_quote_selection_output_toggle(
4335    row: MultiBufferRow,
4336    is_folded: bool,
4337    fold: ToggleFold,
4338    _cx: &mut WindowContext,
4339) -> AnyElement {
4340    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
4341        .selected(is_folded)
4342        .on_click(move |_e, cx| fold(!is_folded, cx))
4343        .into_any_element()
4344}
4345
4346fn render_pending_slash_command_gutter_decoration(
4347    row: MultiBufferRow,
4348    status: &PendingSlashCommandStatus,
4349    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
4350) -> AnyElement {
4351    let mut icon = IconButton::new(
4352        ("slash-command-gutter-decoration", row.0),
4353        ui::IconName::TriangleRight,
4354    )
4355    .on_click(move |_e, cx| confirm_command(cx))
4356    .icon_size(ui::IconSize::Small)
4357    .size(ui::ButtonSize::None);
4358
4359    match status {
4360        PendingSlashCommandStatus::Idle => {
4361            icon = icon.icon_color(Color::Muted);
4362        }
4363        PendingSlashCommandStatus::Running { .. } => {
4364            icon = icon.selected(true);
4365        }
4366        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
4367    }
4368
4369    icon.into_any_element()
4370}
4371
4372fn render_docs_slash_command_trailer(
4373    row: MultiBufferRow,
4374    command: PendingSlashCommand,
4375    cx: &mut WindowContext,
4376) -> AnyElement {
4377    let Some(argument) = command.argument else {
4378        return Empty.into_any();
4379    };
4380
4381    let args = DocsSlashCommandArgs::parse(&argument);
4382
4383    let Some(store) = args
4384        .provider()
4385        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
4386    else {
4387        return Empty.into_any();
4388    };
4389
4390    let Some(package) = args.package() else {
4391        return Empty.into_any();
4392    };
4393
4394    let mut children = Vec::new();
4395
4396    if store.is_indexing(&package) {
4397        children.push(
4398            div()
4399                .id(("crates-being-indexed", row.0))
4400                .child(Icon::new(IconName::ArrowCircle).with_animation(
4401                    "arrow-circle",
4402                    Animation::new(Duration::from_secs(4)).repeat(),
4403                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
4404                ))
4405                .tooltip({
4406                    let package = package.clone();
4407                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
4408                })
4409                .into_any_element(),
4410        );
4411    }
4412
4413    if let Some(latest_error) = store.latest_error_for_package(&package) {
4414        children.push(
4415            div()
4416                .id(("latest-error", row.0))
4417                .child(
4418                    Icon::new(IconName::ExclamationTriangle)
4419                        .size(IconSize::Small)
4420                        .color(Color::Warning),
4421                )
4422                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
4423                .into_any_element(),
4424        )
4425    }
4426
4427    let is_indexing = store.is_indexing(&package);
4428    let latest_error = store.latest_error_for_package(&package);
4429
4430    if !is_indexing && latest_error.is_none() {
4431        return Empty.into_any();
4432    }
4433
4434    h_flex().gap_2().children(children).into_any_element()
4435}
4436
4437fn make_lsp_adapter_delegate(
4438    project: &Model<Project>,
4439    cx: &mut AppContext,
4440) -> Result<Arc<dyn LspAdapterDelegate>> {
4441    project.update(cx, |project, cx| {
4442        // TODO: Find the right worktree.
4443        let worktree = project
4444            .worktrees(cx)
4445            .next()
4446            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
4447        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
4448    })
4449}
4450
4451fn slash_command_error_block_renderer(message: String) -> RenderBlock {
4452    Box::new(move |_| {
4453        div()
4454            .pl_6()
4455            .child(
4456                Label::new(format!("error: {}", message))
4457                    .single_line()
4458                    .color(Color::Error),
4459            )
4460            .into_any()
4461    })
4462}
4463
4464enum TokenState {
4465    NoTokensLeft {
4466        max_token_count: usize,
4467        token_count: usize,
4468    },
4469    HasMoreTokens {
4470        max_token_count: usize,
4471        token_count: usize,
4472        over_warn_threshold: bool,
4473    },
4474}
4475
4476fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
4477    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
4478
4479    let model = LanguageModelRegistry::read_global(cx).active_model()?;
4480    let token_count = context.read(cx).token_count()?;
4481    let max_token_count = model.max_token_count();
4482
4483    let remaining_tokens = max_token_count as isize - token_count as isize;
4484    let token_state = if remaining_tokens <= 0 {
4485        TokenState::NoTokensLeft {
4486            max_token_count,
4487            token_count,
4488        }
4489    } else {
4490        let over_warn_threshold =
4491            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
4492        TokenState::HasMoreTokens {
4493            max_token_count,
4494            token_count,
4495            over_warn_threshold,
4496        }
4497    };
4498    Some(token_state)
4499}
4500
4501enum ConfigurationError {
4502    NoProvider,
4503    ProviderNotAuthenticated,
4504}
4505
4506fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
4507    let provider = LanguageModelRegistry::read_global(cx).active_provider();
4508    let is_authenticated = provider
4509        .as_ref()
4510        .map_or(false, |provider| provider.is_authenticated(cx));
4511
4512    if provider.is_some() && is_authenticated {
4513        return None;
4514    }
4515
4516    if provider.is_none() {
4517        return Some(ConfigurationError::NoProvider);
4518    }
4519
4520    if !is_authenticated {
4521        return Some(ConfigurationError::ProviderNotAuthenticated);
4522    }
4523
4524    None
4525}