assistant_panel.rs

   1use crate::{
   2    assistant_settings::{AssistantDockPosition, AssistantSettings},
   3    humanize_token_count,
   4    prompt_library::open_prompt_library,
   5    prompts::PromptBuilder,
   6    slash_command::{
   7        default_command::DefaultSlashCommand,
   8        docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
   9        file_command::{self, codeblock_fence_for_path},
  10        SlashCommandCompletionProvider, SlashCommandRegistry,
  11    },
  12    slash_command_picker,
  13    terminal_inline_assistant::TerminalInlineAssistant,
  14    Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
  15    ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary,
  16    InlineAssistId, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId,
  17    MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
  18    PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
  19    SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
  20};
  21use anyhow::Result;
  22use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
  23use assistant_tool::ToolRegistry;
  24use client::{proto, Client, Status};
  25use collections::{BTreeSet, HashMap, HashSet};
  26use editor::{
  27    actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
  28    display_map::{
  29        BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CreaseMetadata,
  30        CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
  31    },
  32    scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
  33    Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
  34};
  35use editor::{display_map::CreaseId, FoldPlaceholder};
  36use fs::Fs;
  37use futures::FutureExt;
  38use gpui::{
  39    canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
  40    AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
  41    Context as _, Empty, Entity, EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
  42    FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
  43    RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
  44    Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
  45};
  46use indexed_docs::IndexedDocsStore;
  47use language::{
  48    language_settings::SoftWrap, BufferSnapshot, Capability, LanguageRegistry, LspAdapterDelegate,
  49    ToOffset,
  50};
  51use language_model::{
  52    provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
  53    LanguageModelRegistry, Role,
  54};
  55use language_model::{LanguageModelImage, LanguageModelToolUse};
  56use multi_buffer::MultiBufferRow;
  57use picker::{Picker, PickerDelegate};
  58use project::lsp_store::LocalLspAdapterDelegate;
  59use project::{Project, Worktree};
  60use rope::Point;
  61use search::{buffer_search::DivRegistrar, BufferSearchBar};
  62use serde::{Deserialize, Serialize};
  63use settings::{update_settings_file, Settings};
  64use smol::stream::StreamExt;
  65use std::{
  66    borrow::Cow,
  67    cmp,
  68    collections::hash_map,
  69    ops::{ControlFlow, Range},
  70    path::PathBuf,
  71    sync::Arc,
  72    time::Duration,
  73};
  74use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  75use text::SelectionGoal;
  76use ui::TintColor;
  77use ui::{
  78    prelude::*,
  79    utils::{format_distance_from_now, DateTimeType},
  80    Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
  81    ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
  82};
  83use util::{maybe, ResultExt};
  84use workspace::{
  85    dock::{DockPosition, Panel, PanelEvent},
  86    item::{self, FollowableItem, Item, ItemHandle},
  87    notifications::NotificationId,
  88    pane::{self, SaveIntent},
  89    searchable::{SearchEvent, SearchableItem},
  90    DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent,
  91    ToolbarItemLocation, ToolbarItemView, Workspace,
  92};
  93use workspace::{searchable::SearchableItemHandle, DraggedTab};
  94use zed_actions::InlineAssist;
  95
  96pub fn init(cx: &mut AppContext) {
  97    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  98    cx.observe_new_views(
  99        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
 100            workspace
 101                .register_action(|workspace, _: &ToggleFocus, cx| {
 102                    let settings = AssistantSettings::get_global(cx);
 103                    if !settings.enabled {
 104                        return;
 105                    }
 106
 107                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
 108                })
 109                .register_action(AssistantPanel::inline_assist)
 110                .register_action(ContextEditor::quote_selection)
 111                .register_action(ContextEditor::insert_selection)
 112                .register_action(ContextEditor::copy_code)
 113                .register_action(ContextEditor::insert_dragged_files)
 114                .register_action(AssistantPanel::show_configuration)
 115                .register_action(AssistantPanel::create_new_context);
 116        },
 117    )
 118    .detach();
 119
 120    cx.observe_new_views(
 121        |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
 122            let settings = AssistantSettings::get_global(cx);
 123            terminal_panel.asssistant_enabled(settings.enabled, cx);
 124        },
 125    )
 126    .detach();
 127}
 128
 129pub enum AssistantPanelEvent {
 130    ContextEdited,
 131}
 132
 133pub struct AssistantPanel {
 134    pane: View<Pane>,
 135    workspace: WeakView<Workspace>,
 136    width: Option<Pixels>,
 137    height: Option<Pixels>,
 138    project: Model<Project>,
 139    context_store: Model<ContextStore>,
 140    languages: Arc<LanguageRegistry>,
 141    fs: Arc<dyn Fs>,
 142    subscriptions: Vec<Subscription>,
 143    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
 144    model_summary_editor: View<Editor>,
 145    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
 146    configuration_subscription: Option<Subscription>,
 147    client_status: Option<client::Status>,
 148    watch_client_status: Option<Task<()>>,
 149    show_zed_ai_notice: bool,
 150}
 151
 152#[derive(Clone)]
 153enum ContextMetadata {
 154    Remote(RemoteContextMetadata),
 155    Saved(SavedContextMetadata),
 156}
 157
 158struct SavedContextPickerDelegate {
 159    store: Model<ContextStore>,
 160    project: Model<Project>,
 161    matches: Vec<ContextMetadata>,
 162    selected_index: usize,
 163}
 164
 165enum SavedContextPickerEvent {
 166    Confirmed(ContextMetadata),
 167}
 168
 169enum InlineAssistTarget {
 170    Editor(View<Editor>, bool),
 171    Terminal(View<TerminalView>),
 172}
 173
 174impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
 175
 176impl SavedContextPickerDelegate {
 177    fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
 178        Self {
 179            project,
 180            store,
 181            matches: Vec::new(),
 182            selected_index: 0,
 183        }
 184    }
 185}
 186
 187impl PickerDelegate for SavedContextPickerDelegate {
 188    type ListItem = ListItem;
 189
 190    fn match_count(&self) -> usize {
 191        self.matches.len()
 192    }
 193
 194    fn selected_index(&self) -> usize {
 195        self.selected_index
 196    }
 197
 198    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
 199        self.selected_index = ix;
 200    }
 201
 202    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 203        "Search...".into()
 204    }
 205
 206    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 207        let search = self.store.read(cx).search(query, cx);
 208        cx.spawn(|this, mut cx| async move {
 209            let matches = search.await;
 210            this.update(&mut cx, |this, cx| {
 211                let host_contexts = this.delegate.store.read(cx).host_contexts();
 212                this.delegate.matches = host_contexts
 213                    .iter()
 214                    .cloned()
 215                    .map(ContextMetadata::Remote)
 216                    .chain(matches.into_iter().map(ContextMetadata::Saved))
 217                    .collect();
 218                this.delegate.selected_index = 0;
 219                cx.notify();
 220            })
 221            .ok();
 222        })
 223    }
 224
 225    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
 226        if let Some(metadata) = self.matches.get(self.selected_index) {
 227            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
 228        }
 229    }
 230
 231    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 232
 233    fn render_match(
 234        &self,
 235        ix: usize,
 236        selected: bool,
 237        cx: &mut ViewContext<Picker<Self>>,
 238    ) -> Option<Self::ListItem> {
 239        let context = self.matches.get(ix)?;
 240        let item = match context {
 241            ContextMetadata::Remote(context) => {
 242                let host_user = self.project.read(cx).host().and_then(|collaborator| {
 243                    self.project
 244                        .read(cx)
 245                        .user_store()
 246                        .read(cx)
 247                        .get_cached_user(collaborator.user_id)
 248                });
 249                div()
 250                    .flex()
 251                    .w_full()
 252                    .justify_between()
 253                    .gap_2()
 254                    .child(
 255                        h_flex().flex_1().overflow_x_hidden().child(
 256                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
 257                                .size(LabelSize::Small),
 258                        ),
 259                    )
 260                    .child(
 261                        h_flex()
 262                            .gap_2()
 263                            .children(if let Some(host_user) = host_user {
 264                                vec![
 265                                    Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
 266                                    Label::new(format!("Shared by @{}", host_user.github_login))
 267                                        .color(Color::Muted)
 268                                        .size(LabelSize::Small)
 269                                        .into_any_element(),
 270                                ]
 271                            } else {
 272                                vec![Label::new("Shared by host")
 273                                    .color(Color::Muted)
 274                                    .size(LabelSize::Small)
 275                                    .into_any_element()]
 276                            }),
 277                    )
 278            }
 279            ContextMetadata::Saved(context) => div()
 280                .flex()
 281                .w_full()
 282                .justify_between()
 283                .gap_2()
 284                .child(
 285                    h_flex()
 286                        .flex_1()
 287                        .child(Label::new(context.title.clone()).size(LabelSize::Small))
 288                        .overflow_x_hidden(),
 289                )
 290                .child(
 291                    Label::new(format_distance_from_now(
 292                        DateTimeType::Local(context.mtime),
 293                        false,
 294                        true,
 295                        true,
 296                    ))
 297                    .color(Color::Muted)
 298                    .size(LabelSize::Small),
 299                ),
 300        };
 301        Some(
 302            ListItem::new(ix)
 303                .inset(true)
 304                .spacing(ListItemSpacing::Sparse)
 305                .selected(selected)
 306                .child(item),
 307        )
 308    }
 309}
 310
 311impl AssistantPanel {
 312    pub fn load(
 313        workspace: WeakView<Workspace>,
 314        prompt_builder: Arc<PromptBuilder>,
 315        cx: AsyncWindowContext,
 316    ) -> Task<Result<View<Self>>> {
 317        cx.spawn(|mut cx| async move {
 318            let context_store = workspace
 319                .update(&mut cx, |workspace, cx| {
 320                    let project = workspace.project().clone();
 321                    ContextStore::new(project, prompt_builder.clone(), cx)
 322                })?
 323                .await?;
 324
 325            workspace.update(&mut cx, |workspace, cx| {
 326                // TODO: deserialize state.
 327                cx.new_view(|cx| Self::new(workspace, context_store, cx))
 328            })
 329        })
 330    }
 331
 332    fn new(
 333        workspace: &Workspace,
 334        context_store: Model<ContextStore>,
 335        cx: &mut ViewContext<Self>,
 336    ) -> Self {
 337        let model_selector_menu_handle = PopoverMenuHandle::default();
 338        let model_summary_editor = cx.new_view(Editor::single_line);
 339        let context_editor_toolbar = cx.new_view(|_| {
 340            ContextEditorToolbarItem::new(
 341                workspace,
 342                model_selector_menu_handle.clone(),
 343                model_summary_editor.clone(),
 344            )
 345        });
 346
 347        let pane = cx.new_view(|cx| {
 348            let mut pane = Pane::new(
 349                workspace.weak_handle(),
 350                workspace.project().clone(),
 351                Default::default(),
 352                None,
 353                NewContext.boxed_clone(),
 354                cx,
 355            );
 356
 357            let project = workspace.project().clone();
 358            pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
 359                let action = maybe!({
 360                    if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
 361                        return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
 362                    }
 363
 364                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
 365                    {
 366                        if &tab.pane == cx.view() {
 367                            return None;
 368                        }
 369                        let item = tab.pane.read(cx).item_for_index(tab.ix);
 370                        Some(
 371                            item.and_then(|item| item.project_path(cx))
 372                                .into_iter()
 373                                .collect::<Vec<_>>(),
 374                        )
 375                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
 376                    {
 377                        Some(
 378                            selection
 379                                .items()
 380                                .filter_map(|item| {
 381                                    project.read(cx).path_for_entry(item.entry_id, cx)
 382                                })
 383                                .collect::<Vec<_>>(),
 384                        )
 385                    } else {
 386                        None
 387                    }?;
 388
 389                    let paths = project_paths
 390                        .into_iter()
 391                        .filter_map(|project_path| {
 392                            let worktree = project
 393                                .read(cx)
 394                                .worktree_for_id(project_path.worktree_id, cx)?;
 395
 396                            let mut full_path = PathBuf::from(worktree.read(cx).root_name());
 397                            full_path.push(&project_path.path);
 398                            Some(full_path)
 399                        })
 400                        .collect::<Vec<_>>();
 401
 402                    Some(InsertDraggedFiles::ProjectPaths(paths))
 403                });
 404
 405                if let Some(action) = action {
 406                    cx.dispatch_action(action.boxed_clone());
 407                }
 408
 409                ControlFlow::Break(())
 410            });
 411
 412            pane.set_can_split(false, cx);
 413            pane.set_can_navigate(true, cx);
 414            pane.display_nav_history_buttons(None);
 415            pane.set_should_display_tab_bar(|_| true);
 416            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 417                let focus_handle = pane.focus_handle(cx);
 418                let left_children = IconButton::new("history", IconName::HistoryRerun)
 419                    .icon_size(IconSize::Small)
 420                    .on_click(cx.listener({
 421                        let focus_handle = focus_handle.clone();
 422                        move |_, _, cx| {
 423                            focus_handle.focus(cx);
 424                            cx.dispatch_action(DeployHistory.boxed_clone())
 425                        }
 426                    }))
 427                    .tooltip({
 428                        let focus_handle = focus_handle.clone();
 429                        move |cx| {
 430                            Tooltip::for_action_in(
 431                                "Open History",
 432                                &DeployHistory,
 433                                &focus_handle,
 434                                cx,
 435                            )
 436                        }
 437                    })
 438                    .selected(
 439                        pane.active_item()
 440                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 441                    );
 442                let _pane = cx.view().clone();
 443                let right_children = h_flex()
 444                    .gap(Spacing::Small.rems(cx))
 445                    .child(
 446                        IconButton::new("new-context", IconName::Plus)
 447                            .on_click(
 448                                cx.listener(|_, _, cx| {
 449                                    cx.dispatch_action(NewContext.boxed_clone())
 450                                }),
 451                            )
 452                            .tooltip(move |cx| {
 453                                Tooltip::for_action_in(
 454                                    "New Context",
 455                                    &NewContext,
 456                                    &focus_handle,
 457                                    cx,
 458                                )
 459                            }),
 460                    )
 461                    .child(
 462                        PopoverMenu::new("assistant-panel-popover-menu")
 463                            .trigger(
 464                                IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
 465                            )
 466                            .menu(move |cx| {
 467                                let zoom_label = if _pane.read(cx).is_zoomed() {
 468                                    "Zoom Out"
 469                                } else {
 470                                    "Zoom In"
 471                                };
 472                                let focus_handle = _pane.focus_handle(cx);
 473                                Some(ContextMenu::build(cx, move |menu, _| {
 474                                    menu.context(focus_handle.clone())
 475                                        .action("New Context", Box::new(NewContext))
 476                                        .action("History", Box::new(DeployHistory))
 477                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 478                                        .action("Configure", Box::new(ShowConfiguration))
 479                                        .action(zoom_label, Box::new(ToggleZoom))
 480                                }))
 481                            }),
 482                    )
 483                    .into_any_element()
 484                    .into();
 485
 486                (Some(left_children.into_any_element()), right_children)
 487            });
 488            pane.toolbar().update(cx, |toolbar, cx| {
 489                toolbar.add_item(context_editor_toolbar.clone(), cx);
 490                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 491            });
 492            pane
 493        });
 494
 495        let subscriptions = vec![
 496            cx.observe(&pane, |_, _, cx| cx.notify()),
 497            cx.subscribe(&pane, Self::handle_pane_event),
 498            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 499            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 500            cx.subscribe(&context_store, Self::handle_context_store_event),
 501            cx.subscribe(
 502                &LanguageModelRegistry::global(cx),
 503                |this, _, event: &language_model::Event, cx| match event {
 504                    language_model::Event::ActiveModelChanged => {
 505                        this.completion_provider_changed(cx);
 506                    }
 507                    language_model::Event::ProviderStateChanged => {
 508                        this.ensure_authenticated(cx);
 509                        cx.notify()
 510                    }
 511                    language_model::Event::AddedProvider(_)
 512                    | language_model::Event::RemovedProvider(_) => {
 513                        this.ensure_authenticated(cx);
 514                    }
 515                },
 516            ),
 517        ];
 518
 519        let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
 520
 521        let mut this = Self {
 522            pane,
 523            workspace: workspace.weak_handle(),
 524            width: None,
 525            height: None,
 526            project: workspace.project().clone(),
 527            context_store,
 528            languages: workspace.app_state().languages.clone(),
 529            fs: workspace.app_state().fs.clone(),
 530            subscriptions,
 531            model_selector_menu_handle,
 532            model_summary_editor,
 533            authenticate_provider_task: None,
 534            configuration_subscription: None,
 535            client_status: None,
 536            watch_client_status: Some(watch_client_status),
 537            show_zed_ai_notice: false,
 538        };
 539        this.new_context(cx);
 540        this
 541    }
 542
 543    fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
 544        let mut status_rx = client.status();
 545
 546        cx.spawn(|this, mut cx| async move {
 547            while let Some(status) = status_rx.next().await {
 548                this.update(&mut cx, |this, cx| {
 549                    if this.client_status.is_none()
 550                        || this
 551                            .client_status
 552                            .map_or(false, |old_status| old_status != status)
 553                    {
 554                        this.update_zed_ai_notice_visibility(status, cx);
 555                    }
 556                    this.client_status = Some(status);
 557                })
 558                .log_err();
 559            }
 560            this.update(&mut cx, |this, _cx| this.watch_client_status = None)
 561                .log_err();
 562        })
 563    }
 564
 565    fn handle_pane_event(
 566        &mut self,
 567        pane: View<Pane>,
 568        event: &pane::Event,
 569        cx: &mut ViewContext<Self>,
 570    ) {
 571        let update_model_summary = match event {
 572            pane::Event::Remove { .. } => {
 573                cx.emit(PanelEvent::Close);
 574                false
 575            }
 576            pane::Event::ZoomIn => {
 577                cx.emit(PanelEvent::ZoomIn);
 578                false
 579            }
 580            pane::Event::ZoomOut => {
 581                cx.emit(PanelEvent::ZoomOut);
 582                false
 583            }
 584
 585            pane::Event::AddItem { item } => {
 586                self.workspace
 587                    .update(cx, |workspace, cx| {
 588                        item.added_to_pane(workspace, self.pane.clone(), cx)
 589                    })
 590                    .ok();
 591                true
 592            }
 593
 594            pane::Event::ActivateItem { local } => {
 595                if *local {
 596                    self.workspace
 597                        .update(cx, |workspace, cx| {
 598                            workspace.unfollow_in_pane(&pane, cx);
 599                        })
 600                        .ok();
 601                }
 602                cx.emit(AssistantPanelEvent::ContextEdited);
 603                true
 604            }
 605            pane::Event::RemovedItem { .. } => {
 606                let has_configuration_view = self
 607                    .pane
 608                    .read(cx)
 609                    .items_of_type::<ConfigurationView>()
 610                    .next()
 611                    .is_some();
 612
 613                if !has_configuration_view {
 614                    self.configuration_subscription = None;
 615                }
 616
 617                cx.emit(AssistantPanelEvent::ContextEdited);
 618                true
 619            }
 620
 621            _ => false,
 622        };
 623
 624        if update_model_summary {
 625            if let Some(editor) = self.active_context_editor(cx) {
 626                self.show_updated_summary(&editor, cx)
 627            }
 628        }
 629    }
 630
 631    fn handle_summary_editor_event(
 632        &mut self,
 633        model_summary_editor: View<Editor>,
 634        event: &EditorEvent,
 635        cx: &mut ViewContext<Self>,
 636    ) {
 637        if matches!(event, EditorEvent::Edited { .. }) {
 638            if let Some(context_editor) = self.active_context_editor(cx) {
 639                let new_summary = model_summary_editor.read(cx).text(cx);
 640                context_editor.update(cx, |context_editor, cx| {
 641                    context_editor.context.update(cx, |context, cx| {
 642                        if context.summary().is_none()
 643                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 644                        {
 645                            return;
 646                        }
 647                        context.custom_summary(new_summary, cx)
 648                    });
 649                });
 650            }
 651        }
 652    }
 653
 654    fn update_zed_ai_notice_visibility(
 655        &mut self,
 656        client_status: Status,
 657        cx: &mut ViewContext<Self>,
 658    ) {
 659        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
 660
 661        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 662        // the provider, we want to show a nudge to sign in.
 663        let show_zed_ai_notice = client_status.is_signed_out()
 664            && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
 665
 666        self.show_zed_ai_notice = show_zed_ai_notice;
 667        cx.notify();
 668    }
 669
 670    fn handle_toolbar_event(
 671        &mut self,
 672        _: View<ContextEditorToolbarItem>,
 673        _: &ContextEditorToolbarItemEvent,
 674        cx: &mut ViewContext<Self>,
 675    ) {
 676        if let Some(context_editor) = self.active_context_editor(cx) {
 677            context_editor.update(cx, |context_editor, cx| {
 678                context_editor.context.update(cx, |context, cx| {
 679                    context.summarize(true, cx);
 680                })
 681            })
 682        }
 683    }
 684
 685    fn handle_context_store_event(
 686        &mut self,
 687        _context_store: Model<ContextStore>,
 688        event: &ContextStoreEvent,
 689        cx: &mut ViewContext<Self>,
 690    ) {
 691        let ContextStoreEvent::ContextCreated(context_id) = event;
 692        let Some(context) = self
 693            .context_store
 694            .read(cx)
 695            .loaded_context_for_id(&context_id, cx)
 696        else {
 697            log::error!("no context found with ID: {}", context_id.to_proto());
 698            return;
 699        };
 700        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 701            .log_err()
 702            .flatten();
 703
 704        let assistant_panel = cx.view().downgrade();
 705        let editor = cx.new_view(|cx| {
 706            let mut editor = ContextEditor::for_context(
 707                context,
 708                self.fs.clone(),
 709                self.workspace.clone(),
 710                self.project.clone(),
 711                lsp_adapter_delegate,
 712                assistant_panel,
 713                cx,
 714            );
 715            editor.insert_default_prompt(cx);
 716            editor
 717        });
 718
 719        self.show_context(editor.clone(), cx);
 720    }
 721
 722    fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
 723        if let Some(editor) = self.active_context_editor(cx) {
 724            editor.update(cx, |active_context, cx| {
 725                active_context
 726                    .context
 727                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 728            })
 729        }
 730
 731        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 732            .active_provider()
 733            .map(|p| p.id())
 734        else {
 735            return;
 736        };
 737
 738        if self
 739            .authenticate_provider_task
 740            .as_ref()
 741            .map_or(true, |(old_provider_id, _)| {
 742                *old_provider_id != new_provider_id
 743            })
 744        {
 745            self.authenticate_provider_task = None;
 746            self.ensure_authenticated(cx);
 747        }
 748
 749        if let Some(status) = self.client_status {
 750            self.update_zed_ai_notice_visibility(status, cx);
 751        }
 752    }
 753
 754    fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
 755        if self.is_authenticated(cx) {
 756            return;
 757        }
 758
 759        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 760            return;
 761        };
 762
 763        let load_credentials = self.authenticate(cx);
 764
 765        if self.authenticate_provider_task.is_none() {
 766            self.authenticate_provider_task = Some((
 767                provider.id(),
 768                cx.spawn(|this, mut cx| async move {
 769                    if let Some(future) = load_credentials {
 770                        let _ = future.await;
 771                    }
 772                    this.update(&mut cx, |this, _cx| {
 773                        this.authenticate_provider_task = None;
 774                    })
 775                    .log_err();
 776                }),
 777            ));
 778        }
 779    }
 780
 781    pub fn inline_assist(
 782        workspace: &mut Workspace,
 783        action: &InlineAssist,
 784        cx: &mut ViewContext<Workspace>,
 785    ) {
 786        let settings = AssistantSettings::get_global(cx);
 787        if !settings.enabled {
 788            return;
 789        }
 790
 791        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 792            return;
 793        };
 794
 795        let Some(inline_assist_target) =
 796            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 797        else {
 798            return;
 799        };
 800
 801        let initial_prompt = action.prompt.clone();
 802
 803        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 804            match inline_assist_target {
 805                InlineAssistTarget::Editor(active_editor, include_context) => {
 806                    InlineAssistant::update_global(cx, |assistant, cx| {
 807                        assistant.assist(
 808                            &active_editor,
 809                            Some(cx.view().downgrade()),
 810                            include_context.then_some(&assistant_panel),
 811                            initial_prompt,
 812                            cx,
 813                        )
 814                    })
 815                }
 816                InlineAssistTarget::Terminal(active_terminal) => {
 817                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 818                        assistant.assist(
 819                            &active_terminal,
 820                            Some(cx.view().downgrade()),
 821                            Some(&assistant_panel),
 822                            initial_prompt,
 823                            cx,
 824                        )
 825                    })
 826                }
 827            }
 828        } else {
 829            let assistant_panel = assistant_panel.downgrade();
 830            cx.spawn(|workspace, mut cx| async move {
 831                let Some(task) =
 832                    assistant_panel.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 833                else {
 834                    let answer = cx
 835                        .prompt(
 836                            gpui::PromptLevel::Warning,
 837                            "No language model provider configured",
 838                            None,
 839                            &["Configure", "Cancel"],
 840                        )
 841                        .await
 842                        .ok();
 843                    if let Some(answer) = answer {
 844                        if answer == 0 {
 845                            cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration)))
 846                                .ok();
 847                        }
 848                    }
 849                    return Ok(());
 850                };
 851                task.await?;
 852                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 853                    cx.update(|cx| match inline_assist_target {
 854                        InlineAssistTarget::Editor(active_editor, include_context) => {
 855                            let assistant_panel = if include_context {
 856                                assistant_panel.upgrade()
 857                            } else {
 858                                None
 859                            };
 860                            InlineAssistant::update_global(cx, |assistant, cx| {
 861                                assistant.assist(
 862                                    &active_editor,
 863                                    Some(workspace),
 864                                    assistant_panel.as_ref(),
 865                                    initial_prompt,
 866                                    cx,
 867                                )
 868                            })
 869                        }
 870                        InlineAssistTarget::Terminal(active_terminal) => {
 871                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 872                                assistant.assist(
 873                                    &active_terminal,
 874                                    Some(workspace),
 875                                    assistant_panel.upgrade().as_ref(),
 876                                    initial_prompt,
 877                                    cx,
 878                                )
 879                            })
 880                        }
 881                    })?
 882                } else {
 883                    workspace.update(&mut cx, |workspace, cx| {
 884                        workspace.focus_panel::<AssistantPanel>(cx)
 885                    })?;
 886                }
 887
 888                anyhow::Ok(())
 889            })
 890            .detach_and_log_err(cx)
 891        }
 892    }
 893
 894    fn resolve_inline_assist_target(
 895        workspace: &mut Workspace,
 896        assistant_panel: &View<AssistantPanel>,
 897        cx: &mut WindowContext,
 898    ) -> Option<InlineAssistTarget> {
 899        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 900            if terminal_panel
 901                .read(cx)
 902                .focus_handle(cx)
 903                .contains_focused(cx)
 904            {
 905                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 906                    pane.read(cx)
 907                        .active_item()
 908                        .and_then(|t| t.downcast::<TerminalView>())
 909                }) {
 910                    return Some(InlineAssistTarget::Terminal(terminal_view));
 911                }
 912            }
 913        }
 914        let context_editor =
 915            assistant_panel
 916                .read(cx)
 917                .active_context_editor(cx)
 918                .and_then(|editor| {
 919                    let editor = &editor.read(cx).editor;
 920                    if editor.read(cx).is_focused(cx) {
 921                        Some(editor.clone())
 922                    } else {
 923                        None
 924                    }
 925                });
 926
 927        if let Some(context_editor) = context_editor {
 928            Some(InlineAssistTarget::Editor(context_editor, false))
 929        } else if let Some(workspace_editor) = workspace
 930            .active_item(cx)
 931            .and_then(|item| item.act_as::<Editor>(cx))
 932        {
 933            Some(InlineAssistTarget::Editor(workspace_editor, true))
 934        } else if let Some(terminal_view) = workspace
 935            .active_item(cx)
 936            .and_then(|item| item.act_as::<TerminalView>(cx))
 937        {
 938            Some(InlineAssistTarget::Terminal(terminal_view))
 939        } else {
 940            None
 941        }
 942    }
 943
 944    pub fn create_new_context(
 945        workspace: &mut Workspace,
 946        _: &NewContext,
 947        cx: &mut ViewContext<Workspace>,
 948    ) {
 949        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 950            let did_create_context = panel
 951                .update(cx, |panel, cx| {
 952                    panel.new_context(cx)?;
 953
 954                    Some(())
 955                })
 956                .is_some();
 957            if did_create_context {
 958                ContextEditor::quote_selection(workspace, &Default::default(), cx);
 959            }
 960        }
 961    }
 962
 963    fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 964        let project = self.project.read(cx);
 965        if project.is_via_collab() && project.dev_server_project_id().is_none() {
 966            let task = self
 967                .context_store
 968                .update(cx, |store, cx| store.create_remote_context(cx));
 969
 970            cx.spawn(|this, mut cx| async move {
 971                let context = task.await?;
 972
 973                this.update(&mut cx, |this, cx| {
 974                    let workspace = this.workspace.clone();
 975                    let project = this.project.clone();
 976                    let lsp_adapter_delegate =
 977                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 978
 979                    let fs = this.fs.clone();
 980                    let project = this.project.clone();
 981                    let weak_assistant_panel = cx.view().downgrade();
 982
 983                    let editor = cx.new_view(|cx| {
 984                        ContextEditor::for_context(
 985                            context,
 986                            fs,
 987                            workspace,
 988                            project,
 989                            lsp_adapter_delegate,
 990                            weak_assistant_panel,
 991                            cx,
 992                        )
 993                    });
 994
 995                    this.show_context(editor, cx);
 996
 997                    anyhow::Ok(())
 998                })??;
 999
1000                anyhow::Ok(())
1001            })
1002            .detach_and_log_err(cx);
1003
1004            None
1005        } else {
1006            let context = self.context_store.update(cx, |store, cx| store.create(cx));
1007            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1008                .log_err()
1009                .flatten();
1010
1011            let assistant_panel = cx.view().downgrade();
1012            let editor = cx.new_view(|cx| {
1013                let mut editor = ContextEditor::for_context(
1014                    context,
1015                    self.fs.clone(),
1016                    self.workspace.clone(),
1017                    self.project.clone(),
1018                    lsp_adapter_delegate,
1019                    assistant_panel,
1020                    cx,
1021                );
1022                editor.insert_default_prompt(cx);
1023                editor
1024            });
1025
1026            self.show_context(editor.clone(), cx);
1027            let workspace = self.workspace.clone();
1028            cx.spawn(move |_, mut cx| async move {
1029                workspace
1030                    .update(&mut cx, |workspace, cx| {
1031                        workspace.focus_panel::<AssistantPanel>(cx);
1032                    })
1033                    .ok();
1034            })
1035            .detach();
1036            Some(editor)
1037        }
1038    }
1039
1040    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
1041        let focus = self.focus_handle(cx).contains_focused(cx);
1042        let prev_len = self.pane.read(cx).items_len();
1043        self.pane.update(cx, |pane, cx| {
1044            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
1045        });
1046
1047        if prev_len != self.pane.read(cx).items_len() {
1048            self.subscriptions
1049                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
1050        }
1051
1052        self.show_updated_summary(&context_editor, cx);
1053
1054        cx.emit(AssistantPanelEvent::ContextEdited);
1055        cx.notify();
1056    }
1057
1058    fn show_updated_summary(
1059        &self,
1060        context_editor: &View<ContextEditor>,
1061        cx: &mut ViewContext<Self>,
1062    ) {
1063        context_editor.update(cx, |context_editor, cx| {
1064            let new_summary = context_editor.title(cx).to_string();
1065            self.model_summary_editor.update(cx, |summary_editor, cx| {
1066                if summary_editor.text(cx) != new_summary {
1067                    summary_editor.set_text(new_summary, cx);
1068                }
1069            });
1070        });
1071    }
1072
1073    fn handle_context_editor_event(
1074        &mut self,
1075        context_editor: View<ContextEditor>,
1076        event: &EditorEvent,
1077        cx: &mut ViewContext<Self>,
1078    ) {
1079        match event {
1080            EditorEvent::TitleChanged => {
1081                self.show_updated_summary(&context_editor, cx);
1082                cx.notify()
1083            }
1084            EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
1085            _ => {}
1086        }
1087    }
1088
1089    fn show_configuration(
1090        workspace: &mut Workspace,
1091        _: &ShowConfiguration,
1092        cx: &mut ViewContext<Workspace>,
1093    ) {
1094        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1095            return;
1096        };
1097
1098        if !panel.focus_handle(cx).contains_focused(cx) {
1099            workspace.toggle_panel_focus::<AssistantPanel>(cx);
1100        }
1101
1102        panel.update(cx, |this, cx| {
1103            this.show_configuration_tab(cx);
1104        })
1105    }
1106
1107    fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
1108        let configuration_item_ix = self
1109            .pane
1110            .read(cx)
1111            .items()
1112            .position(|item| item.downcast::<ConfigurationView>().is_some());
1113
1114        if let Some(configuration_item_ix) = configuration_item_ix {
1115            self.pane.update(cx, |pane, cx| {
1116                pane.activate_item(configuration_item_ix, true, true, cx);
1117            });
1118        } else {
1119            let configuration = cx.new_view(ConfigurationView::new);
1120            self.configuration_subscription = Some(cx.subscribe(
1121                &configuration,
1122                |this, _, event: &ConfigurationViewEvent, cx| match event {
1123                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
1124                        if LanguageModelRegistry::read_global(cx)
1125                            .active_provider()
1126                            .map_or(true, |p| p.id() != provider.id())
1127                        {
1128                            if let Some(model) = provider.provided_models(cx).first().cloned() {
1129                                update_settings_file::<AssistantSettings>(
1130                                    this.fs.clone(),
1131                                    cx,
1132                                    move |settings, _| settings.set_model(model),
1133                                );
1134                            }
1135                        }
1136
1137                        this.new_context(cx);
1138                    }
1139                },
1140            ));
1141            self.pane.update(cx, |pane, cx| {
1142                pane.add_item(Box::new(configuration), true, true, None, cx);
1143            });
1144        }
1145    }
1146
1147    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
1148        let history_item_ix = self
1149            .pane
1150            .read(cx)
1151            .items()
1152            .position(|item| item.downcast::<ContextHistory>().is_some());
1153
1154        if let Some(history_item_ix) = history_item_ix {
1155            self.pane.update(cx, |pane, cx| {
1156                pane.activate_item(history_item_ix, true, true, cx);
1157            });
1158        } else {
1159            let assistant_panel = cx.view().downgrade();
1160            let history = cx.new_view(|cx| {
1161                ContextHistory::new(
1162                    self.project.clone(),
1163                    self.context_store.clone(),
1164                    assistant_panel,
1165                    cx,
1166                )
1167            });
1168            self.pane.update(cx, |pane, cx| {
1169                pane.add_item(Box::new(history), true, true, None, cx);
1170            });
1171        }
1172    }
1173
1174    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
1175        open_prompt_library(self.languages.clone(), cx).detach_and_log_err(cx);
1176    }
1177
1178    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1179        self.model_selector_menu_handle.toggle(cx);
1180    }
1181
1182    fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1183        self.pane
1184            .read(cx)
1185            .active_item()?
1186            .downcast::<ContextEditor>()
1187    }
1188
1189    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1190        Some(self.active_context_editor(cx)?.read(cx).context.clone())
1191    }
1192
1193    fn open_saved_context(
1194        &mut self,
1195        path: PathBuf,
1196        cx: &mut ViewContext<Self>,
1197    ) -> Task<Result<()>> {
1198        let existing_context = self.pane.read(cx).items().find_map(|item| {
1199            item.downcast::<ContextEditor>()
1200                .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1201        });
1202        if let Some(existing_context) = existing_context {
1203            return cx.spawn(|this, mut cx| async move {
1204                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1205            });
1206        }
1207
1208        let context = self
1209            .context_store
1210            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1211        let fs = self.fs.clone();
1212        let project = self.project.clone();
1213        let workspace = self.workspace.clone();
1214
1215        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1216
1217        cx.spawn(|this, mut cx| async move {
1218            let context = context.await?;
1219            let assistant_panel = this.clone();
1220            this.update(&mut cx, |this, cx| {
1221                let editor = cx.new_view(|cx| {
1222                    ContextEditor::for_context(
1223                        context,
1224                        fs,
1225                        workspace,
1226                        project,
1227                        lsp_adapter_delegate,
1228                        assistant_panel,
1229                        cx,
1230                    )
1231                });
1232                this.show_context(editor, cx);
1233                anyhow::Ok(())
1234            })??;
1235            Ok(())
1236        })
1237    }
1238
1239    fn open_remote_context(
1240        &mut self,
1241        id: ContextId,
1242        cx: &mut ViewContext<Self>,
1243    ) -> Task<Result<View<ContextEditor>>> {
1244        let existing_context = self.pane.read(cx).items().find_map(|item| {
1245            item.downcast::<ContextEditor>()
1246                .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1247        });
1248        if let Some(existing_context) = existing_context {
1249            return cx.spawn(|this, mut cx| async move {
1250                this.update(&mut cx, |this, cx| {
1251                    this.show_context(existing_context.clone(), cx)
1252                })?;
1253                Ok(existing_context)
1254            });
1255        }
1256
1257        let context = self
1258            .context_store
1259            .update(cx, |store, cx| store.open_remote_context(id, cx));
1260        let fs = self.fs.clone();
1261        let workspace = self.workspace.clone();
1262        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1263            .log_err()
1264            .flatten();
1265
1266        cx.spawn(|this, mut cx| async move {
1267            let context = context.await?;
1268            let assistant_panel = this.clone();
1269            this.update(&mut cx, |this, cx| {
1270                let editor = cx.new_view(|cx| {
1271                    ContextEditor::for_context(
1272                        context,
1273                        fs,
1274                        workspace,
1275                        this.project.clone(),
1276                        lsp_adapter_delegate,
1277                        assistant_panel,
1278                        cx,
1279                    )
1280                });
1281                this.show_context(editor.clone(), cx);
1282                anyhow::Ok(editor)
1283            })?
1284        })
1285    }
1286
1287    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1288        LanguageModelRegistry::read_global(cx)
1289            .active_provider()
1290            .map_or(false, |provider| provider.is_authenticated(cx))
1291    }
1292
1293    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1294        LanguageModelRegistry::read_global(cx)
1295            .active_provider()
1296            .map_or(None, |provider| Some(provider.authenticate(cx)))
1297    }
1298}
1299
1300impl Render for AssistantPanel {
1301    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1302        let mut registrar = DivRegistrar::new(
1303            |panel, cx| {
1304                panel
1305                    .pane
1306                    .read(cx)
1307                    .toolbar()
1308                    .read(cx)
1309                    .item_of_type::<BufferSearchBar>()
1310            },
1311            cx,
1312        );
1313        BufferSearchBar::register(&mut registrar);
1314        let registrar = registrar.into_div();
1315
1316        v_flex()
1317            .key_context("AssistantPanel")
1318            .size_full()
1319            .on_action(cx.listener(|this, _: &NewContext, cx| {
1320                this.new_context(cx);
1321            }))
1322            .on_action(
1323                cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1324            )
1325            .on_action(cx.listener(AssistantPanel::deploy_history))
1326            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1327            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1328            .child(registrar.size_full().child(self.pane.clone()))
1329            .into_any_element()
1330    }
1331}
1332
1333impl Panel for AssistantPanel {
1334    fn persistent_name() -> &'static str {
1335        "AssistantPanel"
1336    }
1337
1338    fn position(&self, cx: &WindowContext) -> DockPosition {
1339        match AssistantSettings::get_global(cx).dock {
1340            AssistantDockPosition::Left => DockPosition::Left,
1341            AssistantDockPosition::Bottom => DockPosition::Bottom,
1342            AssistantDockPosition::Right => DockPosition::Right,
1343        }
1344    }
1345
1346    fn position_is_valid(&self, _: DockPosition) -> bool {
1347        true
1348    }
1349
1350    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1351        settings::update_settings_file::<AssistantSettings>(
1352            self.fs.clone(),
1353            cx,
1354            move |settings, _| {
1355                let dock = match position {
1356                    DockPosition::Left => AssistantDockPosition::Left,
1357                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1358                    DockPosition::Right => AssistantDockPosition::Right,
1359                };
1360                settings.set_dock(dock);
1361            },
1362        );
1363    }
1364
1365    fn size(&self, cx: &WindowContext) -> Pixels {
1366        let settings = AssistantSettings::get_global(cx);
1367        match self.position(cx) {
1368            DockPosition::Left | DockPosition::Right => {
1369                self.width.unwrap_or(settings.default_width)
1370            }
1371            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1372        }
1373    }
1374
1375    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1376        match self.position(cx) {
1377            DockPosition::Left | DockPosition::Right => self.width = size,
1378            DockPosition::Bottom => self.height = size,
1379        }
1380        cx.notify();
1381    }
1382
1383    fn is_zoomed(&self, cx: &WindowContext) -> bool {
1384        self.pane.read(cx).is_zoomed()
1385    }
1386
1387    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1388        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1389    }
1390
1391    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1392        if active {
1393            if self.pane.read(cx).items_len() == 0 {
1394                self.new_context(cx);
1395            }
1396
1397            self.ensure_authenticated(cx);
1398        }
1399    }
1400
1401    fn pane(&self) -> Option<View<Pane>> {
1402        Some(self.pane.clone())
1403    }
1404
1405    fn remote_id() -> Option<proto::PanelId> {
1406        Some(proto::PanelId::AssistantPanel)
1407    }
1408
1409    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1410        let settings = AssistantSettings::get_global(cx);
1411        if !settings.enabled || !settings.button {
1412            return None;
1413        }
1414
1415        Some(IconName::ZedAssistant)
1416    }
1417
1418    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1419        Some("Assistant Panel")
1420    }
1421
1422    fn toggle_action(&self) -> Box<dyn Action> {
1423        Box::new(ToggleFocus)
1424    }
1425}
1426
1427impl EventEmitter<PanelEvent> for AssistantPanel {}
1428impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1429
1430impl FocusableView for AssistantPanel {
1431    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1432        self.pane.focus_handle(cx)
1433    }
1434}
1435
1436pub enum ContextEditorEvent {
1437    Edited,
1438    TabContentChanged,
1439}
1440
1441#[derive(Copy, Clone, Debug, PartialEq)]
1442struct ScrollPosition {
1443    offset_before_cursor: gpui::Point<f32>,
1444    cursor: Anchor,
1445}
1446
1447struct WorkflowStepViewState {
1448    header_block_id: CustomBlockId,
1449    header_crease_id: CreaseId,
1450    footer_block_id: Option<CustomBlockId>,
1451    footer_crease_id: Option<CreaseId>,
1452    assist: Option<WorkflowAssist>,
1453    resolution: Option<Arc<Result<WorkflowStepResolution>>>,
1454}
1455
1456impl WorkflowStepViewState {
1457    fn status(&self, cx: &AppContext) -> WorkflowStepStatus {
1458        if let Some(assist) = &self.assist {
1459            match assist.status(cx) {
1460                WorkflowAssistStatus::Idle => WorkflowStepStatus::Idle,
1461                WorkflowAssistStatus::Pending => WorkflowStepStatus::Pending,
1462                WorkflowAssistStatus::Done => WorkflowStepStatus::Done,
1463                WorkflowAssistStatus::Confirmed => WorkflowStepStatus::Confirmed,
1464            }
1465        } else if let Some(resolution) = self.resolution.as_deref() {
1466            match resolution {
1467                Err(err) => WorkflowStepStatus::Error(err),
1468                Ok(_) => WorkflowStepStatus::Idle,
1469            }
1470        } else {
1471            WorkflowStepStatus::Resolving
1472        }
1473    }
1474}
1475
1476#[derive(Clone, Copy)]
1477enum WorkflowStepStatus<'a> {
1478    Resolving,
1479    Error(&'a anyhow::Error),
1480    Idle,
1481    Pending,
1482    Done,
1483    Confirmed,
1484}
1485
1486impl<'a> WorkflowStepStatus<'a> {
1487    pub(crate) fn is_confirmed(&self) -> bool {
1488        matches!(self, Self::Confirmed)
1489    }
1490}
1491
1492#[derive(Debug, Eq, PartialEq)]
1493struct ActiveWorkflowStep {
1494    range: Range<language::Anchor>,
1495    resolved: bool,
1496}
1497
1498struct WorkflowAssist {
1499    editor: WeakView<Editor>,
1500    editor_was_open: bool,
1501    assist_ids: Vec<InlineAssistId>,
1502}
1503
1504type MessageHeader = MessageMetadata;
1505
1506#[derive(Clone)]
1507enum AssistError {
1508    PaymentRequired,
1509    MaxMonthlySpendReached,
1510    Message(SharedString),
1511}
1512
1513pub struct ContextEditor {
1514    context: Model<Context>,
1515    fs: Arc<dyn Fs>,
1516    workspace: WeakView<Workspace>,
1517    project: Model<Project>,
1518    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1519    editor: View<Editor>,
1520    blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
1521    image_blocks: HashSet<CustomBlockId>,
1522    scroll_position: Option<ScrollPosition>,
1523    remote_id: Option<workspace::ViewId>,
1524    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1525    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1526    pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
1527    _subscriptions: Vec<Subscription>,
1528    workflow_steps: HashMap<Range<language::Anchor>, WorkflowStepViewState>,
1529    active_workflow_step: Option<ActiveWorkflowStep>,
1530    assistant_panel: WeakView<AssistantPanel>,
1531    last_error: Option<AssistError>,
1532    show_accept_terms: bool,
1533    pub(crate) slash_menu_handle:
1534        PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
1535    // dragged_file_worktrees is used to keep references to worktrees that were added
1536    // when the user drag/dropped an external file onto the context editor. Since
1537    // the worktree is not part of the project panel, it would be dropped as soon as
1538    // the file is opened. In order to keep the worktree alive for the duration of the
1539    // context editor, we keep a reference here.
1540    dragged_file_worktrees: Vec<Model<Worktree>>,
1541}
1542
1543const DEFAULT_TAB_TITLE: &str = "New Context";
1544const MAX_TAB_TITLE_LEN: usize = 16;
1545
1546impl ContextEditor {
1547    fn for_context(
1548        context: Model<Context>,
1549        fs: Arc<dyn Fs>,
1550        workspace: WeakView<Workspace>,
1551        project: Model<Project>,
1552        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1553        assistant_panel: WeakView<AssistantPanel>,
1554        cx: &mut ViewContext<Self>,
1555    ) -> Self {
1556        let completion_provider = SlashCommandCompletionProvider::new(
1557            Some(cx.view().downgrade()),
1558            Some(workspace.clone()),
1559        );
1560
1561        let editor = cx.new_view(|cx| {
1562            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1563            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1564            editor.set_show_line_numbers(false, cx);
1565            editor.set_show_git_diff_gutter(false, cx);
1566            editor.set_show_code_actions(false, cx);
1567            editor.set_show_runnables(false, cx);
1568            editor.set_show_wrap_guides(false, cx);
1569            editor.set_show_indent_guides(false, cx);
1570            editor.set_completion_provider(Some(Box::new(completion_provider)));
1571            editor.set_collaboration_hub(Box::new(project.clone()));
1572            editor
1573        });
1574
1575        let _subscriptions = vec![
1576            cx.observe(&context, |_, _, cx| cx.notify()),
1577            cx.subscribe(&context, Self::handle_context_event),
1578            cx.subscribe(&editor, Self::handle_editor_event),
1579            cx.subscribe(&editor, Self::handle_editor_search_event),
1580        ];
1581
1582        let sections = context.read(cx).slash_command_output_sections().to_vec();
1583        let edit_step_ranges = context.read(cx).workflow_step_ranges().collect::<Vec<_>>();
1584        let mut this = Self {
1585            context,
1586            editor,
1587            lsp_adapter_delegate,
1588            blocks: Default::default(),
1589            image_blocks: Default::default(),
1590            scroll_position: None,
1591            remote_id: None,
1592            fs,
1593            workspace,
1594            project,
1595            pending_slash_command_creases: HashMap::default(),
1596            pending_slash_command_blocks: HashMap::default(),
1597            pending_tool_use_creases: HashMap::default(),
1598            _subscriptions,
1599            workflow_steps: HashMap::default(),
1600            active_workflow_step: None,
1601            assistant_panel,
1602            last_error: None,
1603            show_accept_terms: false,
1604            slash_menu_handle: Default::default(),
1605            dragged_file_worktrees: Vec::new(),
1606        };
1607        this.update_message_headers(cx);
1608        this.update_image_blocks(cx);
1609        this.insert_slash_command_output_sections(sections, false, cx);
1610        this.workflow_steps_updated(&Vec::new(), &edit_step_ranges, cx);
1611        this
1612    }
1613
1614    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1615        let command_name = DefaultSlashCommand.name();
1616        self.editor.update(cx, |editor, cx| {
1617            editor.insert(&format!("/{command_name}\n\n"), cx)
1618        });
1619        let command = self.context.update(cx, |context, cx| {
1620            context.reparse(cx);
1621            context.pending_slash_commands()[0].clone()
1622        });
1623        self.run_command(
1624            command.source_range,
1625            &command.name,
1626            &command.arguments,
1627            false,
1628            false,
1629            self.workspace.clone(),
1630            cx,
1631        );
1632    }
1633
1634    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1635        let provider = LanguageModelRegistry::read_global(cx).active_provider();
1636        if provider
1637            .as_ref()
1638            .map_or(false, |provider| provider.must_accept_terms(cx))
1639        {
1640            self.show_accept_terms = true;
1641            cx.notify();
1642            return;
1643        }
1644
1645        if !self.apply_active_workflow_step(cx) {
1646            self.last_error = None;
1647            self.send_to_model(cx);
1648            cx.notify();
1649        }
1650    }
1651
1652    fn apply_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1653        self.show_workflow_step(range.clone(), cx);
1654
1655        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1656            if let Some(assist) = workflow_step.assist.as_ref() {
1657                let assist_ids = assist.assist_ids.clone();
1658                cx.spawn(|this, mut cx| async move {
1659                    for assist_id in assist_ids {
1660                        let mut receiver = this.update(&mut cx, |_, cx| {
1661                            cx.window_context().defer(move |cx| {
1662                                InlineAssistant::update_global(cx, |assistant, cx| {
1663                                    assistant.start_assist(assist_id, cx);
1664                                })
1665                            });
1666                            InlineAssistant::update_global(cx, |assistant, _| {
1667                                assistant.observe_assist(assist_id)
1668                            })
1669                        })?;
1670                        while !receiver.borrow().is_done() {
1671                            let _ = receiver.changed().await;
1672                        }
1673                    }
1674                    anyhow::Ok(())
1675                })
1676                .detach_and_log_err(cx);
1677            }
1678        }
1679    }
1680
1681    fn apply_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
1682        let Some((range, step)) = self.active_workflow_step() else {
1683            return false;
1684        };
1685
1686        if let Some(assist) = step.assist.as_ref() {
1687            match assist.status(cx) {
1688                WorkflowAssistStatus::Pending => {}
1689                WorkflowAssistStatus::Confirmed => return false,
1690                WorkflowAssistStatus::Done => self.confirm_workflow_step(range, cx),
1691                WorkflowAssistStatus::Idle => self.apply_workflow_step(range, cx),
1692            }
1693        } else {
1694            match step.resolution.as_deref() {
1695                Some(Ok(_)) => self.apply_workflow_step(range, cx),
1696                Some(Err(_)) => self.resolve_workflow_step(range, cx),
1697                None => {}
1698            }
1699        }
1700
1701        true
1702    }
1703
1704    fn resolve_workflow_step(
1705        &mut self,
1706        range: Range<language::Anchor>,
1707        cx: &mut ViewContext<Self>,
1708    ) {
1709        self.context
1710            .update(cx, |context, cx| context.resolve_workflow_step(range, cx));
1711    }
1712
1713    fn stop_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1714        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1715            if let Some(assist) = workflow_step.assist.as_ref() {
1716                let assist_ids = assist.assist_ids.clone();
1717                cx.window_context().defer(|cx| {
1718                    InlineAssistant::update_global(cx, |assistant, cx| {
1719                        for assist_id in assist_ids {
1720                            assistant.stop_assist(assist_id, cx);
1721                        }
1722                    })
1723                });
1724            }
1725        }
1726    }
1727
1728    fn undo_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1729        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1730            if let Some(assist) = workflow_step.assist.take() {
1731                cx.window_context().defer(|cx| {
1732                    InlineAssistant::update_global(cx, |assistant, cx| {
1733                        for assist_id in assist.assist_ids {
1734                            assistant.undo_assist(assist_id, cx);
1735                        }
1736                    })
1737                });
1738            }
1739        }
1740    }
1741
1742    fn confirm_workflow_step(
1743        &mut self,
1744        range: Range<language::Anchor>,
1745        cx: &mut ViewContext<Self>,
1746    ) {
1747        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1748            if let Some(assist) = workflow_step.assist.as_ref() {
1749                let assist_ids = assist.assist_ids.clone();
1750                cx.window_context().defer(move |cx| {
1751                    InlineAssistant::update_global(cx, |assistant, cx| {
1752                        for assist_id in assist_ids {
1753                            assistant.finish_assist(assist_id, false, cx);
1754                        }
1755                    })
1756                });
1757            }
1758        }
1759    }
1760
1761    fn reject_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1762        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1763            if let Some(assist) = workflow_step.assist.take() {
1764                cx.window_context().defer(move |cx| {
1765                    InlineAssistant::update_global(cx, |assistant, cx| {
1766                        for assist_id in assist.assist_ids {
1767                            assistant.finish_assist(assist_id, true, cx);
1768                        }
1769                    })
1770                });
1771            }
1772        }
1773    }
1774
1775    fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
1776        if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
1777            let new_selection = {
1778                let cursor = user_message
1779                    .start
1780                    .to_offset(self.context.read(cx).buffer().read(cx));
1781                cursor..cursor
1782            };
1783            self.editor.update(cx, |editor, cx| {
1784                editor.change_selections(
1785                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1786                    cx,
1787                    |selections| selections.select_ranges([new_selection]),
1788                );
1789            });
1790            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1791            cx.defer(|this, _| this.scroll_position = None);
1792        }
1793    }
1794
1795    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1796        self.last_error = None;
1797
1798        if self
1799            .context
1800            .update(cx, |context, cx| context.cancel_last_assist(cx))
1801        {
1802            return;
1803        }
1804
1805        if let Some((range, active_step)) = self.active_workflow_step() {
1806            match active_step.status(cx) {
1807                WorkflowStepStatus::Pending => {
1808                    self.stop_workflow_step(range, cx);
1809                    return;
1810                }
1811                WorkflowStepStatus::Done => {
1812                    self.reject_workflow_step(range, cx);
1813                    return;
1814                }
1815                _ => {}
1816            }
1817        }
1818        cx.propagate();
1819    }
1820
1821    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
1822        let cursors = self.cursors(cx);
1823        self.context.update(cx, |context, cx| {
1824            let messages = context
1825                .messages_for_offsets(cursors, cx)
1826                .into_iter()
1827                .map(|message| message.id)
1828                .collect();
1829            context.cycle_message_roles(messages, cx)
1830        });
1831    }
1832
1833    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
1834        let selections = self.editor.read(cx).selections.all::<usize>(cx);
1835        selections
1836            .into_iter()
1837            .map(|selection| selection.head())
1838            .collect()
1839    }
1840
1841    pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
1842        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1843            self.editor.update(cx, |editor, cx| {
1844                editor.transact(cx, |editor, cx| {
1845                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
1846                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1847                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
1848                    if newest_cursor.column > 0
1849                        || snapshot
1850                            .chars_at(newest_cursor)
1851                            .next()
1852                            .map_or(false, |ch| ch != '\n')
1853                    {
1854                        editor.move_to_end_of_line(
1855                            &MoveToEndOfLine {
1856                                stop_at_soft_wraps: false,
1857                            },
1858                            cx,
1859                        );
1860                        editor.newline(&Newline, cx);
1861                    }
1862
1863                    editor.insert(&format!("/{name}"), cx);
1864                    if command.accepts_arguments() {
1865                        editor.insert(" ", cx);
1866                        editor.show_completions(&ShowCompletions::default(), cx);
1867                    }
1868                });
1869            });
1870            if !command.requires_argument() {
1871                self.confirm_command(&ConfirmCommand, cx);
1872            }
1873        }
1874    }
1875
1876    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
1877        if self.editor.read(cx).has_active_completions_menu() {
1878            return;
1879        }
1880
1881        let selections = self.editor.read(cx).selections.disjoint_anchors();
1882        let mut commands_by_range = HashMap::default();
1883        let workspace = self.workspace.clone();
1884        self.context.update(cx, |context, cx| {
1885            context.reparse(cx);
1886            for selection in selections.iter() {
1887                if let Some(command) =
1888                    context.pending_command_for_position(selection.head().text_anchor, cx)
1889                {
1890                    commands_by_range
1891                        .entry(command.source_range.clone())
1892                        .or_insert_with(|| command.clone());
1893                }
1894            }
1895        });
1896
1897        if commands_by_range.is_empty() {
1898            cx.propagate();
1899        } else {
1900            for command in commands_by_range.into_values() {
1901                self.run_command(
1902                    command.source_range,
1903                    &command.name,
1904                    &command.arguments,
1905                    true,
1906                    false,
1907                    workspace.clone(),
1908                    cx,
1909                );
1910            }
1911            cx.stop_propagation();
1912        }
1913    }
1914
1915    #[allow(clippy::too_many_arguments)]
1916    pub fn run_command(
1917        &mut self,
1918        command_range: Range<language::Anchor>,
1919        name: &str,
1920        arguments: &[String],
1921        ensure_trailing_newline: bool,
1922        expand_result: bool,
1923        workspace: WeakView<Workspace>,
1924        cx: &mut ViewContext<Self>,
1925    ) {
1926        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1927            let context = self.context.read(cx);
1928            let sections = context
1929                .slash_command_output_sections()
1930                .into_iter()
1931                .filter(|section| section.is_valid(context.buffer().read(cx)))
1932                .cloned()
1933                .collect::<Vec<_>>();
1934            let snapshot = context.buffer().read(cx).snapshot();
1935            let output = command.run(
1936                arguments,
1937                &sections,
1938                snapshot,
1939                workspace,
1940                self.lsp_adapter_delegate.clone(),
1941                cx,
1942            );
1943            self.context.update(cx, |context, cx| {
1944                context.insert_command_output(
1945                    command_range,
1946                    output,
1947                    ensure_trailing_newline,
1948                    expand_result,
1949                    cx,
1950                )
1951            });
1952        }
1953    }
1954
1955    fn handle_context_event(
1956        &mut self,
1957        _: Model<Context>,
1958        event: &ContextEvent,
1959        cx: &mut ViewContext<Self>,
1960    ) {
1961        let context_editor = cx.view().downgrade();
1962
1963        match event {
1964            ContextEvent::MessagesEdited => {
1965                self.update_message_headers(cx);
1966                self.update_image_blocks(cx);
1967                self.context.update(cx, |context, cx| {
1968                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1969                });
1970            }
1971            ContextEvent::SummaryChanged => {
1972                cx.emit(EditorEvent::TitleChanged);
1973                self.context.update(cx, |context, cx| {
1974                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1975                });
1976            }
1977            ContextEvent::StreamedCompletion => {
1978                self.editor.update(cx, |editor, cx| {
1979                    if let Some(scroll_position) = self.scroll_position {
1980                        let snapshot = editor.snapshot(cx);
1981                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
1982                        let scroll_top =
1983                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
1984                        editor.set_scroll_position(
1985                            point(scroll_position.offset_before_cursor.x, scroll_top),
1986                            cx,
1987                        );
1988                    }
1989
1990                    let new_tool_uses = self
1991                        .context
1992                        .read(cx)
1993                        .pending_tool_uses()
1994                        .into_iter()
1995                        .filter(|tool_use| {
1996                            !self
1997                                .pending_tool_use_creases
1998                                .contains_key(&tool_use.source_range)
1999                        })
2000                        .cloned()
2001                        .collect::<Vec<_>>();
2002
2003                    let buffer = editor.buffer().read(cx).snapshot(cx);
2004                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
2005                    let excerpt_id = *excerpt_id;
2006
2007                    let mut buffer_rows_to_fold = BTreeSet::new();
2008
2009                    let creases = new_tool_uses
2010                        .iter()
2011                        .map(|tool_use| {
2012                            let placeholder = FoldPlaceholder {
2013                                render: render_fold_icon_button(
2014                                    cx.view().downgrade(),
2015                                    IconName::PocketKnife,
2016                                    tool_use.name.clone().into(),
2017                                ),
2018                                constrain_width: false,
2019                                merge_adjacent: false,
2020                            };
2021                            let render_trailer =
2022                                move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2023
2024                            let start = buffer
2025                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
2026                                .unwrap();
2027                            let end = buffer
2028                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
2029                                .unwrap();
2030
2031                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2032                            buffer_rows_to_fold.insert(buffer_row);
2033
2034                            self.context.update(cx, |context, cx| {
2035                                context.insert_content(
2036                                    Content::ToolUse {
2037                                        range: tool_use.source_range.clone(),
2038                                        tool_use: LanguageModelToolUse {
2039                                            id: tool_use.id.to_string(),
2040                                            name: tool_use.name.clone(),
2041                                            input: tool_use.input.clone(),
2042                                        },
2043                                    },
2044                                    cx,
2045                                );
2046                            });
2047
2048                            Crease::new(
2049                                start..end,
2050                                placeholder,
2051                                fold_toggle("tool-use"),
2052                                render_trailer,
2053                            )
2054                        })
2055                        .collect::<Vec<_>>();
2056
2057                    let crease_ids = editor.insert_creases(creases, cx);
2058
2059                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2060                        editor.fold_at(&FoldAt { buffer_row }, cx);
2061                    }
2062
2063                    self.pending_tool_use_creases.extend(
2064                        new_tool_uses
2065                            .iter()
2066                            .map(|tool_use| tool_use.source_range.clone())
2067                            .zip(crease_ids),
2068                    );
2069                });
2070            }
2071            ContextEvent::WorkflowStepsUpdated { removed, updated } => {
2072                self.workflow_steps_updated(removed, updated, cx);
2073            }
2074            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2075                self.editor.update(cx, |editor, cx| {
2076                    let buffer = editor.buffer().read(cx).snapshot(cx);
2077                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
2078                    let excerpt_id = *excerpt_id;
2079
2080                    editor.remove_creases(
2081                        removed
2082                            .iter()
2083                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
2084                        cx,
2085                    );
2086
2087                    editor.remove_blocks(
2088                        HashSet::from_iter(
2089                            removed.iter().filter_map(|range| {
2090                                self.pending_slash_command_blocks.remove(range)
2091                            }),
2092                        ),
2093                        None,
2094                        cx,
2095                    );
2096
2097                    let crease_ids = editor.insert_creases(
2098                        updated.iter().map(|command| {
2099                            let workspace = self.workspace.clone();
2100                            let confirm_command = Arc::new({
2101                                let context_editor = context_editor.clone();
2102                                let command = command.clone();
2103                                move |cx: &mut WindowContext| {
2104                                    context_editor
2105                                        .update(cx, |context_editor, cx| {
2106                                            context_editor.run_command(
2107                                                command.source_range.clone(),
2108                                                &command.name,
2109                                                &command.arguments,
2110                                                false,
2111                                                false,
2112                                                workspace.clone(),
2113                                                cx,
2114                                            );
2115                                        })
2116                                        .ok();
2117                                }
2118                            });
2119                            let placeholder = FoldPlaceholder {
2120                                render: Arc::new(move |_, _, _| Empty.into_any()),
2121                                constrain_width: false,
2122                                merge_adjacent: false,
2123                            };
2124                            let render_toggle = {
2125                                let confirm_command = confirm_command.clone();
2126                                let command = command.clone();
2127                                move |row, _, _, _cx: &mut WindowContext| {
2128                                    render_pending_slash_command_gutter_decoration(
2129                                        row,
2130                                        &command.status,
2131                                        confirm_command.clone(),
2132                                    )
2133                                }
2134                            };
2135                            let render_trailer = {
2136                                let command = command.clone();
2137                                move |row, _unfold, cx: &mut WindowContext| {
2138                                    // TODO: In the future we should investigate how we can expose
2139                                    // this as a hook on the `SlashCommand` trait so that we don't
2140                                    // need to special-case it here.
2141                                    if command.name == DocsSlashCommand::NAME {
2142                                        return render_docs_slash_command_trailer(
2143                                            row,
2144                                            command.clone(),
2145                                            cx,
2146                                        );
2147                                    }
2148
2149                                    Empty.into_any()
2150                                }
2151                            };
2152
2153                            let start = buffer
2154                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2155                                .unwrap();
2156                            let end = buffer
2157                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2158                                .unwrap();
2159                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2160                        }),
2161                        cx,
2162                    );
2163
2164                    let block_ids = editor.insert_blocks(
2165                        updated
2166                            .iter()
2167                            .filter_map(|command| match &command.status {
2168                                PendingSlashCommandStatus::Error(error) => {
2169                                    Some((command, error.clone()))
2170                                }
2171                                _ => None,
2172                            })
2173                            .map(|(command, error_message)| BlockProperties {
2174                                style: BlockStyle::Fixed,
2175                                position: Anchor {
2176                                    buffer_id: Some(buffer_id),
2177                                    excerpt_id,
2178                                    text_anchor: command.source_range.start,
2179                                },
2180                                height: 1,
2181                                disposition: BlockDisposition::Below,
2182                                render: slash_command_error_block_renderer(error_message),
2183                                priority: 0,
2184                            }),
2185                        None,
2186                        cx,
2187                    );
2188
2189                    self.pending_slash_command_creases.extend(
2190                        updated
2191                            .iter()
2192                            .map(|command| command.source_range.clone())
2193                            .zip(crease_ids),
2194                    );
2195
2196                    self.pending_slash_command_blocks.extend(
2197                        updated
2198                            .iter()
2199                            .map(|command| command.source_range.clone())
2200                            .zip(block_ids),
2201                    );
2202                })
2203            }
2204            ContextEvent::SlashCommandFinished {
2205                output_range,
2206                sections,
2207                run_commands_in_output,
2208                expand_result,
2209            } => {
2210                self.insert_slash_command_output_sections(
2211                    sections.iter().cloned(),
2212                    *expand_result,
2213                    cx,
2214                );
2215
2216                if *run_commands_in_output {
2217                    let commands = self.context.update(cx, |context, cx| {
2218                        context.reparse(cx);
2219                        context
2220                            .pending_commands_for_range(output_range.clone(), cx)
2221                            .to_vec()
2222                    });
2223
2224                    for command in commands {
2225                        self.run_command(
2226                            command.source_range,
2227                            &command.name,
2228                            &command.arguments,
2229                            false,
2230                            false,
2231                            self.workspace.clone(),
2232                            cx,
2233                        );
2234                    }
2235                }
2236            }
2237            ContextEvent::UsePendingTools => {
2238                let pending_tool_uses = self
2239                    .context
2240                    .read(cx)
2241                    .pending_tool_uses()
2242                    .into_iter()
2243                    .filter(|tool_use| tool_use.status.is_idle())
2244                    .cloned()
2245                    .collect::<Vec<_>>();
2246
2247                for tool_use in pending_tool_uses {
2248                    let tool_registry = ToolRegistry::global(cx);
2249                    if let Some(tool) = tool_registry.tool(&tool_use.name) {
2250                        let task = tool.run(tool_use.input, self.workspace.clone(), cx);
2251
2252                        self.context.update(cx, |context, cx| {
2253                            context.insert_tool_output(tool_use.id.clone(), task, cx);
2254                        });
2255                    }
2256                }
2257            }
2258            ContextEvent::ToolFinished {
2259                tool_use_id,
2260                output_range,
2261            } => {
2262                self.editor.update(cx, |editor, cx| {
2263                    let buffer = editor.buffer().read(cx).snapshot(cx);
2264                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
2265                    let excerpt_id = *excerpt_id;
2266
2267                    let placeholder = FoldPlaceholder {
2268                        render: render_fold_icon_button(
2269                            cx.view().downgrade(),
2270                            IconName::PocketKnife,
2271                            format!("Tool Result: {tool_use_id}").into(),
2272                        ),
2273                        constrain_width: false,
2274                        merge_adjacent: false,
2275                    };
2276                    let render_trailer =
2277                        move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2278
2279                    let start = buffer
2280                        .anchor_in_excerpt(excerpt_id, output_range.start)
2281                        .unwrap();
2282                    let end = buffer
2283                        .anchor_in_excerpt(excerpt_id, output_range.end)
2284                        .unwrap();
2285
2286                    let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2287
2288                    let crease = Crease::new(
2289                        start..end,
2290                        placeholder,
2291                        fold_toggle("tool-use"),
2292                        render_trailer,
2293                    );
2294
2295                    editor.insert_creases([crease], cx);
2296                    editor.fold_at(&FoldAt { buffer_row }, cx);
2297                });
2298            }
2299            ContextEvent::Operation(_) => {}
2300            ContextEvent::ShowAssistError(error_message) => {
2301                self.last_error = Some(AssistError::Message(error_message.clone()));
2302            }
2303            ContextEvent::ShowPaymentRequiredError => {
2304                self.last_error = Some(AssistError::PaymentRequired);
2305            }
2306            ContextEvent::ShowMaxMonthlySpendReachedError => {
2307                self.last_error = Some(AssistError::MaxMonthlySpendReached);
2308            }
2309        }
2310    }
2311
2312    fn workflow_steps_updated(
2313        &mut self,
2314        removed: &Vec<Range<text::Anchor>>,
2315        updated: &Vec<Range<text::Anchor>>,
2316        cx: &mut ViewContext<ContextEditor>,
2317    ) {
2318        let this = cx.view().downgrade();
2319        let mut removed_crease_ids = Vec::new();
2320        let mut removed_block_ids = HashSet::default();
2321        let mut editors_to_close = Vec::new();
2322        for range in removed {
2323            if let Some(state) = self.workflow_steps.remove(range) {
2324                editors_to_close.extend(self.hide_workflow_step(range.clone(), cx));
2325                removed_block_ids.insert(state.header_block_id);
2326                removed_crease_ids.push(state.header_crease_id);
2327                removed_block_ids.extend(state.footer_block_id);
2328                removed_crease_ids.extend(state.footer_crease_id);
2329            }
2330        }
2331
2332        for range in updated {
2333            editors_to_close.extend(self.hide_workflow_step(range.clone(), cx));
2334        }
2335
2336        self.editor.update(cx, |editor, cx| {
2337            let snapshot = editor.snapshot(cx);
2338            let multibuffer = &snapshot.buffer_snapshot;
2339            let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2340
2341            for range in updated {
2342                let Some(step) = self.context.read(cx).workflow_step_for_range(&range, cx) else {
2343                    continue;
2344                };
2345
2346                let resolution = step.resolution.clone();
2347                let header_start = step.range.start;
2348                let header_end = if buffer.contains_str_at(step.leading_tags_end, "\n") {
2349                    buffer.anchor_before(step.leading_tags_end.to_offset(&buffer) + 1)
2350                } else {
2351                    step.leading_tags_end
2352                };
2353                let header_range = multibuffer
2354                    .anchor_in_excerpt(excerpt_id, header_start)
2355                    .unwrap()
2356                    ..multibuffer
2357                        .anchor_in_excerpt(excerpt_id, header_end)
2358                        .unwrap();
2359                let footer_range = step.trailing_tag_start.map(|start| {
2360                    let mut step_range_end = step.range.end.to_offset(&buffer);
2361                    if buffer.contains_str_at(step_range_end, "\n") {
2362                        // Only include the newline if it belongs to the same message.
2363                        let messages = self
2364                            .context
2365                            .read(cx)
2366                            .messages_for_offsets([step_range_end, step_range_end + 1], cx);
2367                        if messages.len() == 1 {
2368                            step_range_end += 1;
2369                        }
2370                    }
2371
2372                    let end = buffer.anchor_before(step_range_end);
2373                    multibuffer.anchor_in_excerpt(excerpt_id, start).unwrap()
2374                        ..multibuffer.anchor_in_excerpt(excerpt_id, end).unwrap()
2375                });
2376
2377                let block_ids = editor.insert_blocks(
2378                    [BlockProperties {
2379                        position: header_range.start,
2380                        height: 1,
2381                        style: BlockStyle::Flex,
2382                        render: Box::new({
2383                            let this = this.clone();
2384                            let range = step.range.clone();
2385                            move |cx| {
2386                                let block_id = cx.block_id;
2387                                let max_width = cx.max_width;
2388                                let gutter_width = cx.gutter_dimensions.full_width();
2389                                this.update(&mut **cx, |this, cx| {
2390                                    this.render_workflow_step_header(
2391                                        range.clone(),
2392                                        max_width,
2393                                        gutter_width,
2394                                        block_id,
2395                                        cx,
2396                                    )
2397                                })
2398                                .ok()
2399                                .flatten()
2400                                .unwrap_or_else(|| Empty.into_any())
2401                            }
2402                        }),
2403                        disposition: BlockDisposition::Above,
2404                        priority: 0,
2405                    }]
2406                    .into_iter()
2407                    .chain(footer_range.as_ref().map(|footer_range| {
2408                        return BlockProperties {
2409                            position: footer_range.end,
2410                            height: 1,
2411                            style: BlockStyle::Flex,
2412                            render: Box::new({
2413                                let this = this.clone();
2414                                let range = step.range.clone();
2415                                move |cx| {
2416                                    let max_width = cx.max_width;
2417                                    let gutter_width = cx.gutter_dimensions.full_width();
2418                                    this.update(&mut **cx, |this, cx| {
2419                                        this.render_workflow_step_footer(
2420                                            range.clone(),
2421                                            max_width,
2422                                            gutter_width,
2423                                            cx,
2424                                        )
2425                                    })
2426                                    .ok()
2427                                    .flatten()
2428                                    .unwrap_or_else(|| Empty.into_any())
2429                                }
2430                            }),
2431                            disposition: BlockDisposition::Below,
2432                            priority: 0,
2433                        };
2434                    })),
2435                    None,
2436                    cx,
2437                );
2438
2439                let header_placeholder = FoldPlaceholder {
2440                    render: Arc::new(move |_, _crease_range, _cx| Empty.into_any()),
2441                    constrain_width: false,
2442                    merge_adjacent: false,
2443                };
2444                let footer_placeholder = FoldPlaceholder {
2445                    render: render_fold_icon_button(
2446                        cx.view().downgrade(),
2447                        IconName::Code,
2448                        "Edits".into(),
2449                    ),
2450                    constrain_width: false,
2451                    merge_adjacent: false,
2452                };
2453
2454                let new_crease_ids = editor.insert_creases(
2455                    [Crease::new(
2456                        header_range.clone(),
2457                        header_placeholder.clone(),
2458                        fold_toggle("step-header"),
2459                        |_, _, _| Empty.into_any_element(),
2460                    )]
2461                    .into_iter()
2462                    .chain(footer_range.clone().map(|footer_range| {
2463                        Crease::new(
2464                            footer_range,
2465                            footer_placeholder.clone(),
2466                            |row, is_folded, fold, cx| {
2467                                if is_folded {
2468                                    Empty.into_any_element()
2469                                } else {
2470                                    fold_toggle("step-footer")(row, is_folded, fold, cx)
2471                                }
2472                            },
2473                            |_, _, _| Empty.into_any_element(),
2474                        )
2475                    })),
2476                    cx,
2477                );
2478
2479                let state = WorkflowStepViewState {
2480                    header_block_id: block_ids[0],
2481                    header_crease_id: new_crease_ids[0],
2482                    footer_block_id: block_ids.get(1).copied(),
2483                    footer_crease_id: new_crease_ids.get(1).copied(),
2484                    resolution,
2485                    assist: None,
2486                };
2487
2488                let mut folds_to_insert = [(header_range.clone(), header_placeholder)]
2489                    .into_iter()
2490                    .chain(
2491                        footer_range
2492                            .clone()
2493                            .map(|range| (range, footer_placeholder)),
2494                    )
2495                    .collect::<Vec<_>>();
2496
2497                match self.workflow_steps.entry(range.clone()) {
2498                    hash_map::Entry::Vacant(entry) => {
2499                        entry.insert(state);
2500                    }
2501                    hash_map::Entry::Occupied(mut entry) => {
2502                        let entry = entry.get_mut();
2503                        removed_block_ids.insert(entry.header_block_id);
2504                        removed_crease_ids.push(entry.header_crease_id);
2505                        removed_block_ids.extend(entry.footer_block_id);
2506                        removed_crease_ids.extend(entry.footer_crease_id);
2507                        folds_to_insert.retain(|(range, _)| snapshot.intersects_fold(range.start));
2508                        *entry = state;
2509                    }
2510                }
2511
2512                editor.unfold_ranges(
2513                    [header_range.clone()]
2514                        .into_iter()
2515                        .chain(footer_range.clone()),
2516                    true,
2517                    false,
2518                    cx,
2519                );
2520
2521                if !folds_to_insert.is_empty() {
2522                    editor.fold_ranges(folds_to_insert, false, cx);
2523                }
2524            }
2525
2526            editor.remove_creases(removed_crease_ids, cx);
2527            editor.remove_blocks(removed_block_ids, None, cx);
2528        });
2529
2530        for (editor, editor_was_open) in editors_to_close {
2531            self.close_workflow_editor(cx, editor, editor_was_open);
2532        }
2533
2534        self.update_active_workflow_step(cx);
2535    }
2536
2537    fn insert_slash_command_output_sections(
2538        &mut self,
2539        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2540        expand_result: bool,
2541        cx: &mut ViewContext<Self>,
2542    ) {
2543        self.editor.update(cx, |editor, cx| {
2544            let buffer = editor.buffer().read(cx).snapshot(cx);
2545            let excerpt_id = *buffer.as_singleton().unwrap().0;
2546            let mut buffer_rows_to_fold = BTreeSet::new();
2547            let mut creases = Vec::new();
2548            for section in sections {
2549                let start = buffer
2550                    .anchor_in_excerpt(excerpt_id, section.range.start)
2551                    .unwrap();
2552                let end = buffer
2553                    .anchor_in_excerpt(excerpt_id, section.range.end)
2554                    .unwrap();
2555                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2556                buffer_rows_to_fold.insert(buffer_row);
2557                creases.push(
2558                    Crease::new(
2559                        start..end,
2560                        FoldPlaceholder {
2561                            render: render_fold_icon_button(
2562                                cx.view().downgrade(),
2563                                section.icon,
2564                                section.label.clone(),
2565                            ),
2566                            constrain_width: false,
2567                            merge_adjacent: false,
2568                        },
2569                        render_slash_command_output_toggle,
2570                        |_, _, _| Empty.into_any_element(),
2571                    )
2572                    .with_metadata(CreaseMetadata {
2573                        icon: section.icon,
2574                        label: section.label,
2575                    }),
2576                );
2577            }
2578
2579            editor.insert_creases(creases, cx);
2580
2581            if expand_result {
2582                buffer_rows_to_fold.clear();
2583            }
2584            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2585                editor.fold_at(&FoldAt { buffer_row }, cx);
2586            }
2587        });
2588    }
2589
2590    fn handle_editor_event(
2591        &mut self,
2592        _: View<Editor>,
2593        event: &EditorEvent,
2594        cx: &mut ViewContext<Self>,
2595    ) {
2596        match event {
2597            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2598                let cursor_scroll_position = self.cursor_scroll_position(cx);
2599                if *autoscroll {
2600                    self.scroll_position = cursor_scroll_position;
2601                } else if self.scroll_position != cursor_scroll_position {
2602                    self.scroll_position = None;
2603                }
2604            }
2605            EditorEvent::SelectionsChanged { .. } => {
2606                self.scroll_position = self.cursor_scroll_position(cx);
2607                self.update_active_workflow_step(cx);
2608            }
2609            _ => {}
2610        }
2611        cx.emit(event.clone());
2612    }
2613
2614    fn active_workflow_step(&self) -> Option<(Range<text::Anchor>, &WorkflowStepViewState)> {
2615        let step = self.active_workflow_step.as_ref()?;
2616        Some((step.range.clone(), self.workflow_steps.get(&step.range)?))
2617    }
2618
2619    fn update_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) {
2620        let newest_cursor = self.editor.read(cx).selections.newest::<usize>(cx).head();
2621        let context = self.context.read(cx);
2622
2623        let new_step = context
2624            .workflow_step_containing(newest_cursor, cx)
2625            .map(|step| ActiveWorkflowStep {
2626                resolved: step.resolution.is_some(),
2627                range: step.range.clone(),
2628            });
2629
2630        if new_step.as_ref() != self.active_workflow_step.as_ref() {
2631            let mut old_editor = None;
2632            let mut old_editor_was_open = None;
2633            if let Some(old_step) = self.active_workflow_step.take() {
2634                (old_editor, old_editor_was_open) =
2635                    self.hide_workflow_step(old_step.range, cx).unzip();
2636            }
2637
2638            let mut new_editor = None;
2639            if let Some(new_step) = new_step {
2640                new_editor = self.show_workflow_step(new_step.range.clone(), cx);
2641                self.active_workflow_step = Some(new_step);
2642            }
2643
2644            if new_editor != old_editor {
2645                if let Some((old_editor, old_editor_was_open)) = old_editor.zip(old_editor_was_open)
2646                {
2647                    self.close_workflow_editor(cx, old_editor, old_editor_was_open)
2648                }
2649            }
2650        }
2651    }
2652
2653    fn hide_workflow_step(
2654        &mut self,
2655        step_range: Range<language::Anchor>,
2656        cx: &mut ViewContext<Self>,
2657    ) -> Option<(View<Editor>, bool)> {
2658        if let Some(step) = self.workflow_steps.get_mut(&step_range) {
2659            let assist = step.assist.as_ref()?;
2660            let editor = assist.editor.upgrade()?;
2661
2662            if matches!(step.status(cx), WorkflowStepStatus::Idle) {
2663                let assist = step.assist.take().unwrap();
2664                InlineAssistant::update_global(cx, |assistant, cx| {
2665                    for assist_id in assist.assist_ids {
2666                        assistant.finish_assist(assist_id, true, cx)
2667                    }
2668                });
2669                return Some((editor, assist.editor_was_open));
2670            }
2671        }
2672
2673        None
2674    }
2675
2676    fn close_workflow_editor(
2677        &mut self,
2678        cx: &mut ViewContext<ContextEditor>,
2679        editor: View<Editor>,
2680        editor_was_open: bool,
2681    ) {
2682        self.workspace
2683            .update(cx, |workspace, cx| {
2684                if let Some(pane) = workspace.pane_for(&editor) {
2685                    pane.update(cx, |pane, cx| {
2686                        let item_id = editor.entity_id();
2687                        if !editor_was_open && !editor.read(cx).is_focused(cx) {
2688                            pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
2689                                .detach_and_log_err(cx);
2690                        }
2691                    });
2692                }
2693            })
2694            .ok();
2695    }
2696
2697    fn show_workflow_step(
2698        &mut self,
2699        step_range: Range<language::Anchor>,
2700        cx: &mut ViewContext<Self>,
2701    ) -> Option<View<Editor>> {
2702        let step = self.workflow_steps.get_mut(&step_range)?;
2703
2704        let mut editor_to_return = None;
2705        let mut scroll_to_assist_id = None;
2706        match step.status(cx) {
2707            WorkflowStepStatus::Idle => {
2708                if let Some(assist) = step.assist.as_ref() {
2709                    scroll_to_assist_id = assist.assist_ids.first().copied();
2710                } else if let Some(Ok(resolved)) = step.resolution.clone().as_deref() {
2711                    step.assist = Self::open_assists_for_step(
2712                        &resolved,
2713                        &self.project,
2714                        &self.assistant_panel,
2715                        &self.workspace,
2716                        cx,
2717                    );
2718                    editor_to_return = step
2719                        .assist
2720                        .as_ref()
2721                        .and_then(|assist| assist.editor.upgrade());
2722                }
2723            }
2724            WorkflowStepStatus::Pending => {
2725                if let Some(assist) = step.assist.as_ref() {
2726                    let assistant = InlineAssistant::global(cx);
2727                    scroll_to_assist_id = assist
2728                        .assist_ids
2729                        .iter()
2730                        .copied()
2731                        .find(|assist_id| assistant.assist_status(*assist_id, cx).is_pending());
2732                }
2733            }
2734            WorkflowStepStatus::Done => {
2735                if let Some(assist) = step.assist.as_ref() {
2736                    scroll_to_assist_id = assist.assist_ids.first().copied();
2737                }
2738            }
2739            _ => {}
2740        }
2741
2742        if let Some(assist_id) = scroll_to_assist_id {
2743            if let Some(assist_editor) = step
2744                .assist
2745                .as_ref()
2746                .and_then(|assists| assists.editor.upgrade())
2747            {
2748                editor_to_return = Some(assist_editor.clone());
2749                self.workspace
2750                    .update(cx, |workspace, cx| {
2751                        workspace.activate_item(&assist_editor, false, false, cx);
2752                    })
2753                    .ok();
2754                InlineAssistant::update_global(cx, |assistant, cx| {
2755                    assistant.scroll_to_assist(assist_id, cx)
2756                });
2757            }
2758        }
2759
2760        editor_to_return
2761    }
2762
2763    fn open_assists_for_step(
2764        resolved_step: &WorkflowStepResolution,
2765        project: &Model<Project>,
2766        assistant_panel: &WeakView<AssistantPanel>,
2767        workspace: &WeakView<Workspace>,
2768        cx: &mut ViewContext<Self>,
2769    ) -> Option<WorkflowAssist> {
2770        let assistant_panel = assistant_panel.upgrade()?;
2771        if resolved_step.suggestion_groups.is_empty() {
2772            return None;
2773        }
2774
2775        let editor;
2776        let mut editor_was_open = false;
2777        let mut suggestion_groups = Vec::new();
2778        if resolved_step.suggestion_groups.len() == 1
2779            && resolved_step
2780                .suggestion_groups
2781                .values()
2782                .next()
2783                .unwrap()
2784                .len()
2785                == 1
2786        {
2787            // If there's only one buffer and one suggestion group, open it directly
2788            let (buffer, groups) = resolved_step.suggestion_groups.iter().next().unwrap();
2789            let group = groups.into_iter().next().unwrap();
2790            editor = workspace
2791                .update(cx, |workspace, cx| {
2792                    let active_pane = workspace.active_pane().clone();
2793                    editor_was_open =
2794                        workspace.is_project_item_open::<Editor>(&active_pane, buffer, cx);
2795                    workspace.open_project_item::<Editor>(
2796                        active_pane,
2797                        buffer.clone(),
2798                        false,
2799                        false,
2800                        cx,
2801                    )
2802                })
2803                .log_err()?;
2804            let (&excerpt_id, _, _) = editor
2805                .read(cx)
2806                .buffer()
2807                .read(cx)
2808                .read(cx)
2809                .as_singleton()
2810                .unwrap();
2811
2812            // Scroll the editor to the suggested assist
2813            editor.update(cx, |editor, cx| {
2814                let multibuffer = editor.buffer().read(cx).snapshot(cx);
2815                let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2816                let anchor = if group.context_range.start.to_offset(buffer) == 0 {
2817                    Anchor::min()
2818                } else {
2819                    multibuffer
2820                        .anchor_in_excerpt(excerpt_id, group.context_range.start)
2821                        .unwrap()
2822                };
2823
2824                editor.set_scroll_anchor(
2825                    ScrollAnchor {
2826                        offset: gpui::Point::default(),
2827                        anchor,
2828                    },
2829                    cx,
2830                );
2831            });
2832
2833            suggestion_groups.push((excerpt_id, group));
2834        } else {
2835            // If there are multiple buffers or suggestion groups, create a multibuffer
2836            let multibuffer = cx.new_model(|cx| {
2837                let mut multibuffer =
2838                    MultiBuffer::new(Capability::ReadWrite).with_title(resolved_step.title.clone());
2839                for (buffer, groups) in &resolved_step.suggestion_groups {
2840                    let excerpt_ids = multibuffer.push_excerpts(
2841                        buffer.clone(),
2842                        groups.iter().map(|suggestion_group| ExcerptRange {
2843                            context: suggestion_group.context_range.clone(),
2844                            primary: None,
2845                        }),
2846                        cx,
2847                    );
2848                    suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
2849                }
2850                multibuffer
2851            });
2852
2853            editor = cx.new_view(|cx| {
2854                Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx)
2855            });
2856            workspace
2857                .update(cx, |workspace, cx| {
2858                    workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2859                })
2860                .log_err()?;
2861        }
2862
2863        let mut assist_ids = Vec::new();
2864        for (excerpt_id, suggestion_group) in suggestion_groups {
2865            for suggestion in &suggestion_group.suggestions {
2866                assist_ids.extend(suggestion.show(
2867                    &editor,
2868                    excerpt_id,
2869                    workspace,
2870                    &assistant_panel,
2871                    cx,
2872                ));
2873            }
2874        }
2875
2876        Some(WorkflowAssist {
2877            assist_ids,
2878            editor: editor.downgrade(),
2879            editor_was_open,
2880        })
2881    }
2882
2883    fn handle_editor_search_event(
2884        &mut self,
2885        _: View<Editor>,
2886        event: &SearchEvent,
2887        cx: &mut ViewContext<Self>,
2888    ) {
2889        cx.emit(event.clone());
2890    }
2891
2892    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2893        self.editor.update(cx, |editor, cx| {
2894            let snapshot = editor.snapshot(cx);
2895            let cursor = editor.selections.newest_anchor().head();
2896            let cursor_row = cursor
2897                .to_display_point(&snapshot.display_snapshot)
2898                .row()
2899                .as_f32();
2900            let scroll_position = editor
2901                .scroll_manager
2902                .anchor()
2903                .scroll_position(&snapshot.display_snapshot);
2904
2905            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2906            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2907                Some(ScrollPosition {
2908                    cursor,
2909                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2910                })
2911            } else {
2912                None
2913            }
2914        })
2915    }
2916
2917    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2918        self.editor.update(cx, |editor, cx| {
2919            let buffer = editor.buffer().read(cx).snapshot(cx);
2920
2921            let excerpt_id = *buffer.as_singleton().unwrap().0;
2922            let mut old_blocks = std::mem::take(&mut self.blocks);
2923            let mut blocks_to_remove: HashMap<_, _> = old_blocks
2924                .iter()
2925                .map(|(message_id, (_, block_id))| (*message_id, *block_id))
2926                .collect();
2927            let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
2928
2929            let render_block = |message: MessageMetadata| -> RenderBlock {
2930                Box::new({
2931                    let context = self.context.clone();
2932                    move |cx| {
2933                        let message_id = MessageId(message.timestamp);
2934                        let show_spinner = message.role == Role::Assistant
2935                            && message.status == MessageStatus::Pending;
2936
2937                        let label = match message.role {
2938                            Role::User => {
2939                                Label::new("You").color(Color::Default).into_any_element()
2940                            }
2941                            Role::Assistant => {
2942                                let label = Label::new("Assistant").color(Color::Info);
2943                                if show_spinner {
2944                                    label
2945                                        .with_animation(
2946                                            "pulsating-label",
2947                                            Animation::new(Duration::from_secs(2))
2948                                                .repeat()
2949                                                .with_easing(pulsating_between(0.4, 0.8)),
2950                                            |label, delta| label.alpha(delta),
2951                                        )
2952                                        .into_any_element()
2953                                } else {
2954                                    label.into_any_element()
2955                                }
2956                            }
2957
2958                            Role::System => Label::new("System")
2959                                .color(Color::Warning)
2960                                .into_any_element(),
2961                        };
2962
2963                        let sender = ButtonLike::new("role")
2964                            .style(ButtonStyle::Filled)
2965                            .child(label)
2966                            .tooltip(|cx| {
2967                                Tooltip::with_meta(
2968                                    "Toggle message role",
2969                                    None,
2970                                    "Available roles: You (User), Assistant, System",
2971                                    cx,
2972                                )
2973                            })
2974                            .on_click({
2975                                let context = context.clone();
2976                                move |_, cx| {
2977                                    context.update(cx, |context, cx| {
2978                                        context.cycle_message_roles(
2979                                            HashSet::from_iter(Some(message_id)),
2980                                            cx,
2981                                        )
2982                                    })
2983                                }
2984                            });
2985
2986                        h_flex()
2987                            .id(("message_header", message_id.as_u64()))
2988                            .pl(cx.gutter_dimensions.full_width())
2989                            .h_11()
2990                            .w_full()
2991                            .relative()
2992                            .gap_1()
2993                            .child(sender)
2994                            .children(match &message.cache {
2995                                Some(cache) if cache.is_final_anchor => match cache.status {
2996                                    CacheStatus::Cached => Some(
2997                                        div()
2998                                            .id("cached")
2999                                            .child(
3000                                                Icon::new(IconName::DatabaseZap)
3001                                                    .size(IconSize::XSmall)
3002                                                    .color(Color::Hint),
3003                                            )
3004                                            .tooltip(|cx| {
3005                                                Tooltip::with_meta(
3006                                                    "Context cached",
3007                                                    None,
3008                                                    "Large messages cached to optimize performance",
3009                                                    cx,
3010                                                )
3011                                            })
3012                                            .into_any_element(),
3013                                    ),
3014                                    CacheStatus::Pending => Some(
3015                                        div()
3016                                            .child(
3017                                                Icon::new(IconName::Ellipsis)
3018                                                    .size(IconSize::XSmall)
3019                                                    .color(Color::Hint),
3020                                            )
3021                                            .into_any_element(),
3022                                    ),
3023                                },
3024                                _ => None,
3025                            })
3026                            .children(match &message.status {
3027                                MessageStatus::Error(error) => Some(
3028                                    Button::new("show-error", "Error")
3029                                        .color(Color::Error)
3030                                        .selected_label_color(Color::Error)
3031                                        .selected_icon_color(Color::Error)
3032                                        .icon(IconName::XCircle)
3033                                        .icon_color(Color::Error)
3034                                        .icon_size(IconSize::Small)
3035                                        .icon_position(IconPosition::Start)
3036                                        .tooltip(move |cx| {
3037                                            Tooltip::with_meta(
3038                                                "Error interacting with language model",
3039                                                None,
3040                                                "Click for more details",
3041                                                cx,
3042                                            )
3043                                        })
3044                                        .on_click({
3045                                            let context = context.clone();
3046                                            let error = error.clone();
3047                                            move |_, cx| {
3048                                                context.update(cx, |_, cx| {
3049                                                    cx.emit(ContextEvent::ShowAssistError(
3050                                                        error.clone(),
3051                                                    ));
3052                                                });
3053                                            }
3054                                        })
3055                                        .into_any_element(),
3056                                ),
3057                                MessageStatus::Canceled => Some(
3058                                    ButtonLike::new("canceled")
3059                                        .child(Icon::new(IconName::XCircle).color(Color::Disabled))
3060                                        .child(
3061                                            Label::new("Canceled")
3062                                                .size(LabelSize::Small)
3063                                                .color(Color::Disabled),
3064                                        )
3065                                        .tooltip(move |cx| {
3066                                            Tooltip::with_meta(
3067                                                "Canceled",
3068                                                None,
3069                                                "Interaction with the assistant was canceled",
3070                                                cx,
3071                                            )
3072                                        })
3073                                        .into_any_element(),
3074                                ),
3075                                _ => None,
3076                            })
3077                            .into_any_element()
3078                    }
3079                })
3080            };
3081            let create_block_properties = |message: &Message| BlockProperties {
3082                position: buffer
3083                    .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
3084                    .unwrap(),
3085                height: 2,
3086                style: BlockStyle::Sticky,
3087                disposition: BlockDisposition::Above,
3088                priority: usize::MAX,
3089                render: render_block(MessageMetadata::from(message)),
3090            };
3091            let mut new_blocks = vec![];
3092            let mut block_index_to_message = vec![];
3093            for message in self.context.read(cx).messages(cx) {
3094                if let Some(_) = blocks_to_remove.remove(&message.id) {
3095                    // This is an old message that we might modify.
3096                    let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
3097                        debug_assert!(
3098                            false,
3099                            "old_blocks should contain a message_id we've just removed."
3100                        );
3101                        continue;
3102                    };
3103                    // Should we modify it?
3104                    let message_meta = MessageMetadata::from(&message);
3105                    if meta != &message_meta {
3106                        blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
3107                        *meta = message_meta;
3108                    }
3109                } else {
3110                    // This is a new message.
3111                    new_blocks.push(create_block_properties(&message));
3112                    block_index_to_message.push((message.id, MessageMetadata::from(&message)));
3113                }
3114            }
3115            editor.replace_blocks(blocks_to_replace, None, cx);
3116            editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
3117
3118            let ids = editor.insert_blocks(new_blocks, None, cx);
3119            old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
3120                |(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
3121            ));
3122            self.blocks = old_blocks;
3123        });
3124    }
3125
3126    /// Returns either the selected text, or the content of the Markdown code
3127    /// block surrounding the cursor.
3128    fn get_selection_or_code_block(
3129        context_editor_view: &View<ContextEditor>,
3130        cx: &mut ViewContext<Workspace>,
3131    ) -> Option<(String, bool)> {
3132        const CODE_FENCE_DELIMITER: &'static str = "```";
3133
3134        let context_editor = context_editor_view.read(cx).editor.read(cx);
3135
3136        if context_editor.selections.newest::<Point>(cx).is_empty() {
3137            let snapshot = context_editor.buffer().read(cx).snapshot(cx);
3138            let (_, _, snapshot) = snapshot.as_singleton()?;
3139
3140            let head = context_editor.selections.newest::<Point>(cx).head();
3141            let offset = snapshot.point_to_offset(head);
3142
3143            let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
3144            let mut text = snapshot
3145                .text_for_range(surrounding_code_block_range)
3146                .collect::<String>();
3147
3148            // If there is no newline trailing the closing three-backticks, then
3149            // tree-sitter-md extends the range of the content node to include
3150            // the backticks.
3151            if text.ends_with(CODE_FENCE_DELIMITER) {
3152                text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
3153            }
3154
3155            (!text.is_empty()).then_some((text, true))
3156        } else {
3157            let anchor = context_editor.selections.newest_anchor();
3158            let text = context_editor
3159                .buffer()
3160                .read(cx)
3161                .read(cx)
3162                .text_for_range(anchor.range())
3163                .collect::<String>();
3164
3165            (!text.is_empty()).then_some((text, false))
3166        }
3167    }
3168
3169    fn insert_selection(
3170        workspace: &mut Workspace,
3171        _: &InsertIntoEditor,
3172        cx: &mut ViewContext<Workspace>,
3173    ) {
3174        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3175            return;
3176        };
3177        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3178            return;
3179        };
3180        let Some(active_editor_view) = workspace
3181            .active_item(cx)
3182            .and_then(|item| item.act_as::<Editor>(cx))
3183        else {
3184            return;
3185        };
3186
3187        if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
3188            active_editor_view.update(cx, |editor, cx| {
3189                editor.insert(&text, cx);
3190                editor.focus(cx);
3191            })
3192        }
3193    }
3194
3195    fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
3196        let result = maybe!({
3197            let panel = workspace.panel::<AssistantPanel>(cx)?;
3198            let context_editor_view = panel.read(cx).active_context_editor(cx)?;
3199            Self::get_selection_or_code_block(&context_editor_view, cx)
3200        });
3201        let Some((text, is_code_block)) = result else {
3202            return;
3203        };
3204
3205        cx.write_to_clipboard(ClipboardItem::new_string(text));
3206
3207        struct CopyToClipboardToast;
3208        workspace.show_toast(
3209            Toast::new(
3210                NotificationId::unique::<CopyToClipboardToast>(),
3211                format!(
3212                    "{} copied to clipboard.",
3213                    if is_code_block {
3214                        "Code block"
3215                    } else {
3216                        "Selection"
3217                    }
3218                ),
3219            )
3220            .autohide(),
3221            cx,
3222        );
3223    }
3224
3225    fn insert_dragged_files(
3226        workspace: &mut Workspace,
3227        action: &InsertDraggedFiles,
3228        cx: &mut ViewContext<Workspace>,
3229    ) {
3230        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3231            return;
3232        };
3233        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3234            return;
3235        };
3236
3237        let project = workspace.project().clone();
3238
3239        let paths = match action {
3240            InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
3241            InsertDraggedFiles::ExternalFiles(paths) => {
3242                let tasks = paths
3243                    .clone()
3244                    .into_iter()
3245                    .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
3246                    .collect::<Vec<_>>();
3247
3248                cx.spawn(move |_, cx| async move {
3249                    let mut paths = vec![];
3250                    let mut worktrees = vec![];
3251
3252                    let opened_paths = futures::future::join_all(tasks).await;
3253                    for (worktree, project_path) in opened_paths.into_iter().flatten() {
3254                        let Ok(worktree_root_name) =
3255                            worktree.read_with(&cx, |worktree, _| worktree.root_name().to_string())
3256                        else {
3257                            continue;
3258                        };
3259
3260                        let mut full_path = PathBuf::from(worktree_root_name.clone());
3261                        full_path.push(&project_path.path);
3262                        paths.push(full_path);
3263                        worktrees.push(worktree);
3264                    }
3265
3266                    (paths, worktrees)
3267                })
3268            }
3269        };
3270
3271        cx.spawn(|_, mut cx| async move {
3272            let (paths, dragged_file_worktrees) = paths.await;
3273            let cmd_name = file_command::FileSlashCommand.name();
3274
3275            context_editor_view
3276                .update(&mut cx, |context_editor, cx| {
3277                    let file_argument = paths
3278                        .into_iter()
3279                        .map(|path| path.to_string_lossy().to_string())
3280                        .collect::<Vec<_>>()
3281                        .join(" ");
3282
3283                    context_editor.editor.update(cx, |editor, cx| {
3284                        editor.insert("\n", cx);
3285                        editor.insert(&format!("/{} {}", cmd_name, file_argument), cx);
3286                    });
3287
3288                    context_editor.confirm_command(&ConfirmCommand, cx);
3289
3290                    context_editor
3291                        .dragged_file_worktrees
3292                        .extend(dragged_file_worktrees);
3293                })
3294                .log_err();
3295        })
3296        .detach();
3297    }
3298
3299    fn quote_selection(
3300        workspace: &mut Workspace,
3301        _: &QuoteSelection,
3302        cx: &mut ViewContext<Workspace>,
3303    ) {
3304        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3305            return;
3306        };
3307        let Some(editor) = workspace
3308            .active_item(cx)
3309            .and_then(|item| item.act_as::<Editor>(cx))
3310        else {
3311            return;
3312        };
3313
3314        let mut creases = vec![];
3315        editor.update(cx, |editor, cx| {
3316            let selections = editor.selections.all_adjusted(cx);
3317            let buffer = editor.buffer().read(cx).snapshot(cx);
3318            for selection in selections {
3319                let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3320                    ..editor::ToOffset::to_offset(&selection.end, &buffer);
3321                let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3322                if selected_text.is_empty() {
3323                    continue;
3324                }
3325                let start_language = buffer.language_at(range.start);
3326                let end_language = buffer.language_at(range.end);
3327                let language_name = if start_language == end_language {
3328                    start_language.map(|language| language.code_fence_block_name())
3329                } else {
3330                    None
3331                };
3332                let language_name = language_name.as_deref().unwrap_or("");
3333                let filename = buffer
3334                    .file_at(selection.start)
3335                    .map(|file| file.full_path(cx));
3336                let text = if language_name == "markdown" {
3337                    selected_text
3338                        .lines()
3339                        .map(|line| format!("> {}", line))
3340                        .collect::<Vec<_>>()
3341                        .join("\n")
3342                } else {
3343                    let start_symbols = buffer
3344                        .symbols_containing(selection.start, None)
3345                        .map(|(_, symbols)| symbols);
3346                    let end_symbols = buffer
3347                        .symbols_containing(selection.end, None)
3348                        .map(|(_, symbols)| symbols);
3349
3350                    let outline_text = if let Some((start_symbols, end_symbols)) =
3351                        start_symbols.zip(end_symbols)
3352                    {
3353                        Some(
3354                            start_symbols
3355                                .into_iter()
3356                                .zip(end_symbols)
3357                                .take_while(|(a, b)| a == b)
3358                                .map(|(a, _)| a.text)
3359                                .collect::<Vec<_>>()
3360                                .join(" > "),
3361                        )
3362                    } else {
3363                        None
3364                    };
3365
3366                    let line_comment_prefix = start_language
3367                        .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3368
3369                    let fence = codeblock_fence_for_path(
3370                        filename.as_deref(),
3371                        Some(selection.start.row..=selection.end.row),
3372                    );
3373
3374                    if let Some((line_comment_prefix, outline_text)) =
3375                        line_comment_prefix.zip(outline_text)
3376                    {
3377                        let breadcrumb =
3378                            format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
3379                        format!("{fence}{breadcrumb}{selected_text}\n```")
3380                    } else {
3381                        format!("{fence}{selected_text}\n```")
3382                    }
3383                };
3384                let crease_title = if let Some(path) = filename {
3385                    let start_line = selection.start.row + 1;
3386                    let end_line = selection.end.row + 1;
3387                    if start_line == end_line {
3388                        format!("{}, Line {}", path.display(), start_line)
3389                    } else {
3390                        format!("{}, Lines {} to {}", path.display(), start_line, end_line)
3391                    }
3392                } else {
3393                    "Quoted selection".to_string()
3394                };
3395                creases.push((text, crease_title));
3396            }
3397        });
3398        if creases.is_empty() {
3399            return;
3400        }
3401        // Activate the panel
3402        if !panel.focus_handle(cx).contains_focused(cx) {
3403            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3404        }
3405
3406        panel.update(cx, |_, cx| {
3407            // Wait to create a new context until the workspace is no longer
3408            // being updated.
3409            cx.defer(move |panel, cx| {
3410                if let Some(context) = panel
3411                    .active_context_editor(cx)
3412                    .or_else(|| panel.new_context(cx))
3413                {
3414                    context.update(cx, |context, cx| {
3415                        context.editor.update(cx, |editor, cx| {
3416                            editor.insert("\n", cx);
3417                            for (text, crease_title) in creases {
3418                                let point = editor.selections.newest::<Point>(cx).head();
3419                                let start_row = MultiBufferRow(point.row);
3420
3421                                editor.insert(&text, cx);
3422
3423                                let snapshot = editor.buffer().read(cx).snapshot(cx);
3424                                let anchor_before = snapshot.anchor_after(point);
3425                                let anchor_after = editor
3426                                    .selections
3427                                    .newest_anchor()
3428                                    .head()
3429                                    .bias_left(&snapshot);
3430
3431                                editor.insert("\n", cx);
3432
3433                                let fold_placeholder = quote_selection_fold_placeholder(
3434                                    crease_title,
3435                                    cx.view().downgrade(),
3436                                );
3437                                let crease = Crease::new(
3438                                    anchor_before..anchor_after,
3439                                    fold_placeholder,
3440                                    render_quote_selection_output_toggle,
3441                                    |_, _, _| Empty.into_any(),
3442                                );
3443                                editor.insert_creases(vec![crease], cx);
3444                                editor.fold_at(
3445                                    &FoldAt {
3446                                        buffer_row: start_row,
3447                                    },
3448                                    cx,
3449                                );
3450                            }
3451                        })
3452                    });
3453                };
3454            });
3455        });
3456    }
3457
3458    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3459        if self.editor.read(cx).selections.count() == 1 {
3460            let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
3461            cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3462                copied_text,
3463                metadata,
3464            ));
3465            cx.stop_propagation();
3466            return;
3467        }
3468
3469        cx.propagate();
3470    }
3471
3472    fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
3473        if self.editor.read(cx).selections.count() == 1 {
3474            let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
3475
3476            self.editor.update(cx, |editor, cx| {
3477                editor.transact(cx, |this, cx| {
3478                    this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3479                        s.select(selections);
3480                    });
3481                    this.insert("", cx);
3482                    cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3483                        copied_text,
3484                        metadata,
3485                    ));
3486                });
3487            });
3488
3489            cx.stop_propagation();
3490            return;
3491        }
3492
3493        cx.propagate();
3494    }
3495
3496    fn get_clipboard_contents(
3497        &mut self,
3498        cx: &mut ViewContext<Self>,
3499    ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
3500        let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
3501            let mut selection = editor.selections.newest::<Point>(cx);
3502            let snapshot = editor.buffer().read(cx).snapshot(cx);
3503
3504            let is_entire_line = selection.is_empty() || editor.selections.line_mode;
3505            if is_entire_line {
3506                selection.start = Point::new(selection.start.row, 0);
3507                selection.end =
3508                    cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
3509                selection.goal = SelectionGoal::None;
3510            }
3511
3512            let selection_start = snapshot.point_to_offset(selection.start);
3513
3514            (
3515                snapshot.clone(),
3516                selection.clone(),
3517                editor.display_map.update(cx, |display_map, cx| {
3518                    display_map
3519                        .snapshot(cx)
3520                        .crease_snapshot
3521                        .creases_in_range(
3522                            MultiBufferRow(selection.start.row)
3523                                ..MultiBufferRow(selection.end.row + 1),
3524                            &snapshot,
3525                        )
3526                        .filter_map(|crease| {
3527                            if let Some(metadata) = &crease.metadata {
3528                                let start = crease
3529                                    .range
3530                                    .start
3531                                    .to_offset(&snapshot)
3532                                    .saturating_sub(selection_start);
3533                                let end = crease
3534                                    .range
3535                                    .end
3536                                    .to_offset(&snapshot)
3537                                    .saturating_sub(selection_start);
3538
3539                                let range_relative_to_selection = start..end;
3540
3541                                if range_relative_to_selection.is_empty() {
3542                                    None
3543                                } else {
3544                                    Some(SelectedCreaseMetadata {
3545                                        range_relative_to_selection,
3546                                        crease: metadata.clone(),
3547                                    })
3548                                }
3549                            } else {
3550                                None
3551                            }
3552                        })
3553                        .collect::<Vec<_>>()
3554                }),
3555            )
3556        });
3557
3558        let selection = selection.map(|point| snapshot.point_to_offset(point));
3559        let context = self.context.read(cx);
3560
3561        let mut text = String::new();
3562        for message in context.messages(cx) {
3563            if message.offset_range.start >= selection.range().end {
3564                break;
3565            } else if message.offset_range.end >= selection.range().start {
3566                let range = cmp::max(message.offset_range.start, selection.range().start)
3567                    ..cmp::min(message.offset_range.end, selection.range().end);
3568                if !range.is_empty() {
3569                    for chunk in context.buffer().read(cx).text_for_range(range) {
3570                        text.push_str(chunk);
3571                    }
3572                    if message.offset_range.end < selection.range().end {
3573                        text.push('\n');
3574                    }
3575                }
3576            }
3577        }
3578
3579        (text, CopyMetadata { creases }, vec![selection])
3580    }
3581
3582    fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
3583        cx.stop_propagation();
3584
3585        let images = if let Some(item) = cx.read_from_clipboard() {
3586            item.into_entries()
3587                .filter_map(|entry| {
3588                    if let ClipboardEntry::Image(image) = entry {
3589                        Some(image)
3590                    } else {
3591                        None
3592                    }
3593                })
3594                .collect()
3595        } else {
3596            Vec::new()
3597        };
3598
3599        let metadata = if let Some(item) = cx.read_from_clipboard() {
3600            item.entries().first().and_then(|entry| {
3601                if let ClipboardEntry::String(text) = entry {
3602                    text.metadata_json::<CopyMetadata>()
3603                } else {
3604                    None
3605                }
3606            })
3607        } else {
3608            None
3609        };
3610
3611        if images.is_empty() {
3612            self.editor.update(cx, |editor, cx| {
3613                let paste_position = editor.selections.newest::<usize>(cx).head();
3614                editor.paste(action, cx);
3615
3616                if let Some(metadata) = metadata {
3617                    let buffer = editor.buffer().read(cx).snapshot(cx);
3618
3619                    let mut buffer_rows_to_fold = BTreeSet::new();
3620                    let weak_editor = cx.view().downgrade();
3621                    editor.insert_creases(
3622                        metadata.creases.into_iter().map(|metadata| {
3623                            let start = buffer.anchor_after(
3624                                paste_position + metadata.range_relative_to_selection.start,
3625                            );
3626                            let end = buffer.anchor_before(
3627                                paste_position + metadata.range_relative_to_selection.end,
3628                            );
3629
3630                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
3631                            buffer_rows_to_fold.insert(buffer_row);
3632                            Crease::new(
3633                                start..end,
3634                                FoldPlaceholder {
3635                                    constrain_width: false,
3636                                    render: render_fold_icon_button(
3637                                        weak_editor.clone(),
3638                                        metadata.crease.icon,
3639                                        metadata.crease.label.clone(),
3640                                    ),
3641                                    merge_adjacent: false,
3642                                },
3643                                render_slash_command_output_toggle,
3644                                |_, _, _| Empty.into_any(),
3645                            )
3646                            .with_metadata(metadata.crease.clone())
3647                        }),
3648                        cx,
3649                    );
3650                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
3651                        editor.fold_at(&FoldAt { buffer_row }, cx);
3652                    }
3653                }
3654            });
3655        } else {
3656            let mut image_positions = Vec::new();
3657            self.editor.update(cx, |editor, cx| {
3658                editor.transact(cx, |editor, cx| {
3659                    let edits = editor
3660                        .selections
3661                        .all::<usize>(cx)
3662                        .into_iter()
3663                        .map(|selection| (selection.start..selection.end, "\n"));
3664                    editor.edit(edits, cx);
3665
3666                    let snapshot = editor.buffer().read(cx).snapshot(cx);
3667                    for selection in editor.selections.all::<usize>(cx) {
3668                        image_positions.push(snapshot.anchor_before(selection.end));
3669                    }
3670                });
3671            });
3672
3673            self.context.update(cx, |context, cx| {
3674                for image in images {
3675                    let Some(render_image) = image.to_image_data(cx).log_err() else {
3676                        continue;
3677                    };
3678                    let image_id = image.id();
3679                    let image_task = LanguageModelImage::from_image(image, cx).shared();
3680
3681                    for image_position in image_positions.iter() {
3682                        context.insert_content(
3683                            Content::Image {
3684                                anchor: image_position.text_anchor,
3685                                image_id,
3686                                image: image_task.clone(),
3687                                render_image: render_image.clone(),
3688                            },
3689                            cx,
3690                        );
3691                    }
3692                }
3693            });
3694        }
3695    }
3696
3697    fn update_image_blocks(&mut self, cx: &mut ViewContext<Self>) {
3698        self.editor.update(cx, |editor, cx| {
3699            let buffer = editor.buffer().read(cx).snapshot(cx);
3700            let excerpt_id = *buffer.as_singleton().unwrap().0;
3701            let old_blocks = std::mem::take(&mut self.image_blocks);
3702            let new_blocks = self
3703                .context
3704                .read(cx)
3705                .contents(cx)
3706                .filter_map(|content| {
3707                    if let Content::Image {
3708                        anchor,
3709                        render_image,
3710                        ..
3711                    } = content
3712                    {
3713                        Some((anchor, render_image))
3714                    } else {
3715                        None
3716                    }
3717                })
3718                .filter_map(|(anchor, render_image)| {
3719                    const MAX_HEIGHT_IN_LINES: u32 = 8;
3720                    let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
3721                    let image = render_image.clone();
3722                    anchor.is_valid(&buffer).then(|| BlockProperties {
3723                        position: anchor,
3724                        height: MAX_HEIGHT_IN_LINES,
3725                        style: BlockStyle::Sticky,
3726                        render: Box::new(move |cx| {
3727                            let image_size = size_for_image(
3728                                &image,
3729                                size(
3730                                    cx.max_width - cx.gutter_dimensions.full_width(),
3731                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
3732                                ),
3733                            );
3734                            h_flex()
3735                                .pl(cx.gutter_dimensions.full_width())
3736                                .child(
3737                                    img(image.clone())
3738                                        .object_fit(gpui::ObjectFit::ScaleDown)
3739                                        .w(image_size.width)
3740                                        .h(image_size.height),
3741                                )
3742                                .into_any_element()
3743                        }),
3744
3745                        disposition: BlockDisposition::Above,
3746                        priority: 0,
3747                    })
3748                })
3749                .collect::<Vec<_>>();
3750
3751            editor.remove_blocks(old_blocks, None, cx);
3752            let ids = editor.insert_blocks(new_blocks, None, cx);
3753            self.image_blocks = HashSet::from_iter(ids);
3754        });
3755    }
3756
3757    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3758        self.context.update(cx, |context, cx| {
3759            let selections = self.editor.read(cx).selections.disjoint_anchors();
3760            for selection in selections.as_ref() {
3761                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3762                let range = selection
3763                    .map(|endpoint| endpoint.to_offset(&buffer))
3764                    .range();
3765                context.split_message(range, cx);
3766            }
3767        });
3768    }
3769
3770    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3771        self.context.update(cx, |context, cx| {
3772            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3773        });
3774    }
3775
3776    fn title(&self, cx: &AppContext) -> Cow<str> {
3777        self.context
3778            .read(cx)
3779            .summary()
3780            .map(|summary| summary.text.clone())
3781            .map(Cow::Owned)
3782            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3783    }
3784
3785    fn render_workflow_step_header(
3786        &self,
3787        range: Range<text::Anchor>,
3788        max_width: Pixels,
3789        gutter_width: Pixels,
3790        id: BlockId,
3791        cx: &mut ViewContext<Self>,
3792    ) -> Option<AnyElement> {
3793        let step_state = self.workflow_steps.get(&range)?;
3794        let status = step_state.status(cx);
3795        let this = cx.view().downgrade();
3796
3797        let theme = cx.theme().status();
3798        let is_confirmed = status.is_confirmed();
3799        let border_color = if is_confirmed {
3800            theme.ignored_border
3801        } else {
3802            theme.info_border
3803        };
3804
3805        let editor = self.editor.read(cx);
3806        let focus_handle = editor.focus_handle(cx);
3807        let snapshot = editor
3808            .buffer()
3809            .read(cx)
3810            .as_singleton()?
3811            .read(cx)
3812            .text_snapshot();
3813        let start_offset = range.start.to_offset(&snapshot);
3814        let parent_message = self
3815            .context
3816            .read(cx)
3817            .messages_for_offsets([start_offset], cx);
3818        debug_assert_eq!(parent_message.len(), 1);
3819        let parent_message = parent_message.first()?;
3820
3821        let step_index = self
3822            .workflow_steps
3823            .keys()
3824            .filter(|workflow_step_range| {
3825                workflow_step_range
3826                    .start
3827                    .cmp(&parent_message.anchor_range.start, &snapshot)
3828                    .is_ge()
3829                    && workflow_step_range.end.cmp(&range.end, &snapshot).is_le()
3830            })
3831            .count();
3832
3833        let step_label = Label::new(format!("Step {step_index}")).size(LabelSize::Small);
3834
3835        let step_label = if is_confirmed {
3836            h_flex()
3837                .items_center()
3838                .gap_2()
3839                .child(step_label.strikethrough(true).color(Color::Muted))
3840                .child(
3841                    Icon::new(IconName::Check)
3842                        .size(IconSize::Small)
3843                        .color(Color::Created),
3844                )
3845        } else {
3846            div().child(step_label)
3847        };
3848
3849        Some(
3850            v_flex()
3851                .w(max_width)
3852                .pl(gutter_width)
3853                .child(
3854                    h_flex()
3855                        .w_full()
3856                        .h_8()
3857                        .border_b_1()
3858                        .border_color(border_color)
3859                        .items_center()
3860                        .justify_between()
3861                        .gap_2()
3862                        .child(h_flex().justify_start().gap_2().child(step_label))
3863                        .child(h_flex().w_full().justify_end().child(
3864                            Self::render_workflow_step_status(
3865                                status,
3866                                range.clone(),
3867                                focus_handle.clone(),
3868                                this.clone(),
3869                                id,
3870                            ),
3871                        )),
3872                )
3873                // todo!("do we wanna keep this?")
3874                // .children(edit_paths.iter().map(|path| {
3875                //     h_flex()
3876                //         .gap_1()
3877                //         .child(Icon::new(IconName::File))
3878                //         .child(Label::new(path.clone()))
3879                // }))
3880                .into_any(),
3881        )
3882    }
3883
3884    fn render_workflow_step_footer(
3885        &self,
3886        step_range: Range<text::Anchor>,
3887        max_width: Pixels,
3888        gutter_width: Pixels,
3889        cx: &mut ViewContext<Self>,
3890    ) -> Option<AnyElement> {
3891        let step = self.workflow_steps.get(&step_range)?;
3892        let current_status = step.status(cx);
3893        let theme = cx.theme().status();
3894        let border_color = if current_status.is_confirmed() {
3895            theme.ignored_border
3896        } else {
3897            theme.info_border
3898        };
3899        Some(
3900            v_flex()
3901                .w(max_width)
3902                .pt_1()
3903                .pl(gutter_width)
3904                .child(h_flex().h(px(1.)).bg(border_color))
3905                .into_any(),
3906        )
3907    }
3908
3909    fn render_workflow_step_status(
3910        status: WorkflowStepStatus,
3911        step_range: Range<language::Anchor>,
3912        focus_handle: FocusHandle,
3913        editor: WeakView<ContextEditor>,
3914        id: BlockId,
3915    ) -> AnyElement {
3916        let id = EntityId::from(id).as_u64();
3917        fn display_keybind_in_tooltip(
3918            step_range: &Range<language::Anchor>,
3919            editor: &WeakView<ContextEditor>,
3920            cx: &mut WindowContext<'_>,
3921        ) -> bool {
3922            editor
3923                .update(cx, |this, _| {
3924                    this.active_workflow_step
3925                        .as_ref()
3926                        .map(|step| &step.range == step_range)
3927                })
3928                .ok()
3929                .flatten()
3930                .unwrap_or_default()
3931        }
3932
3933        match status {
3934            WorkflowStepStatus::Error(error) => {
3935                let error = error.to_string();
3936                h_flex()
3937                    .gap_2()
3938                    .child(
3939                        div()
3940                            .id("step-resolution-failure")
3941                            .child(
3942                                Label::new("Step Resolution Failed")
3943                                    .size(LabelSize::Small)
3944                                    .color(Color::Error),
3945                            )
3946                            .tooltip(move |cx| Tooltip::text(error.clone(), cx)),
3947                    )
3948                    .child(
3949                        Button::new(("transform", id), "Retry")
3950                            .icon(IconName::Update)
3951                            .icon_position(IconPosition::Start)
3952                            .icon_size(IconSize::Small)
3953                            .label_size(LabelSize::Small)
3954                            .on_click({
3955                                let editor = editor.clone();
3956                                let step_range = step_range.clone();
3957                                move |_, cx| {
3958                                    editor
3959                                        .update(cx, |this, cx| {
3960                                            this.resolve_workflow_step(step_range.clone(), cx)
3961                                        })
3962                                        .ok();
3963                                }
3964                            }),
3965                    )
3966                    .into_any()
3967            }
3968            WorkflowStepStatus::Idle | WorkflowStepStatus::Resolving { .. } => {
3969                Button::new(("transform", id), "Transform")
3970                    .icon(IconName::SparkleAlt)
3971                    .icon_position(IconPosition::Start)
3972                    .icon_size(IconSize::Small)
3973                    .label_size(LabelSize::Small)
3974                    .style(ButtonStyle::Tinted(TintColor::Accent))
3975                    .tooltip({
3976                        let step_range = step_range.clone();
3977                        let editor = editor.clone();
3978                        move |cx| {
3979                            cx.new_view(|cx| {
3980                                let tooltip = Tooltip::new("Transform");
3981                                if display_keybind_in_tooltip(&step_range, &editor, cx) {
3982                                    tooltip.key_binding(KeyBinding::for_action_in(
3983                                        &Assist,
3984                                        &focus_handle,
3985                                        cx,
3986                                    ))
3987                                } else {
3988                                    tooltip
3989                                }
3990                            })
3991                            .into()
3992                        }
3993                    })
3994                    .on_click({
3995                        let editor = editor.clone();
3996                        let step_range = step_range.clone();
3997                        let is_idle = matches!(status, WorkflowStepStatus::Idle);
3998                        move |_, cx| {
3999                            if is_idle {
4000                                editor
4001                                    .update(cx, |this, cx| {
4002                                        this.apply_workflow_step(step_range.clone(), cx)
4003                                    })
4004                                    .ok();
4005                            }
4006                        }
4007                    })
4008                    .map(|this| {
4009                        if let WorkflowStepStatus::Resolving = &status {
4010                            this.with_animation(
4011                                ("resolving-suggestion-animation", id),
4012                                Animation::new(Duration::from_secs(2))
4013                                    .repeat()
4014                                    .with_easing(pulsating_between(0.4, 0.8)),
4015                                |label, delta| label.alpha(delta),
4016                            )
4017                            .into_any_element()
4018                        } else {
4019                            this.into_any_element()
4020                        }
4021                    })
4022            }
4023            WorkflowStepStatus::Pending => h_flex()
4024                .items_center()
4025                .gap_2()
4026                .child(
4027                    Label::new("Applying...")
4028                        .size(LabelSize::Small)
4029                        .with_animation(
4030                            ("applying-step-transformation-label", id),
4031                            Animation::new(Duration::from_secs(2))
4032                                .repeat()
4033                                .with_easing(pulsating_between(0.4, 0.8)),
4034                            |label, delta| label.alpha(delta),
4035                        ),
4036                )
4037                .child(
4038                    IconButton::new(("stop-transformation", id), IconName::Stop)
4039                        .icon_size(IconSize::Small)
4040                        .icon_color(Color::Error)
4041                        .style(ButtonStyle::Subtle)
4042                        .tooltip({
4043                            let step_range = step_range.clone();
4044                            let editor = editor.clone();
4045                            move |cx| {
4046                                cx.new_view(|cx| {
4047                                    let tooltip = Tooltip::new("Stop Transformation");
4048                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4049                                        tooltip.key_binding(KeyBinding::for_action_in(
4050                                            &editor::actions::Cancel,
4051                                            &focus_handle,
4052                                            cx,
4053                                        ))
4054                                    } else {
4055                                        tooltip
4056                                    }
4057                                })
4058                                .into()
4059                            }
4060                        })
4061                        .on_click({
4062                            let editor = editor.clone();
4063                            let step_range = step_range.clone();
4064                            move |_, cx| {
4065                                editor
4066                                    .update(cx, |this, cx| {
4067                                        this.stop_workflow_step(step_range.clone(), cx)
4068                                    })
4069                                    .ok();
4070                            }
4071                        }),
4072                )
4073                .into_any_element(),
4074            WorkflowStepStatus::Done => h_flex()
4075                .gap_1()
4076                .child(
4077                    IconButton::new(("stop-transformation", id), IconName::Close)
4078                        .icon_size(IconSize::Small)
4079                        .style(ButtonStyle::Tinted(TintColor::Negative))
4080                        .tooltip({
4081                            let focus_handle = focus_handle.clone();
4082                            let editor = editor.clone();
4083                            let step_range = step_range.clone();
4084                            move |cx| {
4085                                cx.new_view(|cx| {
4086                                    let tooltip = Tooltip::new("Reject Transformation");
4087                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4088                                        tooltip.key_binding(KeyBinding::for_action_in(
4089                                            &editor::actions::Cancel,
4090                                            &focus_handle,
4091                                            cx,
4092                                        ))
4093                                    } else {
4094                                        tooltip
4095                                    }
4096                                })
4097                                .into()
4098                            }
4099                        })
4100                        .on_click({
4101                            let editor = editor.clone();
4102                            let step_range = step_range.clone();
4103                            move |_, cx| {
4104                                editor
4105                                    .update(cx, |this, cx| {
4106                                        this.reject_workflow_step(step_range.clone(), cx);
4107                                    })
4108                                    .ok();
4109                            }
4110                        }),
4111                )
4112                .child(
4113                    Button::new(("confirm-workflow-step", id), "Accept")
4114                        .icon(IconName::Check)
4115                        .icon_position(IconPosition::Start)
4116                        .icon_size(IconSize::Small)
4117                        .label_size(LabelSize::Small)
4118                        .style(ButtonStyle::Tinted(TintColor::Positive))
4119                        .tooltip({
4120                            let editor = editor.clone();
4121                            let step_range = step_range.clone();
4122                            move |cx| {
4123                                cx.new_view(|cx| {
4124                                    let tooltip = Tooltip::new("Accept Transformation");
4125                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4126                                        tooltip.key_binding(KeyBinding::for_action_in(
4127                                            &Assist,
4128                                            &focus_handle,
4129                                            cx,
4130                                        ))
4131                                    } else {
4132                                        tooltip
4133                                    }
4134                                })
4135                                .into()
4136                            }
4137                        })
4138                        .on_click({
4139                            let editor = editor.clone();
4140                            let step_range = step_range.clone();
4141                            move |_, cx| {
4142                                editor
4143                                    .update(cx, |this, cx| {
4144                                        this.confirm_workflow_step(step_range.clone(), cx);
4145                                    })
4146                                    .ok();
4147                            }
4148                        }),
4149                )
4150                .into_any_element(),
4151            WorkflowStepStatus::Confirmed => h_flex()
4152                .child(
4153                    Button::new(("revert-workflow-step", id), "Undo")
4154                        .style(ButtonStyle::Filled)
4155                        .icon(Some(IconName::Undo))
4156                        .icon_position(IconPosition::Start)
4157                        .icon_size(IconSize::Small)
4158                        .label_size(LabelSize::Small)
4159                        .on_click({
4160                            let editor = editor.clone();
4161                            let step_range = step_range.clone();
4162                            move |_, cx| {
4163                                editor
4164                                    .update(cx, |this, cx| {
4165                                        this.undo_workflow_step(step_range.clone(), cx);
4166                                    })
4167                                    .ok();
4168                            }
4169                        }),
4170                )
4171                .into_any_element(),
4172        }
4173    }
4174
4175    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
4176        use feature_flags::FeatureFlagAppExt;
4177        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
4178            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
4179        });
4180
4181        if nudge.map_or(false, |value| value) {
4182            Some(
4183                h_flex()
4184                    .p_3()
4185                    .border_b_1()
4186                    .border_color(cx.theme().colors().border_variant)
4187                    .bg(cx.theme().colors().editor_background)
4188                    .justify_between()
4189                    .child(
4190                        h_flex()
4191                            .gap_3()
4192                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
4193                            .child(Label::new("Zed AI is here! Get started by signing in →")),
4194                    )
4195                    .child(
4196                        Button::new("sign-in", "Sign in")
4197                            .size(ButtonSize::Compact)
4198                            .style(ButtonStyle::Filled)
4199                            .on_click(cx.listener(|this, _event, cx| {
4200                                let client = this
4201                                    .workspace
4202                                    .update(cx, |workspace, _| workspace.client().clone())
4203                                    .log_err();
4204
4205                                if let Some(client) = client {
4206                                    cx.spawn(|this, mut cx| async move {
4207                                        client.authenticate_and_connect(true, &mut cx).await?;
4208                                        this.update(&mut cx, |_, cx| cx.notify())
4209                                    })
4210                                    .detach_and_log_err(cx)
4211                                }
4212                            })),
4213                    )
4214                    .into_any_element(),
4215            )
4216        } else if let Some(configuration_error) = configuration_error(cx) {
4217            let label = match configuration_error {
4218                ConfigurationError::NoProvider => "No LLM provider selected.",
4219                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
4220            };
4221            Some(
4222                h_flex()
4223                    .px_3()
4224                    .py_2()
4225                    .border_b_1()
4226                    .border_color(cx.theme().colors().border_variant)
4227                    .bg(cx.theme().colors().editor_background)
4228                    .justify_between()
4229                    .child(
4230                        h_flex()
4231                            .gap_3()
4232                            .child(
4233                                Icon::new(IconName::Warning)
4234                                    .size(IconSize::Small)
4235                                    .color(Color::Warning),
4236                            )
4237                            .child(Label::new(label)),
4238                    )
4239                    .child(
4240                        Button::new("open-configuration", "Configure Providers")
4241                            .size(ButtonSize::Compact)
4242                            .icon(Some(IconName::SlidersVertical))
4243                            .icon_size(IconSize::Small)
4244                            .icon_position(IconPosition::Start)
4245                            .style(ButtonStyle::Filled)
4246                            .on_click({
4247                                let focus_handle = self.focus_handle(cx).clone();
4248                                move |_event, cx| {
4249                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
4250                                }
4251                            }),
4252                    )
4253                    .into_any_element(),
4254            )
4255        } else {
4256            None
4257        }
4258    }
4259
4260    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4261        let focus_handle = self.focus_handle(cx).clone();
4262        let button_text = match self.active_workflow_step() {
4263            Some((_, step)) => match step.status(cx) {
4264                WorkflowStepStatus::Error(_) => "Retry Step Resolution",
4265                WorkflowStepStatus::Resolving => "Transform",
4266                WorkflowStepStatus::Idle => "Transform",
4267                WorkflowStepStatus::Pending => "Applying...",
4268                WorkflowStepStatus::Done => "Accept",
4269                WorkflowStepStatus::Confirmed => "Send",
4270            },
4271            None => "Send",
4272        };
4273
4274        let (style, tooltip) = match token_state(&self.context, cx) {
4275            Some(TokenState::NoTokensLeft { .. }) => (
4276                ButtonStyle::Tinted(TintColor::Negative),
4277                Some(Tooltip::text("Token limit reached", cx)),
4278            ),
4279            Some(TokenState::HasMoreTokens {
4280                over_warn_threshold,
4281                ..
4282            }) => {
4283                let (style, tooltip) = if over_warn_threshold {
4284                    (
4285                        ButtonStyle::Tinted(TintColor::Warning),
4286                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
4287                    )
4288                } else {
4289                    (ButtonStyle::Filled, None)
4290                };
4291                (style, tooltip)
4292            }
4293            None => (ButtonStyle::Filled, None),
4294        };
4295
4296        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4297
4298        let has_configuration_error = configuration_error(cx).is_some();
4299        let needs_to_accept_terms = self.show_accept_terms
4300            && provider
4301                .as_ref()
4302                .map_or(false, |provider| provider.must_accept_terms(cx));
4303        let disabled = has_configuration_error || needs_to_accept_terms;
4304
4305        ButtonLike::new("send_button")
4306            .disabled(disabled)
4307            .style(style)
4308            .when_some(tooltip, |button, tooltip| {
4309                button.tooltip(move |_| tooltip.clone())
4310            })
4311            .layer(ElevationIndex::ModalSurface)
4312            .child(Label::new(button_text))
4313            .children(
4314                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
4315                    .map(|binding| binding.into_any_element()),
4316            )
4317            .on_click(move |_event, cx| {
4318                focus_handle.dispatch_action(&Assist, cx);
4319            })
4320    }
4321
4322    fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
4323        let last_error = self.last_error.as_ref()?;
4324
4325        Some(
4326            div()
4327                .absolute()
4328                .right_3()
4329                .bottom_12()
4330                .max_w_96()
4331                .py_2()
4332                .px_3()
4333                .elevation_2(cx)
4334                .occlude()
4335                .child(match last_error {
4336                    AssistError::PaymentRequired => self.render_payment_required_error(cx),
4337                    AssistError::MaxMonthlySpendReached => {
4338                        self.render_max_monthly_spend_reached_error(cx)
4339                    }
4340                    AssistError::Message(error_message) => {
4341                        self.render_assist_error(error_message, cx)
4342                    }
4343                })
4344                .into_any(),
4345        )
4346    }
4347
4348    fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
4349        const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
4350        const ACCOUNT_URL: &str = "https://zed.dev/account";
4351
4352        v_flex()
4353            .gap_0p5()
4354            .child(
4355                h_flex()
4356                    .gap_1p5()
4357                    .items_center()
4358                    .child(Icon::new(IconName::XCircle).color(Color::Error))
4359                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
4360            )
4361            .child(
4362                div()
4363                    .id("error-message")
4364                    .max_h_24()
4365                    .overflow_y_scroll()
4366                    .child(Label::new(ERROR_MESSAGE)),
4367            )
4368            .child(
4369                h_flex()
4370                    .justify_end()
4371                    .mt_1()
4372                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
4373                        |this, _, cx| {
4374                            this.last_error = None;
4375                            cx.open_url(ACCOUNT_URL);
4376                            cx.notify();
4377                        },
4378                    )))
4379                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
4380                        |this, _, cx| {
4381                            this.last_error = None;
4382                            cx.notify();
4383                        },
4384                    ))),
4385            )
4386            .into_any()
4387    }
4388
4389    fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
4390        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
4391        const ACCOUNT_URL: &str = "https://zed.dev/account";
4392
4393        v_flex()
4394            .gap_0p5()
4395            .child(
4396                h_flex()
4397                    .gap_1p5()
4398                    .items_center()
4399                    .child(Icon::new(IconName::XCircle).color(Color::Error))
4400                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
4401            )
4402            .child(
4403                div()
4404                    .id("error-message")
4405                    .max_h_24()
4406                    .overflow_y_scroll()
4407                    .child(Label::new(ERROR_MESSAGE)),
4408            )
4409            .child(
4410                h_flex()
4411                    .justify_end()
4412                    .mt_1()
4413                    .child(
4414                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
4415                            cx.listener(|this, _, cx| {
4416                                this.last_error = None;
4417                                cx.open_url(ACCOUNT_URL);
4418                                cx.notify();
4419                            }),
4420                        ),
4421                    )
4422                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
4423                        |this, _, cx| {
4424                            this.last_error = None;
4425                            cx.notify();
4426                        },
4427                    ))),
4428            )
4429            .into_any()
4430    }
4431
4432    fn render_assist_error(
4433        &self,
4434        error_message: &SharedString,
4435        cx: &mut ViewContext<Self>,
4436    ) -> AnyElement {
4437        v_flex()
4438            .gap_0p5()
4439            .child(
4440                h_flex()
4441                    .gap_1p5()
4442                    .items_center()
4443                    .child(Icon::new(IconName::XCircle).color(Color::Error))
4444                    .child(
4445                        Label::new("Error interacting with language model")
4446                            .weight(FontWeight::MEDIUM),
4447                    ),
4448            )
4449            .child(
4450                div()
4451                    .id("error-message")
4452                    .max_h_24()
4453                    .overflow_y_scroll()
4454                    .child(Label::new(error_message.clone())),
4455            )
4456            .child(
4457                h_flex()
4458                    .justify_end()
4459                    .mt_1()
4460                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
4461                        |this, _, cx| {
4462                            this.last_error = None;
4463                            cx.notify();
4464                        },
4465                    ))),
4466            )
4467            .into_any()
4468    }
4469}
4470
4471/// Returns the contents of the *outermost* fenced code block that contains the given offset.
4472fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
4473    const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
4474    const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
4475
4476    let layer = snapshot.syntax_layers().next()?;
4477
4478    let root_node = layer.node();
4479    let mut cursor = root_node.walk();
4480
4481    // Go to the first child for the given offset
4482    while cursor.goto_first_child_for_byte(offset).is_some() {
4483        // If we're at the end of the node, go to the next one.
4484        // Example: if you have a fenced-code-block, and you're on the start of the line
4485        // right after the closing ```, you want to skip the fenced-code-block and
4486        // go to the next sibling.
4487        if cursor.node().end_byte() == offset {
4488            cursor.goto_next_sibling();
4489        }
4490
4491        if cursor.node().start_byte() > offset {
4492            break;
4493        }
4494
4495        // We found the fenced code block.
4496        if cursor.node().kind() == CODE_BLOCK_NODE {
4497            // Now we need to find the child node that contains the code.
4498            cursor.goto_first_child();
4499            loop {
4500                if cursor.node().kind() == CODE_BLOCK_CONTENT {
4501                    return Some(cursor.node().byte_range());
4502                }
4503                if !cursor.goto_next_sibling() {
4504                    break;
4505                }
4506            }
4507        }
4508    }
4509
4510    None
4511}
4512
4513fn render_fold_icon_button(
4514    editor: WeakView<Editor>,
4515    icon: IconName,
4516    label: SharedString,
4517) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
4518    Arc::new(move |fold_id, fold_range, _cx| {
4519        let editor = editor.clone();
4520        ButtonLike::new(fold_id)
4521            .style(ButtonStyle::Filled)
4522            .layer(ElevationIndex::ElevatedSurface)
4523            .child(Icon::new(icon))
4524            .child(Label::new(label.clone()).single_line())
4525            .on_click(move |_, cx| {
4526                editor
4527                    .update(cx, |editor, cx| {
4528                        let buffer_start = fold_range
4529                            .start
4530                            .to_point(&editor.buffer().read(cx).read(cx));
4531                        let buffer_row = MultiBufferRow(buffer_start.row);
4532                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4533                    })
4534                    .ok();
4535            })
4536            .into_any_element()
4537    })
4538}
4539
4540#[derive(Debug, Clone, Serialize, Deserialize)]
4541struct CopyMetadata {
4542    creases: Vec<SelectedCreaseMetadata>,
4543}
4544
4545#[derive(Debug, Clone, Serialize, Deserialize)]
4546struct SelectedCreaseMetadata {
4547    range_relative_to_selection: Range<usize>,
4548    crease: CreaseMetadata,
4549}
4550
4551impl EventEmitter<EditorEvent> for ContextEditor {}
4552impl EventEmitter<SearchEvent> for ContextEditor {}
4553
4554impl Render for ContextEditor {
4555    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4556        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4557        let accept_terms = if self.show_accept_terms {
4558            provider
4559                .as_ref()
4560                .and_then(|provider| provider.render_accept_terms(cx))
4561        } else {
4562            None
4563        };
4564        let focus_handle = self
4565            .workspace
4566            .update(cx, |workspace, cx| {
4567                Some(workspace.active_item_as::<Editor>(cx)?.focus_handle(cx))
4568            })
4569            .ok()
4570            .flatten();
4571        v_flex()
4572            .key_context("ContextEditor")
4573            .capture_action(cx.listener(ContextEditor::cancel))
4574            .capture_action(cx.listener(ContextEditor::save))
4575            .capture_action(cx.listener(ContextEditor::copy))
4576            .capture_action(cx.listener(ContextEditor::cut))
4577            .capture_action(cx.listener(ContextEditor::paste))
4578            .capture_action(cx.listener(ContextEditor::cycle_message_role))
4579            .capture_action(cx.listener(ContextEditor::confirm_command))
4580            .on_action(cx.listener(ContextEditor::assist))
4581            .on_action(cx.listener(ContextEditor::split))
4582            .size_full()
4583            .children(self.render_notice(cx))
4584            .child(
4585                div()
4586                    .flex_grow()
4587                    .bg(cx.theme().colors().editor_background)
4588                    .child(self.editor.clone()),
4589            )
4590            .when_some(accept_terms, |this, element| {
4591                this.child(
4592                    div()
4593                        .absolute()
4594                        .right_3()
4595                        .bottom_12()
4596                        .max_w_96()
4597                        .py_2()
4598                        .px_3()
4599                        .elevation_2(cx)
4600                        .bg(cx.theme().colors().surface_background)
4601                        .occlude()
4602                        .child(element),
4603                )
4604            })
4605            .children(self.render_last_error(cx))
4606            .child(
4607                h_flex().w_full().relative().child(
4608                    h_flex()
4609                        .p_2()
4610                        .w_full()
4611                        .border_t_1()
4612                        .border_color(cx.theme().colors().border_variant)
4613                        .bg(cx.theme().colors().editor_background)
4614                        .child(
4615                            h_flex()
4616                                .gap_2()
4617                                .child(render_inject_context_menu(cx.view().downgrade(), cx))
4618                                .child(
4619                                    IconButton::new("quote-button", IconName::Quote)
4620                                        .icon_size(IconSize::Small)
4621                                        .on_click(|_, cx| {
4622                                            cx.dispatch_action(QuoteSelection.boxed_clone());
4623                                        })
4624                                        .tooltip(move |cx| {
4625                                            cx.new_view(|cx| {
4626                                                Tooltip::new("Insert Selection").key_binding(
4627                                                    focus_handle.as_ref().and_then(|handle| {
4628                                                        KeyBinding::for_action_in(
4629                                                            &QuoteSelection,
4630                                                            &handle,
4631                                                            cx,
4632                                                        )
4633                                                    }),
4634                                                )
4635                                            })
4636                                            .into()
4637                                        }),
4638                                ),
4639                        )
4640                        .child(
4641                            h_flex()
4642                                .w_full()
4643                                .justify_end()
4644                                .child(div().child(self.render_send_button(cx))),
4645                        ),
4646                ),
4647            )
4648    }
4649}
4650
4651impl FocusableView for ContextEditor {
4652    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4653        self.editor.focus_handle(cx)
4654    }
4655}
4656
4657impl Item for ContextEditor {
4658    type Event = editor::EditorEvent;
4659
4660    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
4661        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
4662    }
4663
4664    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
4665        match event {
4666            EditorEvent::Edited { .. } => {
4667                f(item::ItemEvent::Edit);
4668            }
4669            EditorEvent::TitleChanged => {
4670                f(item::ItemEvent::UpdateTab);
4671            }
4672            _ => {}
4673        }
4674    }
4675
4676    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
4677        Some(self.title(cx).to_string().into())
4678    }
4679
4680    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
4681        Some(Box::new(handle.clone()))
4682    }
4683
4684    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
4685        self.editor.update(cx, |editor, cx| {
4686            Item::set_nav_history(editor, nav_history, cx)
4687        })
4688    }
4689
4690    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
4691        self.editor
4692            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
4693    }
4694
4695    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
4696        self.editor.update(cx, Item::deactivated)
4697    }
4698}
4699
4700impl SearchableItem for ContextEditor {
4701    type Match = <Editor as SearchableItem>::Match;
4702
4703    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
4704        self.editor.update(cx, |editor, cx| {
4705            editor.clear_matches(cx);
4706        });
4707    }
4708
4709    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4710        self.editor
4711            .update(cx, |editor, cx| editor.update_matches(matches, cx));
4712    }
4713
4714    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
4715        self.editor
4716            .update(cx, |editor, cx| editor.query_suggestion(cx))
4717    }
4718
4719    fn activate_match(
4720        &mut self,
4721        index: usize,
4722        matches: &[Self::Match],
4723        cx: &mut ViewContext<Self>,
4724    ) {
4725        self.editor.update(cx, |editor, cx| {
4726            editor.activate_match(index, matches, cx);
4727        });
4728    }
4729
4730    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4731        self.editor
4732            .update(cx, |editor, cx| editor.select_matches(matches, cx));
4733    }
4734
4735    fn replace(
4736        &mut self,
4737        identifier: &Self::Match,
4738        query: &project::search::SearchQuery,
4739        cx: &mut ViewContext<Self>,
4740    ) {
4741        self.editor
4742            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
4743    }
4744
4745    fn find_matches(
4746        &mut self,
4747        query: Arc<project::search::SearchQuery>,
4748        cx: &mut ViewContext<Self>,
4749    ) -> Task<Vec<Self::Match>> {
4750        self.editor
4751            .update(cx, |editor, cx| editor.find_matches(query, cx))
4752    }
4753
4754    fn active_match_index(
4755        &mut self,
4756        matches: &[Self::Match],
4757        cx: &mut ViewContext<Self>,
4758    ) -> Option<usize> {
4759        self.editor
4760            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
4761    }
4762}
4763
4764impl FollowableItem for ContextEditor {
4765    fn remote_id(&self) -> Option<workspace::ViewId> {
4766        self.remote_id
4767    }
4768
4769    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
4770        let context = self.context.read(cx);
4771        Some(proto::view::Variant::ContextEditor(
4772            proto::view::ContextEditor {
4773                context_id: context.id().to_proto(),
4774                editor: if let Some(proto::view::Variant::Editor(proto)) =
4775                    self.editor.read(cx).to_state_proto(cx)
4776                {
4777                    Some(proto)
4778                } else {
4779                    None
4780                },
4781            },
4782        ))
4783    }
4784
4785    fn from_state_proto(
4786        workspace: View<Workspace>,
4787        id: workspace::ViewId,
4788        state: &mut Option<proto::view::Variant>,
4789        cx: &mut WindowContext,
4790    ) -> Option<Task<Result<View<Self>>>> {
4791        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
4792            return None;
4793        };
4794        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
4795            unreachable!()
4796        };
4797
4798        let context_id = ContextId::from_proto(state.context_id);
4799        let editor_state = state.editor?;
4800
4801        let (project, panel) = workspace.update(cx, |workspace, cx| {
4802            Some((
4803                workspace.project().clone(),
4804                workspace.panel::<AssistantPanel>(cx)?,
4805            ))
4806        })?;
4807
4808        let context_editor =
4809            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
4810
4811        Some(cx.spawn(|mut cx| async move {
4812            let context_editor = context_editor.await?;
4813            context_editor
4814                .update(&mut cx, |context_editor, cx| {
4815                    context_editor.remote_id = Some(id);
4816                    context_editor.editor.update(cx, |editor, cx| {
4817                        editor.apply_update_proto(
4818                            &project,
4819                            proto::update_view::Variant::Editor(proto::update_view::Editor {
4820                                selections: editor_state.selections,
4821                                pending_selection: editor_state.pending_selection,
4822                                scroll_top_anchor: editor_state.scroll_top_anchor,
4823                                scroll_x: editor_state.scroll_y,
4824                                scroll_y: editor_state.scroll_y,
4825                                ..Default::default()
4826                            }),
4827                            cx,
4828                        )
4829                    })
4830                })?
4831                .await?;
4832            Ok(context_editor)
4833        }))
4834    }
4835
4836    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
4837        Editor::to_follow_event(event)
4838    }
4839
4840    fn add_event_to_update_proto(
4841        &self,
4842        event: &Self::Event,
4843        update: &mut Option<proto::update_view::Variant>,
4844        cx: &WindowContext,
4845    ) -> bool {
4846        self.editor
4847            .read(cx)
4848            .add_event_to_update_proto(event, update, cx)
4849    }
4850
4851    fn apply_update_proto(
4852        &mut self,
4853        project: &Model<Project>,
4854        message: proto::update_view::Variant,
4855        cx: &mut ViewContext<Self>,
4856    ) -> Task<Result<()>> {
4857        self.editor.update(cx, |editor, cx| {
4858            editor.apply_update_proto(project, message, cx)
4859        })
4860    }
4861
4862    fn is_project_item(&self, _cx: &WindowContext) -> bool {
4863        true
4864    }
4865
4866    fn set_leader_peer_id(
4867        &mut self,
4868        leader_peer_id: Option<proto::PeerId>,
4869        cx: &mut ViewContext<Self>,
4870    ) {
4871        self.editor.update(cx, |editor, cx| {
4872            editor.set_leader_peer_id(leader_peer_id, cx)
4873        })
4874    }
4875
4876    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
4877        if existing.context.read(cx).id() == self.context.read(cx).id() {
4878            Some(item::Dedup::KeepExisting)
4879        } else {
4880            None
4881        }
4882    }
4883}
4884
4885pub struct ContextEditorToolbarItem {
4886    fs: Arc<dyn Fs>,
4887    workspace: WeakView<Workspace>,
4888    active_context_editor: Option<WeakView<ContextEditor>>,
4889    model_summary_editor: View<Editor>,
4890    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4891}
4892
4893fn active_editor_focus_handle(
4894    workspace: &WeakView<Workspace>,
4895    cx: &WindowContext<'_>,
4896) -> Option<FocusHandle> {
4897    workspace.upgrade().and_then(|workspace| {
4898        Some(
4899            workspace
4900                .read(cx)
4901                .active_item_as::<Editor>(cx)?
4902                .focus_handle(cx),
4903        )
4904    })
4905}
4906
4907fn render_inject_context_menu(
4908    active_context_editor: WeakView<ContextEditor>,
4909    cx: &mut WindowContext<'_>,
4910) -> impl IntoElement {
4911    let commands = SlashCommandRegistry::global(cx);
4912
4913    slash_command_picker::SlashCommandSelector::new(
4914        commands.clone(),
4915        active_context_editor,
4916        IconButton::new("trigger", IconName::SlashSquare)
4917            .icon_size(IconSize::Small)
4918            .tooltip(|cx| {
4919                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
4920            }),
4921    )
4922}
4923
4924impl ContextEditorToolbarItem {
4925    pub fn new(
4926        workspace: &Workspace,
4927        model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4928        model_summary_editor: View<Editor>,
4929    ) -> Self {
4930        Self {
4931            fs: workspace.app_state().fs.clone(),
4932            workspace: workspace.weak_handle(),
4933            active_context_editor: None,
4934            model_summary_editor,
4935            model_selector_menu_handle,
4936        }
4937    }
4938
4939    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
4940        let context = &self
4941            .active_context_editor
4942            .as_ref()?
4943            .upgrade()?
4944            .read(cx)
4945            .context;
4946        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
4947            TokenState::NoTokensLeft {
4948                max_token_count,
4949                token_count,
4950            } => (Color::Error, token_count, max_token_count),
4951            TokenState::HasMoreTokens {
4952                max_token_count,
4953                token_count,
4954                over_warn_threshold,
4955            } => {
4956                let color = if over_warn_threshold {
4957                    Color::Warning
4958                } else {
4959                    Color::Muted
4960                };
4961                (color, token_count, max_token_count)
4962            }
4963        };
4964        Some(
4965            h_flex()
4966                .gap_0p5()
4967                .child(
4968                    Label::new(humanize_token_count(token_count))
4969                        .size(LabelSize::Small)
4970                        .color(token_count_color),
4971                )
4972                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
4973                .child(
4974                    Label::new(humanize_token_count(max_token_count))
4975                        .size(LabelSize::Small)
4976                        .color(Color::Muted),
4977                ),
4978        )
4979    }
4980}
4981
4982impl Render for ContextEditorToolbarItem {
4983    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4984        let left_side = h_flex()
4985            .pl_1()
4986            .gap_2()
4987            .flex_1()
4988            .min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
4989            .when(self.active_context_editor.is_some(), |left_side| {
4990                left_side.child(self.model_summary_editor.clone())
4991            });
4992        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
4993        let active_model = LanguageModelRegistry::read_global(cx).active_model();
4994        let weak_self = cx.view().downgrade();
4995        let right_side = h_flex()
4996            .gap_2()
4997            // TODO display this in a nicer way, once we have a design for it.
4998            // .children({
4999            //     let project = self
5000            //         .workspace
5001            //         .upgrade()
5002            //         .map(|workspace| workspace.read(cx).project().downgrade());
5003            //
5004            //     let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
5005            //         project.and_then(|project| db.remaining_summaries(&project, cx))
5006            //     });
5007
5008            //     scan_items_remaining
5009            //         .map(|remaining_items| format!("Files to scan: {}", remaining_items))
5010            // })
5011            .child(
5012                ModelSelector::new(
5013                    self.fs.clone(),
5014                    ButtonLike::new("active-model")
5015                        .style(ButtonStyle::Subtle)
5016                        .child(
5017                            h_flex()
5018                                .w_full()
5019                                .gap_0p5()
5020                                .child(
5021                                    div()
5022                                        .overflow_x_hidden()
5023                                        .flex_grow()
5024                                        .whitespace_nowrap()
5025                                        .child(match (active_provider, active_model) {
5026                                            (Some(provider), Some(model)) => h_flex()
5027                                                .gap_1()
5028                                                .child(
5029                                                    Icon::new(model.icon().unwrap_or_else(|| provider.icon()))
5030                                                        .color(Color::Muted)
5031                                                        .size(IconSize::XSmall),
5032                                                )
5033                                                .child(
5034                                                    Label::new(model.name().0)
5035                                                        .size(LabelSize::Small)
5036                                                        .color(Color::Muted),
5037                                                )
5038                                                .into_any_element(),
5039                                            _ => Label::new("No model selected")
5040                                                .size(LabelSize::Small)
5041                                                .color(Color::Muted)
5042                                                .into_any_element(),
5043                                        }),
5044                                )
5045                                .child(
5046                                    Icon::new(IconName::ChevronDown)
5047                                        .color(Color::Muted)
5048                                        .size(IconSize::XSmall),
5049                                ),
5050                        )
5051                        .tooltip(move |cx| {
5052                            Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
5053                        }),
5054                )
5055                .with_handle(self.model_selector_menu_handle.clone()),
5056            )
5057            .children(self.render_remaining_tokens(cx))
5058            .child(
5059                PopoverMenu::new("context-editor-popover")
5060                    .trigger(
5061                        IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
5062                            .icon_size(IconSize::Small)
5063                            .tooltip(|cx| Tooltip::text("Open Context Options", cx)),
5064                    )
5065                    .menu({
5066                        let weak_self = weak_self.clone();
5067                        move |cx| {
5068                            let weak_self = weak_self.clone();
5069                            Some(ContextMenu::build(cx, move |menu, cx| {
5070                                let context = weak_self
5071                                    .update(cx, |this, cx| {
5072                                        active_editor_focus_handle(&this.workspace, cx)
5073                                    })
5074                                    .ok()
5075                                    .flatten();
5076                                menu.when_some(context, |menu, context| menu.context(context))
5077                                    .entry("Regenerate Context Title", None, {
5078                                        let weak_self = weak_self.clone();
5079                                        move |cx| {
5080                                            weak_self
5081                                                .update(cx, |_, cx| {
5082                                                    cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
5083                                                })
5084                                                .ok();
5085                                        }
5086                                    })
5087                                    .custom_entry(
5088                                        |_| {
5089                                            h_flex()
5090                                                .w_full()
5091                                                .justify_between()
5092                                                .gap_2()
5093                                                .child(Label::new("Insert Context"))
5094                                                .child(Label::new("/ command").color(Color::Muted))
5095                                                .into_any()
5096                                        },
5097                                        {
5098                                            let weak_self = weak_self.clone();
5099                                            move |cx| {
5100                                                weak_self
5101                                                    .update(cx, |this, cx| {
5102                                                        if let Some(editor) =
5103                                                        &this.active_context_editor
5104                                                        {
5105                                                            editor
5106                                                                .update(cx, |this, cx| {
5107                                                                    this.slash_menu_handle
5108                                                                        .toggle(cx);
5109                                                                })
5110                                                                .ok();
5111                                                        }
5112                                                    })
5113                                                    .ok();
5114                                            }
5115                                        },
5116                                    )
5117                                    .action("Insert Selection", QuoteSelection.boxed_clone())
5118                            }))
5119                        }
5120                    }),
5121            );
5122
5123        h_flex()
5124            .size_full()
5125            .gap_2()
5126            .justify_between()
5127            .child(left_side)
5128            .child(right_side)
5129    }
5130}
5131
5132impl ToolbarItemView for ContextEditorToolbarItem {
5133    fn set_active_pane_item(
5134        &mut self,
5135        active_pane_item: Option<&dyn ItemHandle>,
5136        cx: &mut ViewContext<Self>,
5137    ) -> ToolbarItemLocation {
5138        self.active_context_editor = active_pane_item
5139            .and_then(|item| item.act_as::<ContextEditor>(cx))
5140            .map(|editor| editor.downgrade());
5141        cx.notify();
5142        if self.active_context_editor.is_none() {
5143            ToolbarItemLocation::Hidden
5144        } else {
5145            ToolbarItemLocation::PrimaryRight
5146        }
5147    }
5148
5149    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
5150        cx.notify();
5151    }
5152}
5153
5154impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
5155
5156enum ContextEditorToolbarItemEvent {
5157    RegenerateSummary,
5158}
5159impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
5160
5161pub struct ContextHistory {
5162    picker: View<Picker<SavedContextPickerDelegate>>,
5163    _subscriptions: Vec<Subscription>,
5164    assistant_panel: WeakView<AssistantPanel>,
5165}
5166
5167impl ContextHistory {
5168    fn new(
5169        project: Model<Project>,
5170        context_store: Model<ContextStore>,
5171        assistant_panel: WeakView<AssistantPanel>,
5172        cx: &mut ViewContext<Self>,
5173    ) -> Self {
5174        let picker = cx.new_view(|cx| {
5175            Picker::uniform_list(
5176                SavedContextPickerDelegate::new(project, context_store.clone()),
5177                cx,
5178            )
5179            .modal(false)
5180            .max_height(None)
5181        });
5182
5183        let _subscriptions = vec![
5184            cx.observe(&context_store, |this, _, cx| {
5185                this.picker.update(cx, |picker, cx| picker.refresh(cx));
5186            }),
5187            cx.subscribe(&picker, Self::handle_picker_event),
5188        ];
5189
5190        Self {
5191            picker,
5192            _subscriptions,
5193            assistant_panel,
5194        }
5195    }
5196
5197    fn handle_picker_event(
5198        &mut self,
5199        _: View<Picker<SavedContextPickerDelegate>>,
5200        event: &SavedContextPickerEvent,
5201        cx: &mut ViewContext<Self>,
5202    ) {
5203        let SavedContextPickerEvent::Confirmed(context) = event;
5204        self.assistant_panel
5205            .update(cx, |assistant_panel, cx| match context {
5206                ContextMetadata::Remote(metadata) => {
5207                    assistant_panel
5208                        .open_remote_context(metadata.id.clone(), cx)
5209                        .detach_and_log_err(cx);
5210                }
5211                ContextMetadata::Saved(metadata) => {
5212                    assistant_panel
5213                        .open_saved_context(metadata.path.clone(), cx)
5214                        .detach_and_log_err(cx);
5215                }
5216            })
5217            .ok();
5218    }
5219}
5220
5221#[derive(Debug, PartialEq, Eq, Clone, Copy)]
5222pub enum WorkflowAssistStatus {
5223    Pending,
5224    Confirmed,
5225    Done,
5226    Idle,
5227}
5228
5229impl WorkflowAssist {
5230    pub fn status(&self, cx: &AppContext) -> WorkflowAssistStatus {
5231        let assistant = InlineAssistant::global(cx);
5232        if self
5233            .assist_ids
5234            .iter()
5235            .any(|assist_id| assistant.assist_status(*assist_id, cx).is_pending())
5236        {
5237            WorkflowAssistStatus::Pending
5238        } else if self
5239            .assist_ids
5240            .iter()
5241            .all(|assist_id| assistant.assist_status(*assist_id, cx).is_confirmed())
5242        {
5243            WorkflowAssistStatus::Confirmed
5244        } else if self
5245            .assist_ids
5246            .iter()
5247            .all(|assist_id| assistant.assist_status(*assist_id, cx).is_done())
5248        {
5249            WorkflowAssistStatus::Done
5250        } else {
5251            WorkflowAssistStatus::Idle
5252        }
5253    }
5254}
5255
5256impl Render for ContextHistory {
5257    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
5258        div().size_full().child(self.picker.clone())
5259    }
5260}
5261
5262impl FocusableView for ContextHistory {
5263    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
5264        self.picker.focus_handle(cx)
5265    }
5266}
5267
5268impl EventEmitter<()> for ContextHistory {}
5269
5270impl Item for ContextHistory {
5271    type Event = ();
5272
5273    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
5274        Some("History".into())
5275    }
5276}
5277
5278pub struct ConfigurationView {
5279    focus_handle: FocusHandle,
5280    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
5281    _registry_subscription: Subscription,
5282}
5283
5284impl ConfigurationView {
5285    fn new(cx: &mut ViewContext<Self>) -> Self {
5286        let focus_handle = cx.focus_handle();
5287
5288        let registry_subscription = cx.subscribe(
5289            &LanguageModelRegistry::global(cx),
5290            |this, _, event: &language_model::Event, cx| match event {
5291                language_model::Event::AddedProvider(provider_id) => {
5292                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
5293                    if let Some(provider) = provider {
5294                        this.add_configuration_view(&provider, cx);
5295                    }
5296                }
5297                language_model::Event::RemovedProvider(provider_id) => {
5298                    this.remove_configuration_view(provider_id);
5299                }
5300                _ => {}
5301            },
5302        );
5303
5304        let mut this = Self {
5305            focus_handle,
5306            configuration_views: HashMap::default(),
5307            _registry_subscription: registry_subscription,
5308        };
5309        this.build_configuration_views(cx);
5310        this
5311    }
5312
5313    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
5314        let providers = LanguageModelRegistry::read_global(cx).providers();
5315        for provider in providers {
5316            self.add_configuration_view(&provider, cx);
5317        }
5318    }
5319
5320    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
5321        self.configuration_views.remove(provider_id);
5322    }
5323
5324    fn add_configuration_view(
5325        &mut self,
5326        provider: &Arc<dyn LanguageModelProvider>,
5327        cx: &mut ViewContext<Self>,
5328    ) {
5329        let configuration_view = provider.configuration_view(cx);
5330        self.configuration_views
5331            .insert(provider.id(), configuration_view);
5332    }
5333
5334    fn render_provider_view(
5335        &mut self,
5336        provider: &Arc<dyn LanguageModelProvider>,
5337        cx: &mut ViewContext<Self>,
5338    ) -> Div {
5339        let provider_id = provider.id().0.clone();
5340        let provider_name = provider.name().0.clone();
5341        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
5342
5343        let open_new_context = cx.listener({
5344            let provider = provider.clone();
5345            move |_, _, cx| {
5346                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
5347                    provider.clone(),
5348                ))
5349            }
5350        });
5351
5352        v_flex()
5353            .gap_2()
5354            .child(
5355                h_flex()
5356                    .justify_between()
5357                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
5358                    .when(provider.is_authenticated(cx), move |this| {
5359                        this.child(
5360                            h_flex().justify_end().child(
5361                                Button::new(
5362                                    SharedString::from(format!("new-context-{provider_id}")),
5363                                    "Open new context",
5364                                )
5365                                .icon_position(IconPosition::Start)
5366                                .icon(IconName::Plus)
5367                                .style(ButtonStyle::Filled)
5368                                .layer(ElevationIndex::ModalSurface)
5369                                .on_click(open_new_context),
5370                            ),
5371                        )
5372                    }),
5373            )
5374            .child(
5375                div()
5376                    .p(Spacing::Large.rems(cx))
5377                    .bg(cx.theme().colors().surface_background)
5378                    .border_1()
5379                    .border_color(cx.theme().colors().border_variant)
5380                    .rounded_md()
5381                    .when(configuration_view.is_none(), |this| {
5382                        this.child(div().child(Label::new(format!(
5383                            "No configuration view for {}",
5384                            provider_name
5385                        ))))
5386                    })
5387                    .when_some(configuration_view, |this, configuration_view| {
5388                        this.child(configuration_view)
5389                    }),
5390            )
5391    }
5392}
5393
5394impl Render for ConfigurationView {
5395    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
5396        let providers = LanguageModelRegistry::read_global(cx).providers();
5397        let provider_views = providers
5398            .into_iter()
5399            .map(|provider| self.render_provider_view(&provider, cx))
5400            .collect::<Vec<_>>();
5401
5402        let mut element = v_flex()
5403            .id("assistant-configuration-view")
5404            .track_focus(&self.focus_handle)
5405            .bg(cx.theme().colors().editor_background)
5406            .size_full()
5407            .overflow_y_scroll()
5408            .child(
5409                v_flex()
5410                    .p(Spacing::XXLarge.rems(cx))
5411                    .border_b_1()
5412                    .border_color(cx.theme().colors().border)
5413                    .gap_1()
5414                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
5415                    .child(
5416                        Label::new(
5417                            "At least one LLM provider must be configured to use the Assistant.",
5418                        )
5419                        .color(Color::Muted),
5420                    ),
5421            )
5422            .child(
5423                v_flex()
5424                    .p(Spacing::XXLarge.rems(cx))
5425                    .mt_1()
5426                    .gap_6()
5427                    .flex_1()
5428                    .children(provider_views),
5429            )
5430            .into_any();
5431
5432        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
5433        // because we couldn't the element to take up the size of the parent.
5434        canvas(
5435            move |bounds, cx| {
5436                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
5437                element
5438            },
5439            |_, mut element, cx| {
5440                element.paint(cx);
5441            },
5442        )
5443        .flex_1()
5444        .w_full()
5445    }
5446}
5447
5448pub enum ConfigurationViewEvent {
5449    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
5450}
5451
5452impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
5453
5454impl FocusableView for ConfigurationView {
5455    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
5456        self.focus_handle.clone()
5457    }
5458}
5459
5460impl Item for ConfigurationView {
5461    type Event = ConfigurationViewEvent;
5462
5463    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
5464        Some("Configuration".into())
5465    }
5466}
5467
5468type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
5469
5470fn render_slash_command_output_toggle(
5471    row: MultiBufferRow,
5472    is_folded: bool,
5473    fold: ToggleFold,
5474    _cx: &mut WindowContext,
5475) -> AnyElement {
5476    Disclosure::new(
5477        ("slash-command-output-fold-indicator", row.0 as u64),
5478        !is_folded,
5479    )
5480    .selected(is_folded)
5481    .on_click(move |_e, cx| fold(!is_folded, cx))
5482    .into_any_element()
5483}
5484
5485fn fold_toggle(
5486    name: &'static str,
5487) -> impl Fn(
5488    MultiBufferRow,
5489    bool,
5490    Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
5491    &mut WindowContext<'_>,
5492) -> AnyElement {
5493    move |row, is_folded, fold, _cx| {
5494        Disclosure::new((name, row.0 as u64), !is_folded)
5495            .selected(is_folded)
5496            .on_click(move |_e, cx| fold(!is_folded, cx))
5497            .into_any_element()
5498    }
5499}
5500
5501fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
5502    FoldPlaceholder {
5503        render: Arc::new({
5504            move |fold_id, fold_range, _cx| {
5505                let editor = editor.clone();
5506                ButtonLike::new(fold_id)
5507                    .style(ButtonStyle::Filled)
5508                    .layer(ElevationIndex::ElevatedSurface)
5509                    .child(Icon::new(IconName::TextSnippet))
5510                    .child(Label::new(title.clone()).single_line())
5511                    .on_click(move |_, cx| {
5512                        editor
5513                            .update(cx, |editor, cx| {
5514                                let buffer_start = fold_range
5515                                    .start
5516                                    .to_point(&editor.buffer().read(cx).read(cx));
5517                                let buffer_row = MultiBufferRow(buffer_start.row);
5518                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
5519                            })
5520                            .ok();
5521                    })
5522                    .into_any_element()
5523            }
5524        }),
5525        constrain_width: false,
5526        merge_adjacent: false,
5527    }
5528}
5529
5530fn render_quote_selection_output_toggle(
5531    row: MultiBufferRow,
5532    is_folded: bool,
5533    fold: ToggleFold,
5534    _cx: &mut WindowContext,
5535) -> AnyElement {
5536    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
5537        .selected(is_folded)
5538        .on_click(move |_e, cx| fold(!is_folded, cx))
5539        .into_any_element()
5540}
5541
5542fn render_pending_slash_command_gutter_decoration(
5543    row: MultiBufferRow,
5544    status: &PendingSlashCommandStatus,
5545    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
5546) -> AnyElement {
5547    let mut icon = IconButton::new(
5548        ("slash-command-gutter-decoration", row.0),
5549        ui::IconName::TriangleRight,
5550    )
5551    .on_click(move |_e, cx| confirm_command(cx))
5552    .icon_size(ui::IconSize::Small)
5553    .size(ui::ButtonSize::None);
5554
5555    match status {
5556        PendingSlashCommandStatus::Idle => {
5557            icon = icon.icon_color(Color::Muted);
5558        }
5559        PendingSlashCommandStatus::Running { .. } => {
5560            icon = icon.selected(true);
5561        }
5562        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
5563    }
5564
5565    icon.into_any_element()
5566}
5567
5568fn render_docs_slash_command_trailer(
5569    row: MultiBufferRow,
5570    command: PendingSlashCommand,
5571    cx: &mut WindowContext,
5572) -> AnyElement {
5573    if command.arguments.is_empty() {
5574        return Empty.into_any();
5575    }
5576    let args = DocsSlashCommandArgs::parse(&command.arguments);
5577
5578    let Some(store) = args
5579        .provider()
5580        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
5581    else {
5582        return Empty.into_any();
5583    };
5584
5585    let Some(package) = args.package() else {
5586        return Empty.into_any();
5587    };
5588
5589    let mut children = Vec::new();
5590
5591    if store.is_indexing(&package) {
5592        children.push(
5593            div()
5594                .id(("crates-being-indexed", row.0))
5595                .child(Icon::new(IconName::ArrowCircle).with_animation(
5596                    "arrow-circle",
5597                    Animation::new(Duration::from_secs(4)).repeat(),
5598                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
5599                ))
5600                .tooltip({
5601                    let package = package.clone();
5602                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
5603                })
5604                .into_any_element(),
5605        );
5606    }
5607
5608    if let Some(latest_error) = store.latest_error_for_package(&package) {
5609        children.push(
5610            div()
5611                .id(("latest-error", row.0))
5612                .child(
5613                    Icon::new(IconName::Warning)
5614                        .size(IconSize::Small)
5615                        .color(Color::Warning),
5616                )
5617                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
5618                .into_any_element(),
5619        )
5620    }
5621
5622    let is_indexing = store.is_indexing(&package);
5623    let latest_error = store.latest_error_for_package(&package);
5624
5625    if !is_indexing && latest_error.is_none() {
5626        return Empty.into_any();
5627    }
5628
5629    h_flex().gap_2().children(children).into_any_element()
5630}
5631
5632fn make_lsp_adapter_delegate(
5633    project: &Model<Project>,
5634    cx: &mut AppContext,
5635) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
5636    project.update(cx, |project, cx| {
5637        // TODO: Find the right worktree.
5638        let Some(worktree) = project.worktrees(cx).next() else {
5639            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
5640        };
5641        let http_client = project.client().http_client().clone();
5642        project.lsp_store().update(cx, |lsp_store, cx| {
5643            Ok(Some(LocalLspAdapterDelegate::new(
5644                lsp_store,
5645                &worktree,
5646                http_client,
5647                project.fs().clone(),
5648                cx,
5649            ) as Arc<dyn LspAdapterDelegate>))
5650        })
5651    })
5652}
5653
5654fn slash_command_error_block_renderer(message: String) -> RenderBlock {
5655    Box::new(move |_| {
5656        div()
5657            .pl_6()
5658            .child(
5659                Label::new(format!("error: {}", message))
5660                    .single_line()
5661                    .color(Color::Error),
5662            )
5663            .into_any()
5664    })
5665}
5666
5667enum TokenState {
5668    NoTokensLeft {
5669        max_token_count: usize,
5670        token_count: usize,
5671    },
5672    HasMoreTokens {
5673        max_token_count: usize,
5674        token_count: usize,
5675        over_warn_threshold: bool,
5676    },
5677}
5678
5679fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
5680    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
5681
5682    let model = LanguageModelRegistry::read_global(cx).active_model()?;
5683    let token_count = context.read(cx).token_count()?;
5684    let max_token_count = model.max_token_count();
5685
5686    let remaining_tokens = max_token_count as isize - token_count as isize;
5687    let token_state = if remaining_tokens <= 0 {
5688        TokenState::NoTokensLeft {
5689            max_token_count,
5690            token_count,
5691        }
5692    } else {
5693        let over_warn_threshold =
5694            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
5695        TokenState::HasMoreTokens {
5696            max_token_count,
5697            token_count,
5698            over_warn_threshold,
5699        }
5700    };
5701    Some(token_state)
5702}
5703
5704fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
5705    let image_size = data
5706        .size(0)
5707        .map(|dimension| Pixels::from(u32::from(dimension)));
5708    let image_ratio = image_size.width / image_size.height;
5709    let bounds_ratio = max_size.width / max_size.height;
5710
5711    if image_size.width > max_size.width || image_size.height > max_size.height {
5712        if bounds_ratio > image_ratio {
5713            size(
5714                image_size.width * (max_size.height / image_size.height),
5715                max_size.height,
5716            )
5717        } else {
5718            size(
5719                max_size.width,
5720                image_size.height * (max_size.width / image_size.width),
5721            )
5722        }
5723    } else {
5724        size(image_size.width, image_size.height)
5725    }
5726}
5727
5728enum ConfigurationError {
5729    NoProvider,
5730    ProviderNotAuthenticated,
5731}
5732
5733fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
5734    let provider = LanguageModelRegistry::read_global(cx).active_provider();
5735    let is_authenticated = provider
5736        .as_ref()
5737        .map_or(false, |provider| provider.is_authenticated(cx));
5738
5739    if provider.is_some() && is_authenticated {
5740        return None;
5741    }
5742
5743    if provider.is_none() {
5744        return Some(ConfigurationError::NoProvider);
5745    }
5746
5747    if !is_authenticated {
5748        return Some(ConfigurationError::ProviderNotAuthenticated);
5749    }
5750
5751    None
5752}
5753
5754#[cfg(test)]
5755mod tests {
5756    use super::*;
5757    use gpui::{AppContext, Context};
5758    use language::Buffer;
5759    use unindent::Unindent;
5760
5761    #[gpui::test]
5762    fn test_find_code_blocks(cx: &mut AppContext) {
5763        let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
5764
5765        let buffer = cx.new_model(|cx| {
5766            let text = r#"
5767                line 0
5768                line 1
5769                ```rust
5770                fn main() {}
5771                ```
5772                line 5
5773                line 6
5774                line 7
5775                ```go
5776                func main() {}
5777                ```
5778                line 11
5779                ```
5780                this is plain text code block
5781                ```
5782
5783                ```go
5784                func another() {}
5785                ```
5786                line 19
5787            "#
5788            .unindent();
5789            let mut buffer = Buffer::local(text, cx);
5790            buffer.set_language(Some(markdown.clone()), cx);
5791            buffer
5792        });
5793        let snapshot = buffer.read(cx).snapshot();
5794
5795        let code_blocks = vec![
5796            Point::new(3, 0)..Point::new(4, 0),
5797            Point::new(9, 0)..Point::new(10, 0),
5798            Point::new(13, 0)..Point::new(14, 0),
5799            Point::new(17, 0)..Point::new(18, 0),
5800        ]
5801        .into_iter()
5802        .map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
5803        .collect::<Vec<_>>();
5804
5805        let expected_results = vec![
5806            (0, None),
5807            (1, None),
5808            (2, Some(code_blocks[0].clone())),
5809            (3, Some(code_blocks[0].clone())),
5810            (4, Some(code_blocks[0].clone())),
5811            (5, None),
5812            (6, None),
5813            (7, None),
5814            (8, Some(code_blocks[1].clone())),
5815            (9, Some(code_blocks[1].clone())),
5816            (10, Some(code_blocks[1].clone())),
5817            (11, None),
5818            (12, Some(code_blocks[2].clone())),
5819            (13, Some(code_blocks[2].clone())),
5820            (14, Some(code_blocks[2].clone())),
5821            (15, None),
5822            (16, Some(code_blocks[3].clone())),
5823            (17, Some(code_blocks[3].clone())),
5824            (18, Some(code_blocks[3].clone())),
5825            (19, None),
5826        ];
5827
5828        for (row, expected) in expected_results {
5829            let offset = snapshot.point_to_offset(Point::new(row, 0));
5830            let range = find_surrounding_code_block(&snapshot, offset);
5831            assert_eq!(range, expected, "unexpected result on row {:?}", row);
5832        }
5833    }
5834}