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
1506pub struct ContextEditor {
1507    context: Model<Context>,
1508    fs: Arc<dyn Fs>,
1509    workspace: WeakView<Workspace>,
1510    project: Model<Project>,
1511    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1512    editor: View<Editor>,
1513    blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
1514    image_blocks: HashSet<CustomBlockId>,
1515    scroll_position: Option<ScrollPosition>,
1516    remote_id: Option<workspace::ViewId>,
1517    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1518    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1519    pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
1520    _subscriptions: Vec<Subscription>,
1521    workflow_steps: HashMap<Range<language::Anchor>, WorkflowStepViewState>,
1522    active_workflow_step: Option<ActiveWorkflowStep>,
1523    assistant_panel: WeakView<AssistantPanel>,
1524    error_message: Option<SharedString>,
1525    show_accept_terms: bool,
1526    pub(crate) slash_menu_handle:
1527        PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
1528    // dragged_file_worktrees is used to keep references to worktrees that were added
1529    // when the user drag/dropped an external file onto the context editor. Since
1530    // the worktree is not part of the project panel, it would be dropped as soon as
1531    // the file is opened. In order to keep the worktree alive for the duration of the
1532    // context editor, we keep a reference here.
1533    dragged_file_worktrees: Vec<Model<Worktree>>,
1534}
1535
1536const DEFAULT_TAB_TITLE: &str = "New Context";
1537const MAX_TAB_TITLE_LEN: usize = 16;
1538
1539impl ContextEditor {
1540    fn for_context(
1541        context: Model<Context>,
1542        fs: Arc<dyn Fs>,
1543        workspace: WeakView<Workspace>,
1544        project: Model<Project>,
1545        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1546        assistant_panel: WeakView<AssistantPanel>,
1547        cx: &mut ViewContext<Self>,
1548    ) -> Self {
1549        let completion_provider = SlashCommandCompletionProvider::new(
1550            Some(cx.view().downgrade()),
1551            Some(workspace.clone()),
1552        );
1553
1554        let editor = cx.new_view(|cx| {
1555            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1556            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1557            editor.set_show_line_numbers(false, cx);
1558            editor.set_show_git_diff_gutter(false, cx);
1559            editor.set_show_code_actions(false, cx);
1560            editor.set_show_runnables(false, cx);
1561            editor.set_show_wrap_guides(false, cx);
1562            editor.set_show_indent_guides(false, cx);
1563            editor.set_completion_provider(Some(Box::new(completion_provider)));
1564            editor.set_collaboration_hub(Box::new(project.clone()));
1565            editor
1566        });
1567
1568        let _subscriptions = vec![
1569            cx.observe(&context, |_, _, cx| cx.notify()),
1570            cx.subscribe(&context, Self::handle_context_event),
1571            cx.subscribe(&editor, Self::handle_editor_event),
1572            cx.subscribe(&editor, Self::handle_editor_search_event),
1573        ];
1574
1575        let sections = context.read(cx).slash_command_output_sections().to_vec();
1576        let edit_step_ranges = context.read(cx).workflow_step_ranges().collect::<Vec<_>>();
1577        let mut this = Self {
1578            context,
1579            editor,
1580            lsp_adapter_delegate,
1581            blocks: Default::default(),
1582            image_blocks: Default::default(),
1583            scroll_position: None,
1584            remote_id: None,
1585            fs,
1586            workspace,
1587            project,
1588            pending_slash_command_creases: HashMap::default(),
1589            pending_slash_command_blocks: HashMap::default(),
1590            pending_tool_use_creases: HashMap::default(),
1591            _subscriptions,
1592            workflow_steps: HashMap::default(),
1593            active_workflow_step: None,
1594            assistant_panel,
1595            error_message: None,
1596            show_accept_terms: false,
1597            slash_menu_handle: Default::default(),
1598            dragged_file_worktrees: Vec::new(),
1599        };
1600        this.update_message_headers(cx);
1601        this.update_image_blocks(cx);
1602        this.insert_slash_command_output_sections(sections, false, cx);
1603        this.workflow_steps_updated(&Vec::new(), &edit_step_ranges, cx);
1604        this
1605    }
1606
1607    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1608        let command_name = DefaultSlashCommand.name();
1609        self.editor.update(cx, |editor, cx| {
1610            editor.insert(&format!("/{command_name}\n\n"), cx)
1611        });
1612        let command = self.context.update(cx, |context, cx| {
1613            context.reparse(cx);
1614            context.pending_slash_commands()[0].clone()
1615        });
1616        self.run_command(
1617            command.source_range,
1618            &command.name,
1619            &command.arguments,
1620            false,
1621            false,
1622            self.workspace.clone(),
1623            cx,
1624        );
1625    }
1626
1627    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1628        let provider = LanguageModelRegistry::read_global(cx).active_provider();
1629        if provider
1630            .as_ref()
1631            .map_or(false, |provider| provider.must_accept_terms(cx))
1632        {
1633            self.show_accept_terms = true;
1634            cx.notify();
1635            return;
1636        }
1637
1638        if !self.apply_active_workflow_step(cx) {
1639            self.error_message = None;
1640            self.send_to_model(cx);
1641            cx.notify();
1642        }
1643    }
1644
1645    fn apply_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1646        self.show_workflow_step(range.clone(), cx);
1647
1648        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1649            if let Some(assist) = workflow_step.assist.as_ref() {
1650                let assist_ids = assist.assist_ids.clone();
1651                cx.spawn(|this, mut cx| async move {
1652                    for assist_id in assist_ids {
1653                        let mut receiver = this.update(&mut cx, |_, cx| {
1654                            cx.window_context().defer(move |cx| {
1655                                InlineAssistant::update_global(cx, |assistant, cx| {
1656                                    assistant.start_assist(assist_id, cx);
1657                                })
1658                            });
1659                            InlineAssistant::update_global(cx, |assistant, _| {
1660                                assistant.observe_assist(assist_id)
1661                            })
1662                        })?;
1663                        while !receiver.borrow().is_done() {
1664                            let _ = receiver.changed().await;
1665                        }
1666                    }
1667                    anyhow::Ok(())
1668                })
1669                .detach_and_log_err(cx);
1670            }
1671        }
1672    }
1673
1674    fn apply_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
1675        let Some((range, step)) = self.active_workflow_step() else {
1676            return false;
1677        };
1678
1679        if let Some(assist) = step.assist.as_ref() {
1680            match assist.status(cx) {
1681                WorkflowAssistStatus::Pending => {}
1682                WorkflowAssistStatus::Confirmed => return false,
1683                WorkflowAssistStatus::Done => self.confirm_workflow_step(range, cx),
1684                WorkflowAssistStatus::Idle => self.apply_workflow_step(range, cx),
1685            }
1686        } else {
1687            match step.resolution.as_deref() {
1688                Some(Ok(_)) => self.apply_workflow_step(range, cx),
1689                Some(Err(_)) => self.resolve_workflow_step(range, cx),
1690                None => {}
1691            }
1692        }
1693
1694        true
1695    }
1696
1697    fn resolve_workflow_step(
1698        &mut self,
1699        range: Range<language::Anchor>,
1700        cx: &mut ViewContext<Self>,
1701    ) {
1702        self.context
1703            .update(cx, |context, cx| context.resolve_workflow_step(range, cx));
1704    }
1705
1706    fn stop_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1707        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1708            if let Some(assist) = workflow_step.assist.as_ref() {
1709                let assist_ids = assist.assist_ids.clone();
1710                cx.window_context().defer(|cx| {
1711                    InlineAssistant::update_global(cx, |assistant, cx| {
1712                        for assist_id in assist_ids {
1713                            assistant.stop_assist(assist_id, cx);
1714                        }
1715                    })
1716                });
1717            }
1718        }
1719    }
1720
1721    fn undo_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1722        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1723            if let Some(assist) = workflow_step.assist.take() {
1724                cx.window_context().defer(|cx| {
1725                    InlineAssistant::update_global(cx, |assistant, cx| {
1726                        for assist_id in assist.assist_ids {
1727                            assistant.undo_assist(assist_id, cx);
1728                        }
1729                    })
1730                });
1731            }
1732        }
1733    }
1734
1735    fn confirm_workflow_step(
1736        &mut self,
1737        range: Range<language::Anchor>,
1738        cx: &mut ViewContext<Self>,
1739    ) {
1740        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1741            if let Some(assist) = workflow_step.assist.as_ref() {
1742                let assist_ids = assist.assist_ids.clone();
1743                cx.window_context().defer(move |cx| {
1744                    InlineAssistant::update_global(cx, |assistant, cx| {
1745                        for assist_id in assist_ids {
1746                            assistant.finish_assist(assist_id, false, cx);
1747                        }
1748                    })
1749                });
1750            }
1751        }
1752    }
1753
1754    fn reject_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1755        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1756            if let Some(assist) = workflow_step.assist.take() {
1757                cx.window_context().defer(move |cx| {
1758                    InlineAssistant::update_global(cx, |assistant, cx| {
1759                        for assist_id in assist.assist_ids {
1760                            assistant.finish_assist(assist_id, true, cx);
1761                        }
1762                    })
1763                });
1764            }
1765        }
1766    }
1767
1768    fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
1769        if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
1770            let new_selection = {
1771                let cursor = user_message
1772                    .start
1773                    .to_offset(self.context.read(cx).buffer().read(cx));
1774                cursor..cursor
1775            };
1776            self.editor.update(cx, |editor, cx| {
1777                editor.change_selections(
1778                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1779                    cx,
1780                    |selections| selections.select_ranges([new_selection]),
1781                );
1782            });
1783            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1784            cx.defer(|this, _| this.scroll_position = None);
1785        }
1786    }
1787
1788    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1789        self.error_message = None;
1790
1791        if self
1792            .context
1793            .update(cx, |context, cx| context.cancel_last_assist(cx))
1794        {
1795            return;
1796        }
1797
1798        if let Some((range, active_step)) = self.active_workflow_step() {
1799            match active_step.status(cx) {
1800                WorkflowStepStatus::Pending => {
1801                    self.stop_workflow_step(range, cx);
1802                    return;
1803                }
1804                WorkflowStepStatus::Done => {
1805                    self.reject_workflow_step(range, cx);
1806                    return;
1807                }
1808                _ => {}
1809            }
1810        }
1811        cx.propagate();
1812    }
1813
1814    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
1815        let cursors = self.cursors(cx);
1816        self.context.update(cx, |context, cx| {
1817            let messages = context
1818                .messages_for_offsets(cursors, cx)
1819                .into_iter()
1820                .map(|message| message.id)
1821                .collect();
1822            context.cycle_message_roles(messages, cx)
1823        });
1824    }
1825
1826    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
1827        let selections = self.editor.read(cx).selections.all::<usize>(cx);
1828        selections
1829            .into_iter()
1830            .map(|selection| selection.head())
1831            .collect()
1832    }
1833
1834    pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
1835        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1836            self.editor.update(cx, |editor, cx| {
1837                editor.transact(cx, |editor, cx| {
1838                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
1839                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1840                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
1841                    if newest_cursor.column > 0
1842                        || snapshot
1843                            .chars_at(newest_cursor)
1844                            .next()
1845                            .map_or(false, |ch| ch != '\n')
1846                    {
1847                        editor.move_to_end_of_line(
1848                            &MoveToEndOfLine {
1849                                stop_at_soft_wraps: false,
1850                            },
1851                            cx,
1852                        );
1853                        editor.newline(&Newline, cx);
1854                    }
1855
1856                    editor.insert(&format!("/{name}"), cx);
1857                    if command.accepts_arguments() {
1858                        editor.insert(" ", cx);
1859                        editor.show_completions(&ShowCompletions::default(), cx);
1860                    }
1861                });
1862            });
1863            if !command.requires_argument() {
1864                self.confirm_command(&ConfirmCommand, cx);
1865            }
1866        }
1867    }
1868
1869    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
1870        if self.editor.read(cx).has_active_completions_menu() {
1871            return;
1872        }
1873
1874        let selections = self.editor.read(cx).selections.disjoint_anchors();
1875        let mut commands_by_range = HashMap::default();
1876        let workspace = self.workspace.clone();
1877        self.context.update(cx, |context, cx| {
1878            context.reparse(cx);
1879            for selection in selections.iter() {
1880                if let Some(command) =
1881                    context.pending_command_for_position(selection.head().text_anchor, cx)
1882                {
1883                    commands_by_range
1884                        .entry(command.source_range.clone())
1885                        .or_insert_with(|| command.clone());
1886                }
1887            }
1888        });
1889
1890        if commands_by_range.is_empty() {
1891            cx.propagate();
1892        } else {
1893            for command in commands_by_range.into_values() {
1894                self.run_command(
1895                    command.source_range,
1896                    &command.name,
1897                    &command.arguments,
1898                    true,
1899                    false,
1900                    workspace.clone(),
1901                    cx,
1902                );
1903            }
1904            cx.stop_propagation();
1905        }
1906    }
1907
1908    #[allow(clippy::too_many_arguments)]
1909    pub fn run_command(
1910        &mut self,
1911        command_range: Range<language::Anchor>,
1912        name: &str,
1913        arguments: &[String],
1914        ensure_trailing_newline: bool,
1915        expand_result: bool,
1916        workspace: WeakView<Workspace>,
1917        cx: &mut ViewContext<Self>,
1918    ) {
1919        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1920            let context = self.context.read(cx);
1921            let sections = context
1922                .slash_command_output_sections()
1923                .into_iter()
1924                .filter(|section| section.is_valid(context.buffer().read(cx)))
1925                .cloned()
1926                .collect::<Vec<_>>();
1927            let snapshot = context.buffer().read(cx).snapshot();
1928            let output = command.run(
1929                arguments,
1930                &sections,
1931                snapshot,
1932                workspace,
1933                self.lsp_adapter_delegate.clone(),
1934                cx,
1935            );
1936            self.context.update(cx, |context, cx| {
1937                context.insert_command_output(
1938                    command_range,
1939                    output,
1940                    ensure_trailing_newline,
1941                    expand_result,
1942                    cx,
1943                )
1944            });
1945        }
1946    }
1947
1948    fn handle_context_event(
1949        &mut self,
1950        _: Model<Context>,
1951        event: &ContextEvent,
1952        cx: &mut ViewContext<Self>,
1953    ) {
1954        let context_editor = cx.view().downgrade();
1955
1956        match event {
1957            ContextEvent::MessagesEdited => {
1958                self.update_message_headers(cx);
1959                self.update_image_blocks(cx);
1960                self.context.update(cx, |context, cx| {
1961                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1962                });
1963            }
1964            ContextEvent::SummaryChanged => {
1965                cx.emit(EditorEvent::TitleChanged);
1966                self.context.update(cx, |context, cx| {
1967                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1968                });
1969            }
1970            ContextEvent::StreamedCompletion => {
1971                self.editor.update(cx, |editor, cx| {
1972                    if let Some(scroll_position) = self.scroll_position {
1973                        let snapshot = editor.snapshot(cx);
1974                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
1975                        let scroll_top =
1976                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
1977                        editor.set_scroll_position(
1978                            point(scroll_position.offset_before_cursor.x, scroll_top),
1979                            cx,
1980                        );
1981                    }
1982
1983                    let new_tool_uses = self
1984                        .context
1985                        .read(cx)
1986                        .pending_tool_uses()
1987                        .into_iter()
1988                        .filter(|tool_use| {
1989                            !self
1990                                .pending_tool_use_creases
1991                                .contains_key(&tool_use.source_range)
1992                        })
1993                        .cloned()
1994                        .collect::<Vec<_>>();
1995
1996                    let buffer = editor.buffer().read(cx).snapshot(cx);
1997                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
1998                    let excerpt_id = *excerpt_id;
1999
2000                    let mut buffer_rows_to_fold = BTreeSet::new();
2001
2002                    let creases = new_tool_uses
2003                        .iter()
2004                        .map(|tool_use| {
2005                            let placeholder = FoldPlaceholder {
2006                                render: render_fold_icon_button(
2007                                    cx.view().downgrade(),
2008                                    IconName::PocketKnife,
2009                                    tool_use.name.clone().into(),
2010                                ),
2011                                constrain_width: false,
2012                                merge_adjacent: false,
2013                            };
2014                            let render_trailer =
2015                                move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2016
2017                            let start = buffer
2018                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
2019                                .unwrap();
2020                            let end = buffer
2021                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
2022                                .unwrap();
2023
2024                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2025                            buffer_rows_to_fold.insert(buffer_row);
2026
2027                            self.context.update(cx, |context, cx| {
2028                                context.insert_content(
2029                                    Content::ToolUse {
2030                                        range: tool_use.source_range.clone(),
2031                                        tool_use: LanguageModelToolUse {
2032                                            id: tool_use.id.to_string(),
2033                                            name: tool_use.name.clone(),
2034                                            input: tool_use.input.clone(),
2035                                        },
2036                                    },
2037                                    cx,
2038                                );
2039                            });
2040
2041                            Crease::new(
2042                                start..end,
2043                                placeholder,
2044                                fold_toggle("tool-use"),
2045                                render_trailer,
2046                            )
2047                        })
2048                        .collect::<Vec<_>>();
2049
2050                    let crease_ids = editor.insert_creases(creases, cx);
2051
2052                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2053                        editor.fold_at(&FoldAt { buffer_row }, cx);
2054                    }
2055
2056                    self.pending_tool_use_creases.extend(
2057                        new_tool_uses
2058                            .iter()
2059                            .map(|tool_use| tool_use.source_range.clone())
2060                            .zip(crease_ids),
2061                    );
2062                });
2063            }
2064            ContextEvent::WorkflowStepsUpdated { removed, updated } => {
2065                self.workflow_steps_updated(removed, updated, cx);
2066            }
2067            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2068                self.editor.update(cx, |editor, cx| {
2069                    let buffer = editor.buffer().read(cx).snapshot(cx);
2070                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
2071                    let excerpt_id = *excerpt_id;
2072
2073                    editor.remove_creases(
2074                        removed
2075                            .iter()
2076                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
2077                        cx,
2078                    );
2079
2080                    editor.remove_blocks(
2081                        HashSet::from_iter(
2082                            removed.iter().filter_map(|range| {
2083                                self.pending_slash_command_blocks.remove(range)
2084                            }),
2085                        ),
2086                        None,
2087                        cx,
2088                    );
2089
2090                    let crease_ids = editor.insert_creases(
2091                        updated.iter().map(|command| {
2092                            let workspace = self.workspace.clone();
2093                            let confirm_command = Arc::new({
2094                                let context_editor = context_editor.clone();
2095                                let command = command.clone();
2096                                move |cx: &mut WindowContext| {
2097                                    context_editor
2098                                        .update(cx, |context_editor, cx| {
2099                                            context_editor.run_command(
2100                                                command.source_range.clone(),
2101                                                &command.name,
2102                                                &command.arguments,
2103                                                false,
2104                                                false,
2105                                                workspace.clone(),
2106                                                cx,
2107                                            );
2108                                        })
2109                                        .ok();
2110                                }
2111                            });
2112                            let placeholder = FoldPlaceholder {
2113                                render: Arc::new(move |_, _, _| Empty.into_any()),
2114                                constrain_width: false,
2115                                merge_adjacent: false,
2116                            };
2117                            let render_toggle = {
2118                                let confirm_command = confirm_command.clone();
2119                                let command = command.clone();
2120                                move |row, _, _, _cx: &mut WindowContext| {
2121                                    render_pending_slash_command_gutter_decoration(
2122                                        row,
2123                                        &command.status,
2124                                        confirm_command.clone(),
2125                                    )
2126                                }
2127                            };
2128                            let render_trailer = {
2129                                let command = command.clone();
2130                                move |row, _unfold, cx: &mut WindowContext| {
2131                                    // TODO: In the future we should investigate how we can expose
2132                                    // this as a hook on the `SlashCommand` trait so that we don't
2133                                    // need to special-case it here.
2134                                    if command.name == DocsSlashCommand::NAME {
2135                                        return render_docs_slash_command_trailer(
2136                                            row,
2137                                            command.clone(),
2138                                            cx,
2139                                        );
2140                                    }
2141
2142                                    Empty.into_any()
2143                                }
2144                            };
2145
2146                            let start = buffer
2147                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2148                                .unwrap();
2149                            let end = buffer
2150                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2151                                .unwrap();
2152                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2153                        }),
2154                        cx,
2155                    );
2156
2157                    let block_ids = editor.insert_blocks(
2158                        updated
2159                            .iter()
2160                            .filter_map(|command| match &command.status {
2161                                PendingSlashCommandStatus::Error(error) => {
2162                                    Some((command, error.clone()))
2163                                }
2164                                _ => None,
2165                            })
2166                            .map(|(command, error_message)| BlockProperties {
2167                                style: BlockStyle::Fixed,
2168                                position: Anchor {
2169                                    buffer_id: Some(buffer_id),
2170                                    excerpt_id,
2171                                    text_anchor: command.source_range.start,
2172                                },
2173                                height: 1,
2174                                disposition: BlockDisposition::Below,
2175                                render: slash_command_error_block_renderer(error_message),
2176                                priority: 0,
2177                            }),
2178                        None,
2179                        cx,
2180                    );
2181
2182                    self.pending_slash_command_creases.extend(
2183                        updated
2184                            .iter()
2185                            .map(|command| command.source_range.clone())
2186                            .zip(crease_ids),
2187                    );
2188
2189                    self.pending_slash_command_blocks.extend(
2190                        updated
2191                            .iter()
2192                            .map(|command| command.source_range.clone())
2193                            .zip(block_ids),
2194                    );
2195                })
2196            }
2197            ContextEvent::SlashCommandFinished {
2198                output_range,
2199                sections,
2200                run_commands_in_output,
2201                expand_result,
2202            } => {
2203                self.insert_slash_command_output_sections(
2204                    sections.iter().cloned(),
2205                    *expand_result,
2206                    cx,
2207                );
2208
2209                if *run_commands_in_output {
2210                    let commands = self.context.update(cx, |context, cx| {
2211                        context.reparse(cx);
2212                        context
2213                            .pending_commands_for_range(output_range.clone(), cx)
2214                            .to_vec()
2215                    });
2216
2217                    for command in commands {
2218                        self.run_command(
2219                            command.source_range,
2220                            &command.name,
2221                            &command.arguments,
2222                            false,
2223                            false,
2224                            self.workspace.clone(),
2225                            cx,
2226                        );
2227                    }
2228                }
2229            }
2230            ContextEvent::UsePendingTools => {
2231                let pending_tool_uses = self
2232                    .context
2233                    .read(cx)
2234                    .pending_tool_uses()
2235                    .into_iter()
2236                    .filter(|tool_use| tool_use.status.is_idle())
2237                    .cloned()
2238                    .collect::<Vec<_>>();
2239
2240                for tool_use in pending_tool_uses {
2241                    let tool_registry = ToolRegistry::global(cx);
2242                    if let Some(tool) = tool_registry.tool(&tool_use.name) {
2243                        let task = tool.run(tool_use.input, self.workspace.clone(), cx);
2244
2245                        self.context.update(cx, |context, cx| {
2246                            context.insert_tool_output(tool_use.id.clone(), task, cx);
2247                        });
2248                    }
2249                }
2250            }
2251            ContextEvent::ToolFinished {
2252                tool_use_id,
2253                output_range,
2254            } => {
2255                self.editor.update(cx, |editor, cx| {
2256                    let buffer = editor.buffer().read(cx).snapshot(cx);
2257                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
2258                    let excerpt_id = *excerpt_id;
2259
2260                    let placeholder = FoldPlaceholder {
2261                        render: render_fold_icon_button(
2262                            cx.view().downgrade(),
2263                            IconName::PocketKnife,
2264                            format!("Tool Result: {tool_use_id}").into(),
2265                        ),
2266                        constrain_width: false,
2267                        merge_adjacent: false,
2268                    };
2269                    let render_trailer =
2270                        move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2271
2272                    let start = buffer
2273                        .anchor_in_excerpt(excerpt_id, output_range.start)
2274                        .unwrap();
2275                    let end = buffer
2276                        .anchor_in_excerpt(excerpt_id, output_range.end)
2277                        .unwrap();
2278
2279                    let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2280
2281                    let crease = Crease::new(
2282                        start..end,
2283                        placeholder,
2284                        fold_toggle("tool-use"),
2285                        render_trailer,
2286                    );
2287
2288                    editor.insert_creases([crease], cx);
2289                    editor.fold_at(&FoldAt { buffer_row }, cx);
2290                });
2291            }
2292            ContextEvent::Operation(_) => {}
2293            ContextEvent::ShowAssistError(error_message) => {
2294                self.error_message = Some(error_message.clone());
2295            }
2296        }
2297    }
2298
2299    fn workflow_steps_updated(
2300        &mut self,
2301        removed: &Vec<Range<text::Anchor>>,
2302        updated: &Vec<Range<text::Anchor>>,
2303        cx: &mut ViewContext<ContextEditor>,
2304    ) {
2305        let this = cx.view().downgrade();
2306        let mut removed_crease_ids = Vec::new();
2307        let mut removed_block_ids = HashSet::default();
2308        let mut editors_to_close = Vec::new();
2309        for range in removed {
2310            if let Some(state) = self.workflow_steps.remove(range) {
2311                editors_to_close.extend(self.hide_workflow_step(range.clone(), cx));
2312                removed_block_ids.insert(state.header_block_id);
2313                removed_crease_ids.push(state.header_crease_id);
2314                removed_block_ids.extend(state.footer_block_id);
2315                removed_crease_ids.extend(state.footer_crease_id);
2316            }
2317        }
2318
2319        for range in updated {
2320            editors_to_close.extend(self.hide_workflow_step(range.clone(), cx));
2321        }
2322
2323        self.editor.update(cx, |editor, cx| {
2324            let snapshot = editor.snapshot(cx);
2325            let multibuffer = &snapshot.buffer_snapshot;
2326            let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2327
2328            for range in updated {
2329                let Some(step) = self.context.read(cx).workflow_step_for_range(&range, cx) else {
2330                    continue;
2331                };
2332
2333                let resolution = step.resolution.clone();
2334                let header_start = step.range.start;
2335                let header_end = if buffer.contains_str_at(step.leading_tags_end, "\n") {
2336                    buffer.anchor_before(step.leading_tags_end.to_offset(&buffer) + 1)
2337                } else {
2338                    step.leading_tags_end
2339                };
2340                let header_range = multibuffer
2341                    .anchor_in_excerpt(excerpt_id, header_start)
2342                    .unwrap()
2343                    ..multibuffer
2344                        .anchor_in_excerpt(excerpt_id, header_end)
2345                        .unwrap();
2346                let footer_range = step.trailing_tag_start.map(|start| {
2347                    let mut step_range_end = step.range.end.to_offset(&buffer);
2348                    if buffer.contains_str_at(step_range_end, "\n") {
2349                        // Only include the newline if it belongs to the same message.
2350                        let messages = self
2351                            .context
2352                            .read(cx)
2353                            .messages_for_offsets([step_range_end, step_range_end + 1], cx);
2354                        if messages.len() == 1 {
2355                            step_range_end += 1;
2356                        }
2357                    }
2358
2359                    let end = buffer.anchor_before(step_range_end);
2360                    multibuffer.anchor_in_excerpt(excerpt_id, start).unwrap()
2361                        ..multibuffer.anchor_in_excerpt(excerpt_id, end).unwrap()
2362                });
2363
2364                let block_ids = editor.insert_blocks(
2365                    [BlockProperties {
2366                        position: header_range.start,
2367                        height: 1,
2368                        style: BlockStyle::Flex,
2369                        render: Box::new({
2370                            let this = this.clone();
2371                            let range = step.range.clone();
2372                            move |cx| {
2373                                let block_id = cx.block_id;
2374                                let max_width = cx.max_width;
2375                                let gutter_width = cx.gutter_dimensions.full_width();
2376                                this.update(&mut **cx, |this, cx| {
2377                                    this.render_workflow_step_header(
2378                                        range.clone(),
2379                                        max_width,
2380                                        gutter_width,
2381                                        block_id,
2382                                        cx,
2383                                    )
2384                                })
2385                                .ok()
2386                                .flatten()
2387                                .unwrap_or_else(|| Empty.into_any())
2388                            }
2389                        }),
2390                        disposition: BlockDisposition::Above,
2391                        priority: 0,
2392                    }]
2393                    .into_iter()
2394                    .chain(footer_range.as_ref().map(|footer_range| {
2395                        return BlockProperties {
2396                            position: footer_range.end,
2397                            height: 1,
2398                            style: BlockStyle::Flex,
2399                            render: Box::new({
2400                                let this = this.clone();
2401                                let range = step.range.clone();
2402                                move |cx| {
2403                                    let max_width = cx.max_width;
2404                                    let gutter_width = cx.gutter_dimensions.full_width();
2405                                    this.update(&mut **cx, |this, cx| {
2406                                        this.render_workflow_step_footer(
2407                                            range.clone(),
2408                                            max_width,
2409                                            gutter_width,
2410                                            cx,
2411                                        )
2412                                    })
2413                                    .ok()
2414                                    .flatten()
2415                                    .unwrap_or_else(|| Empty.into_any())
2416                                }
2417                            }),
2418                            disposition: BlockDisposition::Below,
2419                            priority: 0,
2420                        };
2421                    })),
2422                    None,
2423                    cx,
2424                );
2425
2426                let header_placeholder = FoldPlaceholder {
2427                    render: Arc::new(move |_, _crease_range, _cx| Empty.into_any()),
2428                    constrain_width: false,
2429                    merge_adjacent: false,
2430                };
2431                let footer_placeholder = FoldPlaceholder {
2432                    render: render_fold_icon_button(
2433                        cx.view().downgrade(),
2434                        IconName::Code,
2435                        "Edits".into(),
2436                    ),
2437                    constrain_width: false,
2438                    merge_adjacent: false,
2439                };
2440
2441                let new_crease_ids = editor.insert_creases(
2442                    [Crease::new(
2443                        header_range.clone(),
2444                        header_placeholder.clone(),
2445                        fold_toggle("step-header"),
2446                        |_, _, _| Empty.into_any_element(),
2447                    )]
2448                    .into_iter()
2449                    .chain(footer_range.clone().map(|footer_range| {
2450                        Crease::new(
2451                            footer_range,
2452                            footer_placeholder.clone(),
2453                            |row, is_folded, fold, cx| {
2454                                if is_folded {
2455                                    Empty.into_any_element()
2456                                } else {
2457                                    fold_toggle("step-footer")(row, is_folded, fold, cx)
2458                                }
2459                            },
2460                            |_, _, _| Empty.into_any_element(),
2461                        )
2462                    })),
2463                    cx,
2464                );
2465
2466                let state = WorkflowStepViewState {
2467                    header_block_id: block_ids[0],
2468                    header_crease_id: new_crease_ids[0],
2469                    footer_block_id: block_ids.get(1).copied(),
2470                    footer_crease_id: new_crease_ids.get(1).copied(),
2471                    resolution,
2472                    assist: None,
2473                };
2474
2475                let mut folds_to_insert = [(header_range.clone(), header_placeholder)]
2476                    .into_iter()
2477                    .chain(
2478                        footer_range
2479                            .clone()
2480                            .map(|range| (range, footer_placeholder)),
2481                    )
2482                    .collect::<Vec<_>>();
2483
2484                match self.workflow_steps.entry(range.clone()) {
2485                    hash_map::Entry::Vacant(entry) => {
2486                        entry.insert(state);
2487                    }
2488                    hash_map::Entry::Occupied(mut entry) => {
2489                        let entry = entry.get_mut();
2490                        removed_block_ids.insert(entry.header_block_id);
2491                        removed_crease_ids.push(entry.header_crease_id);
2492                        removed_block_ids.extend(entry.footer_block_id);
2493                        removed_crease_ids.extend(entry.footer_crease_id);
2494                        folds_to_insert.retain(|(range, _)| snapshot.intersects_fold(range.start));
2495                        *entry = state;
2496                    }
2497                }
2498
2499                editor.unfold_ranges(
2500                    [header_range.clone()]
2501                        .into_iter()
2502                        .chain(footer_range.clone()),
2503                    true,
2504                    false,
2505                    cx,
2506                );
2507
2508                if !folds_to_insert.is_empty() {
2509                    editor.fold_ranges(folds_to_insert, false, cx);
2510                }
2511            }
2512
2513            editor.remove_creases(removed_crease_ids, cx);
2514            editor.remove_blocks(removed_block_ids, None, cx);
2515        });
2516
2517        for (editor, editor_was_open) in editors_to_close {
2518            self.close_workflow_editor(cx, editor, editor_was_open);
2519        }
2520
2521        self.update_active_workflow_step(cx);
2522    }
2523
2524    fn insert_slash_command_output_sections(
2525        &mut self,
2526        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2527        expand_result: bool,
2528        cx: &mut ViewContext<Self>,
2529    ) {
2530        self.editor.update(cx, |editor, cx| {
2531            let buffer = editor.buffer().read(cx).snapshot(cx);
2532            let excerpt_id = *buffer.as_singleton().unwrap().0;
2533            let mut buffer_rows_to_fold = BTreeSet::new();
2534            let mut creases = Vec::new();
2535            for section in sections {
2536                let start = buffer
2537                    .anchor_in_excerpt(excerpt_id, section.range.start)
2538                    .unwrap();
2539                let end = buffer
2540                    .anchor_in_excerpt(excerpt_id, section.range.end)
2541                    .unwrap();
2542                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2543                buffer_rows_to_fold.insert(buffer_row);
2544                creases.push(
2545                    Crease::new(
2546                        start..end,
2547                        FoldPlaceholder {
2548                            render: render_fold_icon_button(
2549                                cx.view().downgrade(),
2550                                section.icon,
2551                                section.label.clone(),
2552                            ),
2553                            constrain_width: false,
2554                            merge_adjacent: false,
2555                        },
2556                        render_slash_command_output_toggle,
2557                        |_, _, _| Empty.into_any_element(),
2558                    )
2559                    .with_metadata(CreaseMetadata {
2560                        icon: section.icon,
2561                        label: section.label,
2562                    }),
2563                );
2564            }
2565
2566            editor.insert_creases(creases, cx);
2567
2568            if expand_result {
2569                buffer_rows_to_fold.clear();
2570            }
2571            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2572                editor.fold_at(&FoldAt { buffer_row }, cx);
2573            }
2574        });
2575    }
2576
2577    fn handle_editor_event(
2578        &mut self,
2579        _: View<Editor>,
2580        event: &EditorEvent,
2581        cx: &mut ViewContext<Self>,
2582    ) {
2583        match event {
2584            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2585                let cursor_scroll_position = self.cursor_scroll_position(cx);
2586                if *autoscroll {
2587                    self.scroll_position = cursor_scroll_position;
2588                } else if self.scroll_position != cursor_scroll_position {
2589                    self.scroll_position = None;
2590                }
2591            }
2592            EditorEvent::SelectionsChanged { .. } => {
2593                self.scroll_position = self.cursor_scroll_position(cx);
2594                self.update_active_workflow_step(cx);
2595            }
2596            _ => {}
2597        }
2598        cx.emit(event.clone());
2599    }
2600
2601    fn active_workflow_step(&self) -> Option<(Range<text::Anchor>, &WorkflowStepViewState)> {
2602        let step = self.active_workflow_step.as_ref()?;
2603        Some((step.range.clone(), self.workflow_steps.get(&step.range)?))
2604    }
2605
2606    fn update_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) {
2607        let newest_cursor = self.editor.read(cx).selections.newest::<usize>(cx).head();
2608        let context = self.context.read(cx);
2609
2610        let new_step = context
2611            .workflow_step_containing(newest_cursor, cx)
2612            .map(|step| ActiveWorkflowStep {
2613                resolved: step.resolution.is_some(),
2614                range: step.range.clone(),
2615            });
2616
2617        if new_step.as_ref() != self.active_workflow_step.as_ref() {
2618            let mut old_editor = None;
2619            let mut old_editor_was_open = None;
2620            if let Some(old_step) = self.active_workflow_step.take() {
2621                (old_editor, old_editor_was_open) =
2622                    self.hide_workflow_step(old_step.range, cx).unzip();
2623            }
2624
2625            let mut new_editor = None;
2626            if let Some(new_step) = new_step {
2627                new_editor = self.show_workflow_step(new_step.range.clone(), cx);
2628                self.active_workflow_step = Some(new_step);
2629            }
2630
2631            if new_editor != old_editor {
2632                if let Some((old_editor, old_editor_was_open)) = old_editor.zip(old_editor_was_open)
2633                {
2634                    self.close_workflow_editor(cx, old_editor, old_editor_was_open)
2635                }
2636            }
2637        }
2638    }
2639
2640    fn hide_workflow_step(
2641        &mut self,
2642        step_range: Range<language::Anchor>,
2643        cx: &mut ViewContext<Self>,
2644    ) -> Option<(View<Editor>, bool)> {
2645        if let Some(step) = self.workflow_steps.get_mut(&step_range) {
2646            let assist = step.assist.as_ref()?;
2647            let editor = assist.editor.upgrade()?;
2648
2649            if matches!(step.status(cx), WorkflowStepStatus::Idle) {
2650                let assist = step.assist.take().unwrap();
2651                InlineAssistant::update_global(cx, |assistant, cx| {
2652                    for assist_id in assist.assist_ids {
2653                        assistant.finish_assist(assist_id, true, cx)
2654                    }
2655                });
2656                return Some((editor, assist.editor_was_open));
2657            }
2658        }
2659
2660        None
2661    }
2662
2663    fn close_workflow_editor(
2664        &mut self,
2665        cx: &mut ViewContext<ContextEditor>,
2666        editor: View<Editor>,
2667        editor_was_open: bool,
2668    ) {
2669        self.workspace
2670            .update(cx, |workspace, cx| {
2671                if let Some(pane) = workspace.pane_for(&editor) {
2672                    pane.update(cx, |pane, cx| {
2673                        let item_id = editor.entity_id();
2674                        if !editor_was_open && !editor.read(cx).is_focused(cx) {
2675                            pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
2676                                .detach_and_log_err(cx);
2677                        }
2678                    });
2679                }
2680            })
2681            .ok();
2682    }
2683
2684    fn show_workflow_step(
2685        &mut self,
2686        step_range: Range<language::Anchor>,
2687        cx: &mut ViewContext<Self>,
2688    ) -> Option<View<Editor>> {
2689        let step = self.workflow_steps.get_mut(&step_range)?;
2690
2691        let mut editor_to_return = None;
2692        let mut scroll_to_assist_id = None;
2693        match step.status(cx) {
2694            WorkflowStepStatus::Idle => {
2695                if let Some(assist) = step.assist.as_ref() {
2696                    scroll_to_assist_id = assist.assist_ids.first().copied();
2697                } else if let Some(Ok(resolved)) = step.resolution.clone().as_deref() {
2698                    step.assist = Self::open_assists_for_step(
2699                        &resolved,
2700                        &self.project,
2701                        &self.assistant_panel,
2702                        &self.workspace,
2703                        cx,
2704                    );
2705                    editor_to_return = step
2706                        .assist
2707                        .as_ref()
2708                        .and_then(|assist| assist.editor.upgrade());
2709                }
2710            }
2711            WorkflowStepStatus::Pending => {
2712                if let Some(assist) = step.assist.as_ref() {
2713                    let assistant = InlineAssistant::global(cx);
2714                    scroll_to_assist_id = assist
2715                        .assist_ids
2716                        .iter()
2717                        .copied()
2718                        .find(|assist_id| assistant.assist_status(*assist_id, cx).is_pending());
2719                }
2720            }
2721            WorkflowStepStatus::Done => {
2722                if let Some(assist) = step.assist.as_ref() {
2723                    scroll_to_assist_id = assist.assist_ids.first().copied();
2724                }
2725            }
2726            _ => {}
2727        }
2728
2729        if let Some(assist_id) = scroll_to_assist_id {
2730            if let Some(assist_editor) = step
2731                .assist
2732                .as_ref()
2733                .and_then(|assists| assists.editor.upgrade())
2734            {
2735                editor_to_return = Some(assist_editor.clone());
2736                self.workspace
2737                    .update(cx, |workspace, cx| {
2738                        workspace.activate_item(&assist_editor, false, false, cx);
2739                    })
2740                    .ok();
2741                InlineAssistant::update_global(cx, |assistant, cx| {
2742                    assistant.scroll_to_assist(assist_id, cx)
2743                });
2744            }
2745        }
2746
2747        editor_to_return
2748    }
2749
2750    fn open_assists_for_step(
2751        resolved_step: &WorkflowStepResolution,
2752        project: &Model<Project>,
2753        assistant_panel: &WeakView<AssistantPanel>,
2754        workspace: &WeakView<Workspace>,
2755        cx: &mut ViewContext<Self>,
2756    ) -> Option<WorkflowAssist> {
2757        let assistant_panel = assistant_panel.upgrade()?;
2758        if resolved_step.suggestion_groups.is_empty() {
2759            return None;
2760        }
2761
2762        let editor;
2763        let mut editor_was_open = false;
2764        let mut suggestion_groups = Vec::new();
2765        if resolved_step.suggestion_groups.len() == 1
2766            && resolved_step
2767                .suggestion_groups
2768                .values()
2769                .next()
2770                .unwrap()
2771                .len()
2772                == 1
2773        {
2774            // If there's only one buffer and one suggestion group, open it directly
2775            let (buffer, groups) = resolved_step.suggestion_groups.iter().next().unwrap();
2776            let group = groups.into_iter().next().unwrap();
2777            editor = workspace
2778                .update(cx, |workspace, cx| {
2779                    let active_pane = workspace.active_pane().clone();
2780                    editor_was_open =
2781                        workspace.is_project_item_open::<Editor>(&active_pane, buffer, cx);
2782                    workspace.open_project_item::<Editor>(
2783                        active_pane,
2784                        buffer.clone(),
2785                        false,
2786                        false,
2787                        cx,
2788                    )
2789                })
2790                .log_err()?;
2791            let (&excerpt_id, _, _) = editor
2792                .read(cx)
2793                .buffer()
2794                .read(cx)
2795                .read(cx)
2796                .as_singleton()
2797                .unwrap();
2798
2799            // Scroll the editor to the suggested assist
2800            editor.update(cx, |editor, cx| {
2801                let multibuffer = editor.buffer().read(cx).snapshot(cx);
2802                let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2803                let anchor = if group.context_range.start.to_offset(buffer) == 0 {
2804                    Anchor::min()
2805                } else {
2806                    multibuffer
2807                        .anchor_in_excerpt(excerpt_id, group.context_range.start)
2808                        .unwrap()
2809                };
2810
2811                editor.set_scroll_anchor(
2812                    ScrollAnchor {
2813                        offset: gpui::Point::default(),
2814                        anchor,
2815                    },
2816                    cx,
2817                );
2818            });
2819
2820            suggestion_groups.push((excerpt_id, group));
2821        } else {
2822            // If there are multiple buffers or suggestion groups, create a multibuffer
2823            let multibuffer = cx.new_model(|cx| {
2824                let mut multibuffer =
2825                    MultiBuffer::new(Capability::ReadWrite).with_title(resolved_step.title.clone());
2826                for (buffer, groups) in &resolved_step.suggestion_groups {
2827                    let excerpt_ids = multibuffer.push_excerpts(
2828                        buffer.clone(),
2829                        groups.iter().map(|suggestion_group| ExcerptRange {
2830                            context: suggestion_group.context_range.clone(),
2831                            primary: None,
2832                        }),
2833                        cx,
2834                    );
2835                    suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
2836                }
2837                multibuffer
2838            });
2839
2840            editor = cx.new_view(|cx| {
2841                Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx)
2842            });
2843            workspace
2844                .update(cx, |workspace, cx| {
2845                    workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2846                })
2847                .log_err()?;
2848        }
2849
2850        let mut assist_ids = Vec::new();
2851        for (excerpt_id, suggestion_group) in suggestion_groups {
2852            for suggestion in &suggestion_group.suggestions {
2853                assist_ids.extend(suggestion.show(
2854                    &editor,
2855                    excerpt_id,
2856                    workspace,
2857                    &assistant_panel,
2858                    cx,
2859                ));
2860            }
2861        }
2862
2863        Some(WorkflowAssist {
2864            assist_ids,
2865            editor: editor.downgrade(),
2866            editor_was_open,
2867        })
2868    }
2869
2870    fn handle_editor_search_event(
2871        &mut self,
2872        _: View<Editor>,
2873        event: &SearchEvent,
2874        cx: &mut ViewContext<Self>,
2875    ) {
2876        cx.emit(event.clone());
2877    }
2878
2879    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2880        self.editor.update(cx, |editor, cx| {
2881            let snapshot = editor.snapshot(cx);
2882            let cursor = editor.selections.newest_anchor().head();
2883            let cursor_row = cursor
2884                .to_display_point(&snapshot.display_snapshot)
2885                .row()
2886                .as_f32();
2887            let scroll_position = editor
2888                .scroll_manager
2889                .anchor()
2890                .scroll_position(&snapshot.display_snapshot);
2891
2892            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2893            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2894                Some(ScrollPosition {
2895                    cursor,
2896                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2897                })
2898            } else {
2899                None
2900            }
2901        })
2902    }
2903
2904    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2905        self.editor.update(cx, |editor, cx| {
2906            let buffer = editor.buffer().read(cx).snapshot(cx);
2907
2908            let excerpt_id = *buffer.as_singleton().unwrap().0;
2909            let mut old_blocks = std::mem::take(&mut self.blocks);
2910            let mut blocks_to_remove: HashMap<_, _> = old_blocks
2911                .iter()
2912                .map(|(message_id, (_, block_id))| (*message_id, *block_id))
2913                .collect();
2914            let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
2915
2916            let render_block = |message: MessageMetadata| -> RenderBlock {
2917                Box::new({
2918                    let context = self.context.clone();
2919                    move |cx| {
2920                        let message_id = MessageId(message.timestamp);
2921                        let show_spinner = message.role == Role::Assistant
2922                            && message.status == MessageStatus::Pending;
2923
2924                        let label = match message.role {
2925                            Role::User => {
2926                                Label::new("You").color(Color::Default).into_any_element()
2927                            }
2928                            Role::Assistant => {
2929                                let label = Label::new("Assistant").color(Color::Info);
2930                                if show_spinner {
2931                                    label
2932                                        .with_animation(
2933                                            "pulsating-label",
2934                                            Animation::new(Duration::from_secs(2))
2935                                                .repeat()
2936                                                .with_easing(pulsating_between(0.4, 0.8)),
2937                                            |label, delta| label.alpha(delta),
2938                                        )
2939                                        .into_any_element()
2940                                } else {
2941                                    label.into_any_element()
2942                                }
2943                            }
2944
2945                            Role::System => Label::new("System")
2946                                .color(Color::Warning)
2947                                .into_any_element(),
2948                        };
2949
2950                        let sender = ButtonLike::new("role")
2951                            .style(ButtonStyle::Filled)
2952                            .child(label)
2953                            .tooltip(|cx| {
2954                                Tooltip::with_meta(
2955                                    "Toggle message role",
2956                                    None,
2957                                    "Available roles: You (User), Assistant, System",
2958                                    cx,
2959                                )
2960                            })
2961                            .on_click({
2962                                let context = context.clone();
2963                                move |_, cx| {
2964                                    context.update(cx, |context, cx| {
2965                                        context.cycle_message_roles(
2966                                            HashSet::from_iter(Some(message_id)),
2967                                            cx,
2968                                        )
2969                                    })
2970                                }
2971                            });
2972
2973                        h_flex()
2974                            .id(("message_header", message_id.as_u64()))
2975                            .pl(cx.gutter_dimensions.full_width())
2976                            .h_11()
2977                            .w_full()
2978                            .relative()
2979                            .gap_1()
2980                            .child(sender)
2981                            .children(match &message.cache {
2982                                Some(cache) if cache.is_final_anchor => match cache.status {
2983                                    CacheStatus::Cached => Some(
2984                                        div()
2985                                            .id("cached")
2986                                            .child(
2987                                                Icon::new(IconName::DatabaseZap)
2988                                                    .size(IconSize::XSmall)
2989                                                    .color(Color::Hint),
2990                                            )
2991                                            .tooltip(|cx| {
2992                                                Tooltip::with_meta(
2993                                                    "Context cached",
2994                                                    None,
2995                                                    "Large messages cached to optimize performance",
2996                                                    cx,
2997                                                )
2998                                            })
2999                                            .into_any_element(),
3000                                    ),
3001                                    CacheStatus::Pending => Some(
3002                                        div()
3003                                            .child(
3004                                                Icon::new(IconName::Ellipsis)
3005                                                    .size(IconSize::XSmall)
3006                                                    .color(Color::Hint),
3007                                            )
3008                                            .into_any_element(),
3009                                    ),
3010                                },
3011                                _ => None,
3012                            })
3013                            .children(match &message.status {
3014                                MessageStatus::Error(error) => Some(
3015                                    Button::new("show-error", "Error")
3016                                        .color(Color::Error)
3017                                        .selected_label_color(Color::Error)
3018                                        .selected_icon_color(Color::Error)
3019                                        .icon(IconName::XCircle)
3020                                        .icon_color(Color::Error)
3021                                        .icon_size(IconSize::Small)
3022                                        .icon_position(IconPosition::Start)
3023                                        .tooltip(move |cx| {
3024                                            Tooltip::with_meta(
3025                                                "Error interacting with language model",
3026                                                None,
3027                                                "Click for more details",
3028                                                cx,
3029                                            )
3030                                        })
3031                                        .on_click({
3032                                            let context = context.clone();
3033                                            let error = error.clone();
3034                                            move |_, cx| {
3035                                                context.update(cx, |_, cx| {
3036                                                    cx.emit(ContextEvent::ShowAssistError(
3037                                                        error.clone(),
3038                                                    ));
3039                                                });
3040                                            }
3041                                        })
3042                                        .into_any_element(),
3043                                ),
3044                                MessageStatus::Canceled => Some(
3045                                    ButtonLike::new("canceled")
3046                                        .child(Icon::new(IconName::XCircle).color(Color::Disabled))
3047                                        .child(
3048                                            Label::new("Canceled")
3049                                                .size(LabelSize::Small)
3050                                                .color(Color::Disabled),
3051                                        )
3052                                        .tooltip(move |cx| {
3053                                            Tooltip::with_meta(
3054                                                "Canceled",
3055                                                None,
3056                                                "Interaction with the assistant was canceled",
3057                                                cx,
3058                                            )
3059                                        })
3060                                        .into_any_element(),
3061                                ),
3062                                _ => None,
3063                            })
3064                            .into_any_element()
3065                    }
3066                })
3067            };
3068            let create_block_properties = |message: &Message| BlockProperties {
3069                position: buffer
3070                    .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
3071                    .unwrap(),
3072                height: 2,
3073                style: BlockStyle::Sticky,
3074                disposition: BlockDisposition::Above,
3075                priority: usize::MAX,
3076                render: render_block(MessageMetadata::from(message)),
3077            };
3078            let mut new_blocks = vec![];
3079            let mut block_index_to_message = vec![];
3080            for message in self.context.read(cx).messages(cx) {
3081                if let Some(_) = blocks_to_remove.remove(&message.id) {
3082                    // This is an old message that we might modify.
3083                    let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
3084                        debug_assert!(
3085                            false,
3086                            "old_blocks should contain a message_id we've just removed."
3087                        );
3088                        continue;
3089                    };
3090                    // Should we modify it?
3091                    let message_meta = MessageMetadata::from(&message);
3092                    if meta != &message_meta {
3093                        blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
3094                        *meta = message_meta;
3095                    }
3096                } else {
3097                    // This is a new message.
3098                    new_blocks.push(create_block_properties(&message));
3099                    block_index_to_message.push((message.id, MessageMetadata::from(&message)));
3100                }
3101            }
3102            editor.replace_blocks(blocks_to_replace, None, cx);
3103            editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
3104
3105            let ids = editor.insert_blocks(new_blocks, None, cx);
3106            old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
3107                |(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
3108            ));
3109            self.blocks = old_blocks;
3110        });
3111    }
3112
3113    /// Returns either the selected text, or the content of the Markdown code
3114    /// block surrounding the cursor.
3115    fn get_selection_or_code_block(
3116        context_editor_view: &View<ContextEditor>,
3117        cx: &mut ViewContext<Workspace>,
3118    ) -> Option<(String, bool)> {
3119        const CODE_FENCE_DELIMITER: &'static str = "```";
3120
3121        let context_editor = context_editor_view.read(cx).editor.read(cx);
3122
3123        if context_editor.selections.newest::<Point>(cx).is_empty() {
3124            let snapshot = context_editor.buffer().read(cx).snapshot(cx);
3125            let (_, _, snapshot) = snapshot.as_singleton()?;
3126
3127            let head = context_editor.selections.newest::<Point>(cx).head();
3128            let offset = snapshot.point_to_offset(head);
3129
3130            let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
3131            let mut text = snapshot
3132                .text_for_range(surrounding_code_block_range)
3133                .collect::<String>();
3134
3135            // If there is no newline trailing the closing three-backticks, then
3136            // tree-sitter-md extends the range of the content node to include
3137            // the backticks.
3138            if text.ends_with(CODE_FENCE_DELIMITER) {
3139                text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
3140            }
3141
3142            (!text.is_empty()).then_some((text, true))
3143        } else {
3144            let anchor = context_editor.selections.newest_anchor();
3145            let text = context_editor
3146                .buffer()
3147                .read(cx)
3148                .read(cx)
3149                .text_for_range(anchor.range())
3150                .collect::<String>();
3151
3152            (!text.is_empty()).then_some((text, false))
3153        }
3154    }
3155
3156    fn insert_selection(
3157        workspace: &mut Workspace,
3158        _: &InsertIntoEditor,
3159        cx: &mut ViewContext<Workspace>,
3160    ) {
3161        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3162            return;
3163        };
3164        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3165            return;
3166        };
3167        let Some(active_editor_view) = workspace
3168            .active_item(cx)
3169            .and_then(|item| item.act_as::<Editor>(cx))
3170        else {
3171            return;
3172        };
3173
3174        if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
3175            active_editor_view.update(cx, |editor, cx| {
3176                editor.insert(&text, cx);
3177                editor.focus(cx);
3178            })
3179        }
3180    }
3181
3182    fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
3183        let result = maybe!({
3184            let panel = workspace.panel::<AssistantPanel>(cx)?;
3185            let context_editor_view = panel.read(cx).active_context_editor(cx)?;
3186            Self::get_selection_or_code_block(&context_editor_view, cx)
3187        });
3188        let Some((text, is_code_block)) = result else {
3189            return;
3190        };
3191
3192        cx.write_to_clipboard(ClipboardItem::new_string(text));
3193
3194        struct CopyToClipboardToast;
3195        workspace.show_toast(
3196            Toast::new(
3197                NotificationId::unique::<CopyToClipboardToast>(),
3198                format!(
3199                    "{} copied to clipboard.",
3200                    if is_code_block {
3201                        "Code block"
3202                    } else {
3203                        "Selection"
3204                    }
3205                ),
3206            )
3207            .autohide(),
3208            cx,
3209        );
3210    }
3211
3212    fn insert_dragged_files(
3213        workspace: &mut Workspace,
3214        action: &InsertDraggedFiles,
3215        cx: &mut ViewContext<Workspace>,
3216    ) {
3217        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3218            return;
3219        };
3220        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3221            return;
3222        };
3223
3224        let project = workspace.project().clone();
3225
3226        let paths = match action {
3227            InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
3228            InsertDraggedFiles::ExternalFiles(paths) => {
3229                let tasks = paths
3230                    .clone()
3231                    .into_iter()
3232                    .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
3233                    .collect::<Vec<_>>();
3234
3235                cx.spawn(move |_, cx| async move {
3236                    let mut paths = vec![];
3237                    let mut worktrees = vec![];
3238
3239                    let opened_paths = futures::future::join_all(tasks).await;
3240                    for (worktree, project_path) in opened_paths.into_iter().flatten() {
3241                        let Ok(worktree_root_name) =
3242                            worktree.read_with(&cx, |worktree, _| worktree.root_name().to_string())
3243                        else {
3244                            continue;
3245                        };
3246
3247                        let mut full_path = PathBuf::from(worktree_root_name.clone());
3248                        full_path.push(&project_path.path);
3249                        paths.push(full_path);
3250                        worktrees.push(worktree);
3251                    }
3252
3253                    (paths, worktrees)
3254                })
3255            }
3256        };
3257
3258        cx.spawn(|_, mut cx| async move {
3259            let (paths, dragged_file_worktrees) = paths.await;
3260            let cmd_name = file_command::FileSlashCommand.name();
3261
3262            context_editor_view
3263                .update(&mut cx, |context_editor, cx| {
3264                    let file_argument = paths
3265                        .into_iter()
3266                        .map(|path| path.to_string_lossy().to_string())
3267                        .collect::<Vec<_>>()
3268                        .join(" ");
3269
3270                    context_editor.editor.update(cx, |editor, cx| {
3271                        editor.insert("\n", cx);
3272                        editor.insert(&format!("/{} {}", cmd_name, file_argument), cx);
3273                    });
3274
3275                    context_editor.confirm_command(&ConfirmCommand, cx);
3276
3277                    context_editor
3278                        .dragged_file_worktrees
3279                        .extend(dragged_file_worktrees);
3280                })
3281                .log_err();
3282        })
3283        .detach();
3284    }
3285
3286    fn quote_selection(
3287        workspace: &mut Workspace,
3288        _: &QuoteSelection,
3289        cx: &mut ViewContext<Workspace>,
3290    ) {
3291        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3292            return;
3293        };
3294        let Some(editor) = workspace
3295            .active_item(cx)
3296            .and_then(|item| item.act_as::<Editor>(cx))
3297        else {
3298            return;
3299        };
3300
3301        let mut creases = vec![];
3302        editor.update(cx, |editor, cx| {
3303            let selections = editor.selections.all_adjusted(cx);
3304            let buffer = editor.buffer().read(cx).snapshot(cx);
3305            for selection in selections {
3306                let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3307                    ..editor::ToOffset::to_offset(&selection.end, &buffer);
3308                let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3309                if selected_text.is_empty() {
3310                    continue;
3311                }
3312                let start_language = buffer.language_at(range.start);
3313                let end_language = buffer.language_at(range.end);
3314                let language_name = if start_language == end_language {
3315                    start_language.map(|language| language.code_fence_block_name())
3316                } else {
3317                    None
3318                };
3319                let language_name = language_name.as_deref().unwrap_or("");
3320                let filename = buffer
3321                    .file_at(selection.start)
3322                    .map(|file| file.full_path(cx));
3323                let text = if language_name == "markdown" {
3324                    selected_text
3325                        .lines()
3326                        .map(|line| format!("> {}", line))
3327                        .collect::<Vec<_>>()
3328                        .join("\n")
3329                } else {
3330                    let start_symbols = buffer
3331                        .symbols_containing(selection.start, None)
3332                        .map(|(_, symbols)| symbols);
3333                    let end_symbols = buffer
3334                        .symbols_containing(selection.end, None)
3335                        .map(|(_, symbols)| symbols);
3336
3337                    let outline_text = if let Some((start_symbols, end_symbols)) =
3338                        start_symbols.zip(end_symbols)
3339                    {
3340                        Some(
3341                            start_symbols
3342                                .into_iter()
3343                                .zip(end_symbols)
3344                                .take_while(|(a, b)| a == b)
3345                                .map(|(a, _)| a.text)
3346                                .collect::<Vec<_>>()
3347                                .join(" > "),
3348                        )
3349                    } else {
3350                        None
3351                    };
3352
3353                    let line_comment_prefix = start_language
3354                        .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3355
3356                    let fence = codeblock_fence_for_path(
3357                        filename.as_deref(),
3358                        Some(selection.start.row..=selection.end.row),
3359                    );
3360
3361                    if let Some((line_comment_prefix, outline_text)) =
3362                        line_comment_prefix.zip(outline_text)
3363                    {
3364                        let breadcrumb =
3365                            format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
3366                        format!("{fence}{breadcrumb}{selected_text}\n```")
3367                    } else {
3368                        format!("{fence}{selected_text}\n```")
3369                    }
3370                };
3371                let crease_title = if let Some(path) = filename {
3372                    let start_line = selection.start.row + 1;
3373                    let end_line = selection.end.row + 1;
3374                    if start_line == end_line {
3375                        format!("{}, Line {}", path.display(), start_line)
3376                    } else {
3377                        format!("{}, Lines {} to {}", path.display(), start_line, end_line)
3378                    }
3379                } else {
3380                    "Quoted selection".to_string()
3381                };
3382                creases.push((text, crease_title));
3383            }
3384        });
3385        if creases.is_empty() {
3386            return;
3387        }
3388        // Activate the panel
3389        if !panel.focus_handle(cx).contains_focused(cx) {
3390            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3391        }
3392
3393        panel.update(cx, |_, cx| {
3394            // Wait to create a new context until the workspace is no longer
3395            // being updated.
3396            cx.defer(move |panel, cx| {
3397                if let Some(context) = panel
3398                    .active_context_editor(cx)
3399                    .or_else(|| panel.new_context(cx))
3400                {
3401                    context.update(cx, |context, cx| {
3402                        context.editor.update(cx, |editor, cx| {
3403                            editor.insert("\n", cx);
3404                            for (text, crease_title) in creases {
3405                                let point = editor.selections.newest::<Point>(cx).head();
3406                                let start_row = MultiBufferRow(point.row);
3407
3408                                editor.insert(&text, cx);
3409
3410                                let snapshot = editor.buffer().read(cx).snapshot(cx);
3411                                let anchor_before = snapshot.anchor_after(point);
3412                                let anchor_after = editor
3413                                    .selections
3414                                    .newest_anchor()
3415                                    .head()
3416                                    .bias_left(&snapshot);
3417
3418                                editor.insert("\n", cx);
3419
3420                                let fold_placeholder = quote_selection_fold_placeholder(
3421                                    crease_title,
3422                                    cx.view().downgrade(),
3423                                );
3424                                let crease = Crease::new(
3425                                    anchor_before..anchor_after,
3426                                    fold_placeholder,
3427                                    render_quote_selection_output_toggle,
3428                                    |_, _, _| Empty.into_any(),
3429                                );
3430                                editor.insert_creases(vec![crease], cx);
3431                                editor.fold_at(
3432                                    &FoldAt {
3433                                        buffer_row: start_row,
3434                                    },
3435                                    cx,
3436                                );
3437                            }
3438                        })
3439                    });
3440                };
3441            });
3442        });
3443    }
3444
3445    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3446        if self.editor.read(cx).selections.count() == 1 {
3447            let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
3448            cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3449                copied_text,
3450                metadata,
3451            ));
3452            cx.stop_propagation();
3453            return;
3454        }
3455
3456        cx.propagate();
3457    }
3458
3459    fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
3460        if self.editor.read(cx).selections.count() == 1 {
3461            let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
3462
3463            self.editor.update(cx, |editor, cx| {
3464                editor.transact(cx, |this, cx| {
3465                    this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3466                        s.select(selections);
3467                    });
3468                    this.insert("", cx);
3469                    cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3470                        copied_text,
3471                        metadata,
3472                    ));
3473                });
3474            });
3475
3476            cx.stop_propagation();
3477            return;
3478        }
3479
3480        cx.propagate();
3481    }
3482
3483    fn get_clipboard_contents(
3484        &mut self,
3485        cx: &mut ViewContext<Self>,
3486    ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
3487        let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
3488            let mut selection = editor.selections.newest::<Point>(cx);
3489            let snapshot = editor.buffer().read(cx).snapshot(cx);
3490
3491            let is_entire_line = selection.is_empty() || editor.selections.line_mode;
3492            if is_entire_line {
3493                selection.start = Point::new(selection.start.row, 0);
3494                selection.end =
3495                    cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
3496                selection.goal = SelectionGoal::None;
3497            }
3498
3499            let selection_start = snapshot.point_to_offset(selection.start);
3500
3501            (
3502                snapshot.clone(),
3503                selection.clone(),
3504                editor.display_map.update(cx, |display_map, cx| {
3505                    display_map
3506                        .snapshot(cx)
3507                        .crease_snapshot
3508                        .creases_in_range(
3509                            MultiBufferRow(selection.start.row)
3510                                ..MultiBufferRow(selection.end.row + 1),
3511                            &snapshot,
3512                        )
3513                        .filter_map(|crease| {
3514                            if let Some(metadata) = &crease.metadata {
3515                                let start = crease
3516                                    .range
3517                                    .start
3518                                    .to_offset(&snapshot)
3519                                    .saturating_sub(selection_start);
3520                                let end = crease
3521                                    .range
3522                                    .end
3523                                    .to_offset(&snapshot)
3524                                    .saturating_sub(selection_start);
3525
3526                                let range_relative_to_selection = start..end;
3527
3528                                if range_relative_to_selection.is_empty() {
3529                                    None
3530                                } else {
3531                                    Some(SelectedCreaseMetadata {
3532                                        range_relative_to_selection,
3533                                        crease: metadata.clone(),
3534                                    })
3535                                }
3536                            } else {
3537                                None
3538                            }
3539                        })
3540                        .collect::<Vec<_>>()
3541                }),
3542            )
3543        });
3544
3545        let selection = selection.map(|point| snapshot.point_to_offset(point));
3546        let context = self.context.read(cx);
3547
3548        let mut text = String::new();
3549        for message in context.messages(cx) {
3550            if message.offset_range.start >= selection.range().end {
3551                break;
3552            } else if message.offset_range.end >= selection.range().start {
3553                let range = cmp::max(message.offset_range.start, selection.range().start)
3554                    ..cmp::min(message.offset_range.end, selection.range().end);
3555                if !range.is_empty() {
3556                    for chunk in context.buffer().read(cx).text_for_range(range) {
3557                        text.push_str(chunk);
3558                    }
3559                    if message.offset_range.end < selection.range().end {
3560                        text.push('\n');
3561                    }
3562                }
3563            }
3564        }
3565
3566        (text, CopyMetadata { creases }, vec![selection])
3567    }
3568
3569    fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
3570        cx.stop_propagation();
3571
3572        let images = if let Some(item) = cx.read_from_clipboard() {
3573            item.into_entries()
3574                .filter_map(|entry| {
3575                    if let ClipboardEntry::Image(image) = entry {
3576                        Some(image)
3577                    } else {
3578                        None
3579                    }
3580                })
3581                .collect()
3582        } else {
3583            Vec::new()
3584        };
3585
3586        let metadata = if let Some(item) = cx.read_from_clipboard() {
3587            item.entries().first().and_then(|entry| {
3588                if let ClipboardEntry::String(text) = entry {
3589                    text.metadata_json::<CopyMetadata>()
3590                } else {
3591                    None
3592                }
3593            })
3594        } else {
3595            None
3596        };
3597
3598        if images.is_empty() {
3599            self.editor.update(cx, |editor, cx| {
3600                let paste_position = editor.selections.newest::<usize>(cx).head();
3601                editor.paste(action, cx);
3602
3603                if let Some(metadata) = metadata {
3604                    let buffer = editor.buffer().read(cx).snapshot(cx);
3605
3606                    let mut buffer_rows_to_fold = BTreeSet::new();
3607                    let weak_editor = cx.view().downgrade();
3608                    editor.insert_creases(
3609                        metadata.creases.into_iter().map(|metadata| {
3610                            let start = buffer.anchor_after(
3611                                paste_position + metadata.range_relative_to_selection.start,
3612                            );
3613                            let end = buffer.anchor_before(
3614                                paste_position + metadata.range_relative_to_selection.end,
3615                            );
3616
3617                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
3618                            buffer_rows_to_fold.insert(buffer_row);
3619                            Crease::new(
3620                                start..end,
3621                                FoldPlaceholder {
3622                                    constrain_width: false,
3623                                    render: render_fold_icon_button(
3624                                        weak_editor.clone(),
3625                                        metadata.crease.icon,
3626                                        metadata.crease.label.clone(),
3627                                    ),
3628                                    merge_adjacent: false,
3629                                },
3630                                render_slash_command_output_toggle,
3631                                |_, _, _| Empty.into_any(),
3632                            )
3633                            .with_metadata(metadata.crease.clone())
3634                        }),
3635                        cx,
3636                    );
3637                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
3638                        editor.fold_at(&FoldAt { buffer_row }, cx);
3639                    }
3640                }
3641            });
3642        } else {
3643            let mut image_positions = Vec::new();
3644            self.editor.update(cx, |editor, cx| {
3645                editor.transact(cx, |editor, cx| {
3646                    let edits = editor
3647                        .selections
3648                        .all::<usize>(cx)
3649                        .into_iter()
3650                        .map(|selection| (selection.start..selection.end, "\n"));
3651                    editor.edit(edits, cx);
3652
3653                    let snapshot = editor.buffer().read(cx).snapshot(cx);
3654                    for selection in editor.selections.all::<usize>(cx) {
3655                        image_positions.push(snapshot.anchor_before(selection.end));
3656                    }
3657                });
3658            });
3659
3660            self.context.update(cx, |context, cx| {
3661                for image in images {
3662                    let Some(render_image) = image.to_image_data(cx).log_err() else {
3663                        continue;
3664                    };
3665                    let image_id = image.id();
3666                    let image_task = LanguageModelImage::from_image(image, cx).shared();
3667
3668                    for image_position in image_positions.iter() {
3669                        context.insert_content(
3670                            Content::Image {
3671                                anchor: image_position.text_anchor,
3672                                image_id,
3673                                image: image_task.clone(),
3674                                render_image: render_image.clone(),
3675                            },
3676                            cx,
3677                        );
3678                    }
3679                }
3680            });
3681        }
3682    }
3683
3684    fn update_image_blocks(&mut self, cx: &mut ViewContext<Self>) {
3685        self.editor.update(cx, |editor, cx| {
3686            let buffer = editor.buffer().read(cx).snapshot(cx);
3687            let excerpt_id = *buffer.as_singleton().unwrap().0;
3688            let old_blocks = std::mem::take(&mut self.image_blocks);
3689            let new_blocks = self
3690                .context
3691                .read(cx)
3692                .contents(cx)
3693                .filter_map(|content| {
3694                    if let Content::Image {
3695                        anchor,
3696                        render_image,
3697                        ..
3698                    } = content
3699                    {
3700                        Some((anchor, render_image))
3701                    } else {
3702                        None
3703                    }
3704                })
3705                .filter_map(|(anchor, render_image)| {
3706                    const MAX_HEIGHT_IN_LINES: u32 = 8;
3707                    let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
3708                    let image = render_image.clone();
3709                    anchor.is_valid(&buffer).then(|| BlockProperties {
3710                        position: anchor,
3711                        height: MAX_HEIGHT_IN_LINES,
3712                        style: BlockStyle::Sticky,
3713                        render: Box::new(move |cx| {
3714                            let image_size = size_for_image(
3715                                &image,
3716                                size(
3717                                    cx.max_width - cx.gutter_dimensions.full_width(),
3718                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
3719                                ),
3720                            );
3721                            h_flex()
3722                                .pl(cx.gutter_dimensions.full_width())
3723                                .child(
3724                                    img(image.clone())
3725                                        .object_fit(gpui::ObjectFit::ScaleDown)
3726                                        .w(image_size.width)
3727                                        .h(image_size.height),
3728                                )
3729                                .into_any_element()
3730                        }),
3731
3732                        disposition: BlockDisposition::Above,
3733                        priority: 0,
3734                    })
3735                })
3736                .collect::<Vec<_>>();
3737
3738            editor.remove_blocks(old_blocks, None, cx);
3739            let ids = editor.insert_blocks(new_blocks, None, cx);
3740            self.image_blocks = HashSet::from_iter(ids);
3741        });
3742    }
3743
3744    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3745        self.context.update(cx, |context, cx| {
3746            let selections = self.editor.read(cx).selections.disjoint_anchors();
3747            for selection in selections.as_ref() {
3748                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3749                let range = selection
3750                    .map(|endpoint| endpoint.to_offset(&buffer))
3751                    .range();
3752                context.split_message(range, cx);
3753            }
3754        });
3755    }
3756
3757    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3758        self.context.update(cx, |context, cx| {
3759            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3760        });
3761    }
3762
3763    fn title(&self, cx: &AppContext) -> Cow<str> {
3764        self.context
3765            .read(cx)
3766            .summary()
3767            .map(|summary| summary.text.clone())
3768            .map(Cow::Owned)
3769            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3770    }
3771
3772    fn render_workflow_step_header(
3773        &self,
3774        range: Range<text::Anchor>,
3775        max_width: Pixels,
3776        gutter_width: Pixels,
3777        id: BlockId,
3778        cx: &mut ViewContext<Self>,
3779    ) -> Option<AnyElement> {
3780        let step_state = self.workflow_steps.get(&range)?;
3781        let status = step_state.status(cx);
3782        let this = cx.view().downgrade();
3783
3784        let theme = cx.theme().status();
3785        let is_confirmed = status.is_confirmed();
3786        let border_color = if is_confirmed {
3787            theme.ignored_border
3788        } else {
3789            theme.info_border
3790        };
3791
3792        let editor = self.editor.read(cx);
3793        let focus_handle = editor.focus_handle(cx);
3794        let snapshot = editor
3795            .buffer()
3796            .read(cx)
3797            .as_singleton()?
3798            .read(cx)
3799            .text_snapshot();
3800        let start_offset = range.start.to_offset(&snapshot);
3801        let parent_message = self
3802            .context
3803            .read(cx)
3804            .messages_for_offsets([start_offset], cx);
3805        debug_assert_eq!(parent_message.len(), 1);
3806        let parent_message = parent_message.first()?;
3807
3808        let step_index = self
3809            .workflow_steps
3810            .keys()
3811            .filter(|workflow_step_range| {
3812                workflow_step_range
3813                    .start
3814                    .cmp(&parent_message.anchor_range.start, &snapshot)
3815                    .is_ge()
3816                    && workflow_step_range.end.cmp(&range.end, &snapshot).is_le()
3817            })
3818            .count();
3819
3820        let step_label = Label::new(format!("Step {step_index}")).size(LabelSize::Small);
3821
3822        let step_label = if is_confirmed {
3823            h_flex()
3824                .items_center()
3825                .gap_2()
3826                .child(step_label.strikethrough(true).color(Color::Muted))
3827                .child(
3828                    Icon::new(IconName::Check)
3829                        .size(IconSize::Small)
3830                        .color(Color::Created),
3831                )
3832        } else {
3833            div().child(step_label)
3834        };
3835
3836        Some(
3837            v_flex()
3838                .w(max_width)
3839                .pl(gutter_width)
3840                .child(
3841                    h_flex()
3842                        .w_full()
3843                        .h_8()
3844                        .border_b_1()
3845                        .border_color(border_color)
3846                        .items_center()
3847                        .justify_between()
3848                        .gap_2()
3849                        .child(h_flex().justify_start().gap_2().child(step_label))
3850                        .child(h_flex().w_full().justify_end().child(
3851                            Self::render_workflow_step_status(
3852                                status,
3853                                range.clone(),
3854                                focus_handle.clone(),
3855                                this.clone(),
3856                                id,
3857                            ),
3858                        )),
3859                )
3860                // todo!("do we wanna keep this?")
3861                // .children(edit_paths.iter().map(|path| {
3862                //     h_flex()
3863                //         .gap_1()
3864                //         .child(Icon::new(IconName::File))
3865                //         .child(Label::new(path.clone()))
3866                // }))
3867                .into_any(),
3868        )
3869    }
3870
3871    fn render_workflow_step_footer(
3872        &self,
3873        step_range: Range<text::Anchor>,
3874        max_width: Pixels,
3875        gutter_width: Pixels,
3876        cx: &mut ViewContext<Self>,
3877    ) -> Option<AnyElement> {
3878        let step = self.workflow_steps.get(&step_range)?;
3879        let current_status = step.status(cx);
3880        let theme = cx.theme().status();
3881        let border_color = if current_status.is_confirmed() {
3882            theme.ignored_border
3883        } else {
3884            theme.info_border
3885        };
3886        Some(
3887            v_flex()
3888                .w(max_width)
3889                .pt_1()
3890                .pl(gutter_width)
3891                .child(h_flex().h(px(1.)).bg(border_color))
3892                .into_any(),
3893        )
3894    }
3895
3896    fn render_workflow_step_status(
3897        status: WorkflowStepStatus,
3898        step_range: Range<language::Anchor>,
3899        focus_handle: FocusHandle,
3900        editor: WeakView<ContextEditor>,
3901        id: BlockId,
3902    ) -> AnyElement {
3903        let id = EntityId::from(id).as_u64();
3904        fn display_keybind_in_tooltip(
3905            step_range: &Range<language::Anchor>,
3906            editor: &WeakView<ContextEditor>,
3907            cx: &mut WindowContext<'_>,
3908        ) -> bool {
3909            editor
3910                .update(cx, |this, _| {
3911                    this.active_workflow_step
3912                        .as_ref()
3913                        .map(|step| &step.range == step_range)
3914                })
3915                .ok()
3916                .flatten()
3917                .unwrap_or_default()
3918        }
3919
3920        match status {
3921            WorkflowStepStatus::Error(error) => {
3922                let error = error.to_string();
3923                h_flex()
3924                    .gap_2()
3925                    .child(
3926                        div()
3927                            .id("step-resolution-failure")
3928                            .child(
3929                                Label::new("Step Resolution Failed")
3930                                    .size(LabelSize::Small)
3931                                    .color(Color::Error),
3932                            )
3933                            .tooltip(move |cx| Tooltip::text(error.clone(), cx)),
3934                    )
3935                    .child(
3936                        Button::new(("transform", id), "Retry")
3937                            .icon(IconName::Update)
3938                            .icon_position(IconPosition::Start)
3939                            .icon_size(IconSize::Small)
3940                            .label_size(LabelSize::Small)
3941                            .on_click({
3942                                let editor = editor.clone();
3943                                let step_range = step_range.clone();
3944                                move |_, cx| {
3945                                    editor
3946                                        .update(cx, |this, cx| {
3947                                            this.resolve_workflow_step(step_range.clone(), cx)
3948                                        })
3949                                        .ok();
3950                                }
3951                            }),
3952                    )
3953                    .into_any()
3954            }
3955            WorkflowStepStatus::Idle | WorkflowStepStatus::Resolving { .. } => {
3956                Button::new(("transform", id), "Transform")
3957                    .icon(IconName::SparkleAlt)
3958                    .icon_position(IconPosition::Start)
3959                    .icon_size(IconSize::Small)
3960                    .label_size(LabelSize::Small)
3961                    .style(ButtonStyle::Tinted(TintColor::Accent))
3962                    .tooltip({
3963                        let step_range = step_range.clone();
3964                        let editor = editor.clone();
3965                        move |cx| {
3966                            cx.new_view(|cx| {
3967                                let tooltip = Tooltip::new("Transform");
3968                                if display_keybind_in_tooltip(&step_range, &editor, cx) {
3969                                    tooltip.key_binding(KeyBinding::for_action_in(
3970                                        &Assist,
3971                                        &focus_handle,
3972                                        cx,
3973                                    ))
3974                                } else {
3975                                    tooltip
3976                                }
3977                            })
3978                            .into()
3979                        }
3980                    })
3981                    .on_click({
3982                        let editor = editor.clone();
3983                        let step_range = step_range.clone();
3984                        let is_idle = matches!(status, WorkflowStepStatus::Idle);
3985                        move |_, cx| {
3986                            if is_idle {
3987                                editor
3988                                    .update(cx, |this, cx| {
3989                                        this.apply_workflow_step(step_range.clone(), cx)
3990                                    })
3991                                    .ok();
3992                            }
3993                        }
3994                    })
3995                    .map(|this| {
3996                        if let WorkflowStepStatus::Resolving = &status {
3997                            this.with_animation(
3998                                ("resolving-suggestion-animation", id),
3999                                Animation::new(Duration::from_secs(2))
4000                                    .repeat()
4001                                    .with_easing(pulsating_between(0.4, 0.8)),
4002                                |label, delta| label.alpha(delta),
4003                            )
4004                            .into_any_element()
4005                        } else {
4006                            this.into_any_element()
4007                        }
4008                    })
4009            }
4010            WorkflowStepStatus::Pending => h_flex()
4011                .items_center()
4012                .gap_2()
4013                .child(
4014                    Label::new("Applying...")
4015                        .size(LabelSize::Small)
4016                        .with_animation(
4017                            ("applying-step-transformation-label", id),
4018                            Animation::new(Duration::from_secs(2))
4019                                .repeat()
4020                                .with_easing(pulsating_between(0.4, 0.8)),
4021                            |label, delta| label.alpha(delta),
4022                        ),
4023                )
4024                .child(
4025                    IconButton::new(("stop-transformation", id), IconName::Stop)
4026                        .icon_size(IconSize::Small)
4027                        .icon_color(Color::Error)
4028                        .style(ButtonStyle::Subtle)
4029                        .tooltip({
4030                            let step_range = step_range.clone();
4031                            let editor = editor.clone();
4032                            move |cx| {
4033                                cx.new_view(|cx| {
4034                                    let tooltip = Tooltip::new("Stop Transformation");
4035                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4036                                        tooltip.key_binding(KeyBinding::for_action_in(
4037                                            &editor::actions::Cancel,
4038                                            &focus_handle,
4039                                            cx,
4040                                        ))
4041                                    } else {
4042                                        tooltip
4043                                    }
4044                                })
4045                                .into()
4046                            }
4047                        })
4048                        .on_click({
4049                            let editor = editor.clone();
4050                            let step_range = step_range.clone();
4051                            move |_, cx| {
4052                                editor
4053                                    .update(cx, |this, cx| {
4054                                        this.stop_workflow_step(step_range.clone(), cx)
4055                                    })
4056                                    .ok();
4057                            }
4058                        }),
4059                )
4060                .into_any_element(),
4061            WorkflowStepStatus::Done => h_flex()
4062                .gap_1()
4063                .child(
4064                    IconButton::new(("stop-transformation", id), IconName::Close)
4065                        .icon_size(IconSize::Small)
4066                        .style(ButtonStyle::Tinted(TintColor::Negative))
4067                        .tooltip({
4068                            let focus_handle = focus_handle.clone();
4069                            let editor = editor.clone();
4070                            let step_range = step_range.clone();
4071                            move |cx| {
4072                                cx.new_view(|cx| {
4073                                    let tooltip = Tooltip::new("Reject Transformation");
4074                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4075                                        tooltip.key_binding(KeyBinding::for_action_in(
4076                                            &editor::actions::Cancel,
4077                                            &focus_handle,
4078                                            cx,
4079                                        ))
4080                                    } else {
4081                                        tooltip
4082                                    }
4083                                })
4084                                .into()
4085                            }
4086                        })
4087                        .on_click({
4088                            let editor = editor.clone();
4089                            let step_range = step_range.clone();
4090                            move |_, cx| {
4091                                editor
4092                                    .update(cx, |this, cx| {
4093                                        this.reject_workflow_step(step_range.clone(), cx);
4094                                    })
4095                                    .ok();
4096                            }
4097                        }),
4098                )
4099                .child(
4100                    Button::new(("confirm-workflow-step", id), "Accept")
4101                        .icon(IconName::Check)
4102                        .icon_position(IconPosition::Start)
4103                        .icon_size(IconSize::Small)
4104                        .label_size(LabelSize::Small)
4105                        .style(ButtonStyle::Tinted(TintColor::Positive))
4106                        .tooltip({
4107                            let editor = editor.clone();
4108                            let step_range = step_range.clone();
4109                            move |cx| {
4110                                cx.new_view(|cx| {
4111                                    let tooltip = Tooltip::new("Accept Transformation");
4112                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
4113                                        tooltip.key_binding(KeyBinding::for_action_in(
4114                                            &Assist,
4115                                            &focus_handle,
4116                                            cx,
4117                                        ))
4118                                    } else {
4119                                        tooltip
4120                                    }
4121                                })
4122                                .into()
4123                            }
4124                        })
4125                        .on_click({
4126                            let editor = editor.clone();
4127                            let step_range = step_range.clone();
4128                            move |_, cx| {
4129                                editor
4130                                    .update(cx, |this, cx| {
4131                                        this.confirm_workflow_step(step_range.clone(), cx);
4132                                    })
4133                                    .ok();
4134                            }
4135                        }),
4136                )
4137                .into_any_element(),
4138            WorkflowStepStatus::Confirmed => h_flex()
4139                .child(
4140                    Button::new(("revert-workflow-step", id), "Undo")
4141                        .style(ButtonStyle::Filled)
4142                        .icon(Some(IconName::Undo))
4143                        .icon_position(IconPosition::Start)
4144                        .icon_size(IconSize::Small)
4145                        .label_size(LabelSize::Small)
4146                        .on_click({
4147                            let editor = editor.clone();
4148                            let step_range = step_range.clone();
4149                            move |_, cx| {
4150                                editor
4151                                    .update(cx, |this, cx| {
4152                                        this.undo_workflow_step(step_range.clone(), cx);
4153                                    })
4154                                    .ok();
4155                            }
4156                        }),
4157                )
4158                .into_any_element(),
4159        }
4160    }
4161
4162    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
4163        use feature_flags::FeatureFlagAppExt;
4164        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
4165            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
4166        });
4167
4168        if nudge.map_or(false, |value| value) {
4169            Some(
4170                h_flex()
4171                    .p_3()
4172                    .border_b_1()
4173                    .border_color(cx.theme().colors().border_variant)
4174                    .bg(cx.theme().colors().editor_background)
4175                    .justify_between()
4176                    .child(
4177                        h_flex()
4178                            .gap_3()
4179                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
4180                            .child(Label::new("Zed AI is here! Get started by signing in →")),
4181                    )
4182                    .child(
4183                        Button::new("sign-in", "Sign in")
4184                            .size(ButtonSize::Compact)
4185                            .style(ButtonStyle::Filled)
4186                            .on_click(cx.listener(|this, _event, cx| {
4187                                let client = this
4188                                    .workspace
4189                                    .update(cx, |workspace, _| workspace.client().clone())
4190                                    .log_err();
4191
4192                                if let Some(client) = client {
4193                                    cx.spawn(|this, mut cx| async move {
4194                                        client.authenticate_and_connect(true, &mut cx).await?;
4195                                        this.update(&mut cx, |_, cx| cx.notify())
4196                                    })
4197                                    .detach_and_log_err(cx)
4198                                }
4199                            })),
4200                    )
4201                    .into_any_element(),
4202            )
4203        } else if let Some(configuration_error) = configuration_error(cx) {
4204            let label = match configuration_error {
4205                ConfigurationError::NoProvider => "No LLM provider selected.",
4206                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
4207            };
4208            Some(
4209                h_flex()
4210                    .px_3()
4211                    .py_2()
4212                    .border_b_1()
4213                    .border_color(cx.theme().colors().border_variant)
4214                    .bg(cx.theme().colors().editor_background)
4215                    .justify_between()
4216                    .child(
4217                        h_flex()
4218                            .gap_3()
4219                            .child(
4220                                Icon::new(IconName::Warning)
4221                                    .size(IconSize::Small)
4222                                    .color(Color::Warning),
4223                            )
4224                            .child(Label::new(label)),
4225                    )
4226                    .child(
4227                        Button::new("open-configuration", "Configure Providers")
4228                            .size(ButtonSize::Compact)
4229                            .icon(Some(IconName::SlidersVertical))
4230                            .icon_size(IconSize::Small)
4231                            .icon_position(IconPosition::Start)
4232                            .style(ButtonStyle::Filled)
4233                            .on_click({
4234                                let focus_handle = self.focus_handle(cx).clone();
4235                                move |_event, cx| {
4236                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
4237                                }
4238                            }),
4239                    )
4240                    .into_any_element(),
4241            )
4242        } else {
4243            None
4244        }
4245    }
4246
4247    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4248        let focus_handle = self.focus_handle(cx).clone();
4249        let button_text = match self.active_workflow_step() {
4250            Some((_, step)) => match step.status(cx) {
4251                WorkflowStepStatus::Error(_) => "Retry Step Resolution",
4252                WorkflowStepStatus::Resolving => "Transform",
4253                WorkflowStepStatus::Idle => "Transform",
4254                WorkflowStepStatus::Pending => "Applying...",
4255                WorkflowStepStatus::Done => "Accept",
4256                WorkflowStepStatus::Confirmed => "Send",
4257            },
4258            None => "Send",
4259        };
4260
4261        let (style, tooltip) = match token_state(&self.context, cx) {
4262            Some(TokenState::NoTokensLeft { .. }) => (
4263                ButtonStyle::Tinted(TintColor::Negative),
4264                Some(Tooltip::text("Token limit reached", cx)),
4265            ),
4266            Some(TokenState::HasMoreTokens {
4267                over_warn_threshold,
4268                ..
4269            }) => {
4270                let (style, tooltip) = if over_warn_threshold {
4271                    (
4272                        ButtonStyle::Tinted(TintColor::Warning),
4273                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
4274                    )
4275                } else {
4276                    (ButtonStyle::Filled, None)
4277                };
4278                (style, tooltip)
4279            }
4280            None => (ButtonStyle::Filled, None),
4281        };
4282
4283        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4284
4285        let has_configuration_error = configuration_error(cx).is_some();
4286        let needs_to_accept_terms = self.show_accept_terms
4287            && provider
4288                .as_ref()
4289                .map_or(false, |provider| provider.must_accept_terms(cx));
4290        let disabled = has_configuration_error || needs_to_accept_terms;
4291
4292        ButtonLike::new("send_button")
4293            .disabled(disabled)
4294            .style(style)
4295            .when_some(tooltip, |button, tooltip| {
4296                button.tooltip(move |_| tooltip.clone())
4297            })
4298            .layer(ElevationIndex::ModalSurface)
4299            .child(Label::new(button_text))
4300            .children(
4301                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
4302                    .map(|binding| binding.into_any_element()),
4303            )
4304            .on_click(move |_event, cx| {
4305                focus_handle.dispatch_action(&Assist, cx);
4306            })
4307    }
4308}
4309
4310/// Returns the contents of the *outermost* fenced code block that contains the given offset.
4311fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
4312    const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
4313    const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
4314
4315    let layer = snapshot.syntax_layers().next()?;
4316
4317    let root_node = layer.node();
4318    let mut cursor = root_node.walk();
4319
4320    // Go to the first child for the given offset
4321    while cursor.goto_first_child_for_byte(offset).is_some() {
4322        // If we're at the end of the node, go to the next one.
4323        // Example: if you have a fenced-code-block, and you're on the start of the line
4324        // right after the closing ```, you want to skip the fenced-code-block and
4325        // go to the next sibling.
4326        if cursor.node().end_byte() == offset {
4327            cursor.goto_next_sibling();
4328        }
4329
4330        if cursor.node().start_byte() > offset {
4331            break;
4332        }
4333
4334        // We found the fenced code block.
4335        if cursor.node().kind() == CODE_BLOCK_NODE {
4336            // Now we need to find the child node that contains the code.
4337            cursor.goto_first_child();
4338            loop {
4339                if cursor.node().kind() == CODE_BLOCK_CONTENT {
4340                    return Some(cursor.node().byte_range());
4341                }
4342                if !cursor.goto_next_sibling() {
4343                    break;
4344                }
4345            }
4346        }
4347    }
4348
4349    None
4350}
4351
4352fn render_fold_icon_button(
4353    editor: WeakView<Editor>,
4354    icon: IconName,
4355    label: SharedString,
4356) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
4357    Arc::new(move |fold_id, fold_range, _cx| {
4358        let editor = editor.clone();
4359        ButtonLike::new(fold_id)
4360            .style(ButtonStyle::Filled)
4361            .layer(ElevationIndex::ElevatedSurface)
4362            .child(Icon::new(icon))
4363            .child(Label::new(label.clone()).single_line())
4364            .on_click(move |_, cx| {
4365                editor
4366                    .update(cx, |editor, cx| {
4367                        let buffer_start = fold_range
4368                            .start
4369                            .to_point(&editor.buffer().read(cx).read(cx));
4370                        let buffer_row = MultiBufferRow(buffer_start.row);
4371                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4372                    })
4373                    .ok();
4374            })
4375            .into_any_element()
4376    })
4377}
4378
4379#[derive(Debug, Clone, Serialize, Deserialize)]
4380struct CopyMetadata {
4381    creases: Vec<SelectedCreaseMetadata>,
4382}
4383
4384#[derive(Debug, Clone, Serialize, Deserialize)]
4385struct SelectedCreaseMetadata {
4386    range_relative_to_selection: Range<usize>,
4387    crease: CreaseMetadata,
4388}
4389
4390impl EventEmitter<EditorEvent> for ContextEditor {}
4391impl EventEmitter<SearchEvent> for ContextEditor {}
4392
4393impl Render for ContextEditor {
4394    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4395        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4396        let accept_terms = if self.show_accept_terms {
4397            provider
4398                .as_ref()
4399                .and_then(|provider| provider.render_accept_terms(cx))
4400        } else {
4401            None
4402        };
4403        let focus_handle = self
4404            .workspace
4405            .update(cx, |workspace, cx| {
4406                Some(workspace.active_item_as::<Editor>(cx)?.focus_handle(cx))
4407            })
4408            .ok()
4409            .flatten();
4410        v_flex()
4411            .key_context("ContextEditor")
4412            .capture_action(cx.listener(ContextEditor::cancel))
4413            .capture_action(cx.listener(ContextEditor::save))
4414            .capture_action(cx.listener(ContextEditor::copy))
4415            .capture_action(cx.listener(ContextEditor::cut))
4416            .capture_action(cx.listener(ContextEditor::paste))
4417            .capture_action(cx.listener(ContextEditor::cycle_message_role))
4418            .capture_action(cx.listener(ContextEditor::confirm_command))
4419            .on_action(cx.listener(ContextEditor::assist))
4420            .on_action(cx.listener(ContextEditor::split))
4421            .size_full()
4422            .children(self.render_notice(cx))
4423            .child(
4424                div()
4425                    .flex_grow()
4426                    .bg(cx.theme().colors().editor_background)
4427                    .child(self.editor.clone()),
4428            )
4429            .when_some(accept_terms, |this, element| {
4430                this.child(
4431                    div()
4432                        .absolute()
4433                        .right_3()
4434                        .bottom_12()
4435                        .max_w_96()
4436                        .py_2()
4437                        .px_3()
4438                        .elevation_2(cx)
4439                        .bg(cx.theme().colors().surface_background)
4440                        .occlude()
4441                        .child(element),
4442                )
4443            })
4444            .when_some(self.error_message.clone(), |this, error_message| {
4445                this.child(
4446                    div()
4447                        .absolute()
4448                        .right_3()
4449                        .bottom_12()
4450                        .max_w_96()
4451                        .py_2()
4452                        .px_3()
4453                        .elevation_2(cx)
4454                        .occlude()
4455                        .child(
4456                            v_flex()
4457                                .gap_0p5()
4458                                .child(
4459                                    h_flex()
4460                                        .gap_1p5()
4461                                        .items_center()
4462                                        .child(Icon::new(IconName::XCircle).color(Color::Error))
4463                                        .child(
4464                                            Label::new("Error interacting with language model")
4465                                                .weight(FontWeight::MEDIUM),
4466                                        ),
4467                                )
4468                                .child(
4469                                    div()
4470                                        .id("error-message")
4471                                        .max_h_24()
4472                                        .overflow_y_scroll()
4473                                        .child(Label::new(error_message)),
4474                                )
4475                                .child(h_flex().justify_end().mt_1().child(
4476                                    Button::new("dismiss", "Dismiss").on_click(cx.listener(
4477                                        |this, _, cx| {
4478                                            this.error_message = None;
4479                                            cx.notify();
4480                                        },
4481                                    )),
4482                                )),
4483                        ),
4484                )
4485            })
4486            .child(
4487                h_flex().w_full().relative().child(
4488                    h_flex()
4489                        .p_2()
4490                        .w_full()
4491                        .border_t_1()
4492                        .border_color(cx.theme().colors().border_variant)
4493                        .bg(cx.theme().colors().editor_background)
4494                        .child(
4495                            h_flex()
4496                                .gap_2()
4497                                .child(render_inject_context_menu(cx.view().downgrade(), cx))
4498                                .child(
4499                                    IconButton::new("quote-button", IconName::Quote)
4500                                        .icon_size(IconSize::Small)
4501                                        .on_click(|_, cx| {
4502                                            cx.dispatch_action(QuoteSelection.boxed_clone());
4503                                        })
4504                                        .tooltip(move |cx| {
4505                                            cx.new_view(|cx| {
4506                                                Tooltip::new("Insert Selection").key_binding(
4507                                                    focus_handle.as_ref().and_then(|handle| {
4508                                                        KeyBinding::for_action_in(
4509                                                            &QuoteSelection,
4510                                                            &handle,
4511                                                            cx,
4512                                                        )
4513                                                    }),
4514                                                )
4515                                            })
4516                                            .into()
4517                                        }),
4518                                ),
4519                        )
4520                        .child(
4521                            h_flex()
4522                                .w_full()
4523                                .justify_end()
4524                                .child(div().child(self.render_send_button(cx))),
4525                        ),
4526                ),
4527            )
4528    }
4529}
4530
4531impl FocusableView for ContextEditor {
4532    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4533        self.editor.focus_handle(cx)
4534    }
4535}
4536
4537impl Item for ContextEditor {
4538    type Event = editor::EditorEvent;
4539
4540    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
4541        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
4542    }
4543
4544    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
4545        match event {
4546            EditorEvent::Edited { .. } => {
4547                f(item::ItemEvent::Edit);
4548            }
4549            EditorEvent::TitleChanged => {
4550                f(item::ItemEvent::UpdateTab);
4551            }
4552            _ => {}
4553        }
4554    }
4555
4556    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
4557        Some(self.title(cx).to_string().into())
4558    }
4559
4560    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
4561        Some(Box::new(handle.clone()))
4562    }
4563
4564    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
4565        self.editor.update(cx, |editor, cx| {
4566            Item::set_nav_history(editor, nav_history, cx)
4567        })
4568    }
4569
4570    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
4571        self.editor
4572            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
4573    }
4574
4575    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
4576        self.editor.update(cx, Item::deactivated)
4577    }
4578}
4579
4580impl SearchableItem for ContextEditor {
4581    type Match = <Editor as SearchableItem>::Match;
4582
4583    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
4584        self.editor.update(cx, |editor, cx| {
4585            editor.clear_matches(cx);
4586        });
4587    }
4588
4589    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4590        self.editor
4591            .update(cx, |editor, cx| editor.update_matches(matches, cx));
4592    }
4593
4594    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
4595        self.editor
4596            .update(cx, |editor, cx| editor.query_suggestion(cx))
4597    }
4598
4599    fn activate_match(
4600        &mut self,
4601        index: usize,
4602        matches: &[Self::Match],
4603        cx: &mut ViewContext<Self>,
4604    ) {
4605        self.editor.update(cx, |editor, cx| {
4606            editor.activate_match(index, matches, cx);
4607        });
4608    }
4609
4610    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4611        self.editor
4612            .update(cx, |editor, cx| editor.select_matches(matches, cx));
4613    }
4614
4615    fn replace(
4616        &mut self,
4617        identifier: &Self::Match,
4618        query: &project::search::SearchQuery,
4619        cx: &mut ViewContext<Self>,
4620    ) {
4621        self.editor
4622            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
4623    }
4624
4625    fn find_matches(
4626        &mut self,
4627        query: Arc<project::search::SearchQuery>,
4628        cx: &mut ViewContext<Self>,
4629    ) -> Task<Vec<Self::Match>> {
4630        self.editor
4631            .update(cx, |editor, cx| editor.find_matches(query, cx))
4632    }
4633
4634    fn active_match_index(
4635        &mut self,
4636        matches: &[Self::Match],
4637        cx: &mut ViewContext<Self>,
4638    ) -> Option<usize> {
4639        self.editor
4640            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
4641    }
4642}
4643
4644impl FollowableItem for ContextEditor {
4645    fn remote_id(&self) -> Option<workspace::ViewId> {
4646        self.remote_id
4647    }
4648
4649    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
4650        let context = self.context.read(cx);
4651        Some(proto::view::Variant::ContextEditor(
4652            proto::view::ContextEditor {
4653                context_id: context.id().to_proto(),
4654                editor: if let Some(proto::view::Variant::Editor(proto)) =
4655                    self.editor.read(cx).to_state_proto(cx)
4656                {
4657                    Some(proto)
4658                } else {
4659                    None
4660                },
4661            },
4662        ))
4663    }
4664
4665    fn from_state_proto(
4666        workspace: View<Workspace>,
4667        id: workspace::ViewId,
4668        state: &mut Option<proto::view::Variant>,
4669        cx: &mut WindowContext,
4670    ) -> Option<Task<Result<View<Self>>>> {
4671        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
4672            return None;
4673        };
4674        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
4675            unreachable!()
4676        };
4677
4678        let context_id = ContextId::from_proto(state.context_id);
4679        let editor_state = state.editor?;
4680
4681        let (project, panel) = workspace.update(cx, |workspace, cx| {
4682            Some((
4683                workspace.project().clone(),
4684                workspace.panel::<AssistantPanel>(cx)?,
4685            ))
4686        })?;
4687
4688        let context_editor =
4689            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
4690
4691        Some(cx.spawn(|mut cx| async move {
4692            let context_editor = context_editor.await?;
4693            context_editor
4694                .update(&mut cx, |context_editor, cx| {
4695                    context_editor.remote_id = Some(id);
4696                    context_editor.editor.update(cx, |editor, cx| {
4697                        editor.apply_update_proto(
4698                            &project,
4699                            proto::update_view::Variant::Editor(proto::update_view::Editor {
4700                                selections: editor_state.selections,
4701                                pending_selection: editor_state.pending_selection,
4702                                scroll_top_anchor: editor_state.scroll_top_anchor,
4703                                scroll_x: editor_state.scroll_y,
4704                                scroll_y: editor_state.scroll_y,
4705                                ..Default::default()
4706                            }),
4707                            cx,
4708                        )
4709                    })
4710                })?
4711                .await?;
4712            Ok(context_editor)
4713        }))
4714    }
4715
4716    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
4717        Editor::to_follow_event(event)
4718    }
4719
4720    fn add_event_to_update_proto(
4721        &self,
4722        event: &Self::Event,
4723        update: &mut Option<proto::update_view::Variant>,
4724        cx: &WindowContext,
4725    ) -> bool {
4726        self.editor
4727            .read(cx)
4728            .add_event_to_update_proto(event, update, cx)
4729    }
4730
4731    fn apply_update_proto(
4732        &mut self,
4733        project: &Model<Project>,
4734        message: proto::update_view::Variant,
4735        cx: &mut ViewContext<Self>,
4736    ) -> Task<Result<()>> {
4737        self.editor.update(cx, |editor, cx| {
4738            editor.apply_update_proto(project, message, cx)
4739        })
4740    }
4741
4742    fn is_project_item(&self, _cx: &WindowContext) -> bool {
4743        true
4744    }
4745
4746    fn set_leader_peer_id(
4747        &mut self,
4748        leader_peer_id: Option<proto::PeerId>,
4749        cx: &mut ViewContext<Self>,
4750    ) {
4751        self.editor.update(cx, |editor, cx| {
4752            editor.set_leader_peer_id(leader_peer_id, cx)
4753        })
4754    }
4755
4756    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
4757        if existing.context.read(cx).id() == self.context.read(cx).id() {
4758            Some(item::Dedup::KeepExisting)
4759        } else {
4760            None
4761        }
4762    }
4763}
4764
4765pub struct ContextEditorToolbarItem {
4766    fs: Arc<dyn Fs>,
4767    workspace: WeakView<Workspace>,
4768    active_context_editor: Option<WeakView<ContextEditor>>,
4769    model_summary_editor: View<Editor>,
4770    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4771}
4772
4773fn active_editor_focus_handle(
4774    workspace: &WeakView<Workspace>,
4775    cx: &WindowContext<'_>,
4776) -> Option<FocusHandle> {
4777    workspace.upgrade().and_then(|workspace| {
4778        Some(
4779            workspace
4780                .read(cx)
4781                .active_item_as::<Editor>(cx)?
4782                .focus_handle(cx),
4783        )
4784    })
4785}
4786
4787fn render_inject_context_menu(
4788    active_context_editor: WeakView<ContextEditor>,
4789    cx: &mut WindowContext<'_>,
4790) -> impl IntoElement {
4791    let commands = SlashCommandRegistry::global(cx);
4792
4793    slash_command_picker::SlashCommandSelector::new(
4794        commands.clone(),
4795        active_context_editor,
4796        IconButton::new("trigger", IconName::SlashSquare)
4797            .icon_size(IconSize::Small)
4798            .tooltip(|cx| {
4799                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
4800            }),
4801    )
4802}
4803
4804impl ContextEditorToolbarItem {
4805    pub fn new(
4806        workspace: &Workspace,
4807        model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4808        model_summary_editor: View<Editor>,
4809    ) -> Self {
4810        Self {
4811            fs: workspace.app_state().fs.clone(),
4812            workspace: workspace.weak_handle(),
4813            active_context_editor: None,
4814            model_summary_editor,
4815            model_selector_menu_handle,
4816        }
4817    }
4818
4819    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
4820        let context = &self
4821            .active_context_editor
4822            .as_ref()?
4823            .upgrade()?
4824            .read(cx)
4825            .context;
4826        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
4827            TokenState::NoTokensLeft {
4828                max_token_count,
4829                token_count,
4830            } => (Color::Error, token_count, max_token_count),
4831            TokenState::HasMoreTokens {
4832                max_token_count,
4833                token_count,
4834                over_warn_threshold,
4835            } => {
4836                let color = if over_warn_threshold {
4837                    Color::Warning
4838                } else {
4839                    Color::Muted
4840                };
4841                (color, token_count, max_token_count)
4842            }
4843        };
4844        Some(
4845            h_flex()
4846                .gap_0p5()
4847                .child(
4848                    Label::new(humanize_token_count(token_count))
4849                        .size(LabelSize::Small)
4850                        .color(token_count_color),
4851                )
4852                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
4853                .child(
4854                    Label::new(humanize_token_count(max_token_count))
4855                        .size(LabelSize::Small)
4856                        .color(Color::Muted),
4857                ),
4858        )
4859    }
4860}
4861
4862impl Render for ContextEditorToolbarItem {
4863    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4864        let left_side = h_flex()
4865            .pl_1()
4866            .gap_2()
4867            .flex_1()
4868            .min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
4869            .when(self.active_context_editor.is_some(), |left_side| {
4870                left_side.child(self.model_summary_editor.clone())
4871            });
4872        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
4873        let active_model = LanguageModelRegistry::read_global(cx).active_model();
4874        let weak_self = cx.view().downgrade();
4875        let right_side = h_flex()
4876            .gap_2()
4877            // TODO display this in a nicer way, once we have a design for it.
4878            // .children({
4879            //     let project = self
4880            //         .workspace
4881            //         .upgrade()
4882            //         .map(|workspace| workspace.read(cx).project().downgrade());
4883            //
4884            //     let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
4885            //         project.and_then(|project| db.remaining_summaries(&project, cx))
4886            //     });
4887
4888            //     scan_items_remaining
4889            //         .map(|remaining_items| format!("Files to scan: {}", remaining_items))
4890            // })
4891            .child(
4892                ModelSelector::new(
4893                    self.fs.clone(),
4894                    ButtonLike::new("active-model")
4895                        .style(ButtonStyle::Subtle)
4896                        .child(
4897                            h_flex()
4898                                .w_full()
4899                                .gap_0p5()
4900                                .child(
4901                                    div()
4902                                        .overflow_x_hidden()
4903                                        .flex_grow()
4904                                        .whitespace_nowrap()
4905                                        .child(match (active_provider, active_model) {
4906                                            (Some(provider), Some(model)) => h_flex()
4907                                                .gap_1()
4908                                                .child(
4909                                                    Icon::new(model.icon().unwrap_or_else(|| provider.icon()))
4910                                                        .color(Color::Muted)
4911                                                        .size(IconSize::XSmall),
4912                                                )
4913                                                .child(
4914                                                    Label::new(model.name().0)
4915                                                        .size(LabelSize::Small)
4916                                                        .color(Color::Muted),
4917                                                )
4918                                                .into_any_element(),
4919                                            _ => Label::new("No model selected")
4920                                                .size(LabelSize::Small)
4921                                                .color(Color::Muted)
4922                                                .into_any_element(),
4923                                        }),
4924                                )
4925                                .child(
4926                                    Icon::new(IconName::ChevronDown)
4927                                        .color(Color::Muted)
4928                                        .size(IconSize::XSmall),
4929                                ),
4930                        )
4931                        .tooltip(move |cx| {
4932                            Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
4933                        }),
4934                )
4935                .with_handle(self.model_selector_menu_handle.clone()),
4936            )
4937            .children(self.render_remaining_tokens(cx))
4938            .child(
4939                PopoverMenu::new("context-editor-popover")
4940                    .trigger(
4941                        IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
4942                            .icon_size(IconSize::Small)
4943                            .tooltip(|cx| Tooltip::text("Open Context Options", cx)),
4944                    )
4945                    .menu({
4946                        let weak_self = weak_self.clone();
4947                        move |cx| {
4948                            let weak_self = weak_self.clone();
4949                            Some(ContextMenu::build(cx, move |menu, cx| {
4950                                let context = weak_self
4951                                    .update(cx, |this, cx| {
4952                                        active_editor_focus_handle(&this.workspace, cx)
4953                                    })
4954                                    .ok()
4955                                    .flatten();
4956                                menu.when_some(context, |menu, context| menu.context(context))
4957                                    .entry("Regenerate Context Title", None, {
4958                                        let weak_self = weak_self.clone();
4959                                        move |cx| {
4960                                            weak_self
4961                                                .update(cx, |_, cx| {
4962                                                    cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
4963                                                })
4964                                                .ok();
4965                                        }
4966                                    })
4967                                    .custom_entry(
4968                                        |_| {
4969                                            h_flex()
4970                                                .w_full()
4971                                                .justify_between()
4972                                                .gap_2()
4973                                                .child(Label::new("Insert Context"))
4974                                                .child(Label::new("/ command").color(Color::Muted))
4975                                                .into_any()
4976                                        },
4977                                        {
4978                                            let weak_self = weak_self.clone();
4979                                            move |cx| {
4980                                                weak_self
4981                                                    .update(cx, |this, cx| {
4982                                                        if let Some(editor) =
4983                                                        &this.active_context_editor
4984                                                        {
4985                                                            editor
4986                                                                .update(cx, |this, cx| {
4987                                                                    this.slash_menu_handle
4988                                                                        .toggle(cx);
4989                                                                })
4990                                                                .ok();
4991                                                        }
4992                                                    })
4993                                                    .ok();
4994                                            }
4995                                        },
4996                                    )
4997                                    .action("Insert Selection", QuoteSelection.boxed_clone())
4998                            }))
4999                        }
5000                    }),
5001            );
5002
5003        h_flex()
5004            .size_full()
5005            .gap_2()
5006            .justify_between()
5007            .child(left_side)
5008            .child(right_side)
5009    }
5010}
5011
5012impl ToolbarItemView for ContextEditorToolbarItem {
5013    fn set_active_pane_item(
5014        &mut self,
5015        active_pane_item: Option<&dyn ItemHandle>,
5016        cx: &mut ViewContext<Self>,
5017    ) -> ToolbarItemLocation {
5018        self.active_context_editor = active_pane_item
5019            .and_then(|item| item.act_as::<ContextEditor>(cx))
5020            .map(|editor| editor.downgrade());
5021        cx.notify();
5022        if self.active_context_editor.is_none() {
5023            ToolbarItemLocation::Hidden
5024        } else {
5025            ToolbarItemLocation::PrimaryRight
5026        }
5027    }
5028
5029    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
5030        cx.notify();
5031    }
5032}
5033
5034impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
5035
5036enum ContextEditorToolbarItemEvent {
5037    RegenerateSummary,
5038}
5039impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
5040
5041pub struct ContextHistory {
5042    picker: View<Picker<SavedContextPickerDelegate>>,
5043    _subscriptions: Vec<Subscription>,
5044    assistant_panel: WeakView<AssistantPanel>,
5045}
5046
5047impl ContextHistory {
5048    fn new(
5049        project: Model<Project>,
5050        context_store: Model<ContextStore>,
5051        assistant_panel: WeakView<AssistantPanel>,
5052        cx: &mut ViewContext<Self>,
5053    ) -> Self {
5054        let picker = cx.new_view(|cx| {
5055            Picker::uniform_list(
5056                SavedContextPickerDelegate::new(project, context_store.clone()),
5057                cx,
5058            )
5059            .modal(false)
5060            .max_height(None)
5061        });
5062
5063        let _subscriptions = vec![
5064            cx.observe(&context_store, |this, _, cx| {
5065                this.picker.update(cx, |picker, cx| picker.refresh(cx));
5066            }),
5067            cx.subscribe(&picker, Self::handle_picker_event),
5068        ];
5069
5070        Self {
5071            picker,
5072            _subscriptions,
5073            assistant_panel,
5074        }
5075    }
5076
5077    fn handle_picker_event(
5078        &mut self,
5079        _: View<Picker<SavedContextPickerDelegate>>,
5080        event: &SavedContextPickerEvent,
5081        cx: &mut ViewContext<Self>,
5082    ) {
5083        let SavedContextPickerEvent::Confirmed(context) = event;
5084        self.assistant_panel
5085            .update(cx, |assistant_panel, cx| match context {
5086                ContextMetadata::Remote(metadata) => {
5087                    assistant_panel
5088                        .open_remote_context(metadata.id.clone(), cx)
5089                        .detach_and_log_err(cx);
5090                }
5091                ContextMetadata::Saved(metadata) => {
5092                    assistant_panel
5093                        .open_saved_context(metadata.path.clone(), cx)
5094                        .detach_and_log_err(cx);
5095                }
5096            })
5097            .ok();
5098    }
5099}
5100
5101#[derive(Debug, PartialEq, Eq, Clone, Copy)]
5102pub enum WorkflowAssistStatus {
5103    Pending,
5104    Confirmed,
5105    Done,
5106    Idle,
5107}
5108
5109impl WorkflowAssist {
5110    pub fn status(&self, cx: &AppContext) -> WorkflowAssistStatus {
5111        let assistant = InlineAssistant::global(cx);
5112        if self
5113            .assist_ids
5114            .iter()
5115            .any(|assist_id| assistant.assist_status(*assist_id, cx).is_pending())
5116        {
5117            WorkflowAssistStatus::Pending
5118        } else if self
5119            .assist_ids
5120            .iter()
5121            .all(|assist_id| assistant.assist_status(*assist_id, cx).is_confirmed())
5122        {
5123            WorkflowAssistStatus::Confirmed
5124        } else if self
5125            .assist_ids
5126            .iter()
5127            .all(|assist_id| assistant.assist_status(*assist_id, cx).is_done())
5128        {
5129            WorkflowAssistStatus::Done
5130        } else {
5131            WorkflowAssistStatus::Idle
5132        }
5133    }
5134}
5135
5136impl Render for ContextHistory {
5137    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
5138        div().size_full().child(self.picker.clone())
5139    }
5140}
5141
5142impl FocusableView for ContextHistory {
5143    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
5144        self.picker.focus_handle(cx)
5145    }
5146}
5147
5148impl EventEmitter<()> for ContextHistory {}
5149
5150impl Item for ContextHistory {
5151    type Event = ();
5152
5153    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
5154        Some("History".into())
5155    }
5156}
5157
5158pub struct ConfigurationView {
5159    focus_handle: FocusHandle,
5160    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
5161    _registry_subscription: Subscription,
5162}
5163
5164impl ConfigurationView {
5165    fn new(cx: &mut ViewContext<Self>) -> Self {
5166        let focus_handle = cx.focus_handle();
5167
5168        let registry_subscription = cx.subscribe(
5169            &LanguageModelRegistry::global(cx),
5170            |this, _, event: &language_model::Event, cx| match event {
5171                language_model::Event::AddedProvider(provider_id) => {
5172                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
5173                    if let Some(provider) = provider {
5174                        this.add_configuration_view(&provider, cx);
5175                    }
5176                }
5177                language_model::Event::RemovedProvider(provider_id) => {
5178                    this.remove_configuration_view(provider_id);
5179                }
5180                _ => {}
5181            },
5182        );
5183
5184        let mut this = Self {
5185            focus_handle,
5186            configuration_views: HashMap::default(),
5187            _registry_subscription: registry_subscription,
5188        };
5189        this.build_configuration_views(cx);
5190        this
5191    }
5192
5193    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
5194        let providers = LanguageModelRegistry::read_global(cx).providers();
5195        for provider in providers {
5196            self.add_configuration_view(&provider, cx);
5197        }
5198    }
5199
5200    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
5201        self.configuration_views.remove(provider_id);
5202    }
5203
5204    fn add_configuration_view(
5205        &mut self,
5206        provider: &Arc<dyn LanguageModelProvider>,
5207        cx: &mut ViewContext<Self>,
5208    ) {
5209        let configuration_view = provider.configuration_view(cx);
5210        self.configuration_views
5211            .insert(provider.id(), configuration_view);
5212    }
5213
5214    fn render_provider_view(
5215        &mut self,
5216        provider: &Arc<dyn LanguageModelProvider>,
5217        cx: &mut ViewContext<Self>,
5218    ) -> Div {
5219        let provider_id = provider.id().0.clone();
5220        let provider_name = provider.name().0.clone();
5221        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
5222
5223        let open_new_context = cx.listener({
5224            let provider = provider.clone();
5225            move |_, _, cx| {
5226                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
5227                    provider.clone(),
5228                ))
5229            }
5230        });
5231
5232        v_flex()
5233            .gap_2()
5234            .child(
5235                h_flex()
5236                    .justify_between()
5237                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
5238                    .when(provider.is_authenticated(cx), move |this| {
5239                        this.child(
5240                            h_flex().justify_end().child(
5241                                Button::new(
5242                                    SharedString::from(format!("new-context-{provider_id}")),
5243                                    "Open new context",
5244                                )
5245                                .icon_position(IconPosition::Start)
5246                                .icon(IconName::Plus)
5247                                .style(ButtonStyle::Filled)
5248                                .layer(ElevationIndex::ModalSurface)
5249                                .on_click(open_new_context),
5250                            ),
5251                        )
5252                    }),
5253            )
5254            .child(
5255                div()
5256                    .p(Spacing::Large.rems(cx))
5257                    .bg(cx.theme().colors().surface_background)
5258                    .border_1()
5259                    .border_color(cx.theme().colors().border_variant)
5260                    .rounded_md()
5261                    .when(configuration_view.is_none(), |this| {
5262                        this.child(div().child(Label::new(format!(
5263                            "No configuration view for {}",
5264                            provider_name
5265                        ))))
5266                    })
5267                    .when_some(configuration_view, |this, configuration_view| {
5268                        this.child(configuration_view)
5269                    }),
5270            )
5271    }
5272}
5273
5274impl Render for ConfigurationView {
5275    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
5276        let providers = LanguageModelRegistry::read_global(cx).providers();
5277        let provider_views = providers
5278            .into_iter()
5279            .map(|provider| self.render_provider_view(&provider, cx))
5280            .collect::<Vec<_>>();
5281
5282        let mut element = v_flex()
5283            .id("assistant-configuration-view")
5284            .track_focus(&self.focus_handle)
5285            .bg(cx.theme().colors().editor_background)
5286            .size_full()
5287            .overflow_y_scroll()
5288            .child(
5289                v_flex()
5290                    .p(Spacing::XXLarge.rems(cx))
5291                    .border_b_1()
5292                    .border_color(cx.theme().colors().border)
5293                    .gap_1()
5294                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
5295                    .child(
5296                        Label::new(
5297                            "At least one LLM provider must be configured to use the Assistant.",
5298                        )
5299                        .color(Color::Muted),
5300                    ),
5301            )
5302            .child(
5303                v_flex()
5304                    .p(Spacing::XXLarge.rems(cx))
5305                    .mt_1()
5306                    .gap_6()
5307                    .flex_1()
5308                    .children(provider_views),
5309            )
5310            .into_any();
5311
5312        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
5313        // because we couldn't the element to take up the size of the parent.
5314        canvas(
5315            move |bounds, cx| {
5316                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
5317                element
5318            },
5319            |_, mut element, cx| {
5320                element.paint(cx);
5321            },
5322        )
5323        .flex_1()
5324        .w_full()
5325    }
5326}
5327
5328pub enum ConfigurationViewEvent {
5329    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
5330}
5331
5332impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
5333
5334impl FocusableView for ConfigurationView {
5335    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
5336        self.focus_handle.clone()
5337    }
5338}
5339
5340impl Item for ConfigurationView {
5341    type Event = ConfigurationViewEvent;
5342
5343    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
5344        Some("Configuration".into())
5345    }
5346}
5347
5348type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
5349
5350fn render_slash_command_output_toggle(
5351    row: MultiBufferRow,
5352    is_folded: bool,
5353    fold: ToggleFold,
5354    _cx: &mut WindowContext,
5355) -> AnyElement {
5356    Disclosure::new(
5357        ("slash-command-output-fold-indicator", row.0 as u64),
5358        !is_folded,
5359    )
5360    .selected(is_folded)
5361    .on_click(move |_e, cx| fold(!is_folded, cx))
5362    .into_any_element()
5363}
5364
5365fn fold_toggle(
5366    name: &'static str,
5367) -> impl Fn(
5368    MultiBufferRow,
5369    bool,
5370    Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
5371    &mut WindowContext<'_>,
5372) -> AnyElement {
5373    move |row, is_folded, fold, _cx| {
5374        Disclosure::new((name, row.0 as u64), !is_folded)
5375            .selected(is_folded)
5376            .on_click(move |_e, cx| fold(!is_folded, cx))
5377            .into_any_element()
5378    }
5379}
5380
5381fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
5382    FoldPlaceholder {
5383        render: Arc::new({
5384            move |fold_id, fold_range, _cx| {
5385                let editor = editor.clone();
5386                ButtonLike::new(fold_id)
5387                    .style(ButtonStyle::Filled)
5388                    .layer(ElevationIndex::ElevatedSurface)
5389                    .child(Icon::new(IconName::TextSnippet))
5390                    .child(Label::new(title.clone()).single_line())
5391                    .on_click(move |_, cx| {
5392                        editor
5393                            .update(cx, |editor, cx| {
5394                                let buffer_start = fold_range
5395                                    .start
5396                                    .to_point(&editor.buffer().read(cx).read(cx));
5397                                let buffer_row = MultiBufferRow(buffer_start.row);
5398                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
5399                            })
5400                            .ok();
5401                    })
5402                    .into_any_element()
5403            }
5404        }),
5405        constrain_width: false,
5406        merge_adjacent: false,
5407    }
5408}
5409
5410fn render_quote_selection_output_toggle(
5411    row: MultiBufferRow,
5412    is_folded: bool,
5413    fold: ToggleFold,
5414    _cx: &mut WindowContext,
5415) -> AnyElement {
5416    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
5417        .selected(is_folded)
5418        .on_click(move |_e, cx| fold(!is_folded, cx))
5419        .into_any_element()
5420}
5421
5422fn render_pending_slash_command_gutter_decoration(
5423    row: MultiBufferRow,
5424    status: &PendingSlashCommandStatus,
5425    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
5426) -> AnyElement {
5427    let mut icon = IconButton::new(
5428        ("slash-command-gutter-decoration", row.0),
5429        ui::IconName::TriangleRight,
5430    )
5431    .on_click(move |_e, cx| confirm_command(cx))
5432    .icon_size(ui::IconSize::Small)
5433    .size(ui::ButtonSize::None);
5434
5435    match status {
5436        PendingSlashCommandStatus::Idle => {
5437            icon = icon.icon_color(Color::Muted);
5438        }
5439        PendingSlashCommandStatus::Running { .. } => {
5440            icon = icon.selected(true);
5441        }
5442        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
5443    }
5444
5445    icon.into_any_element()
5446}
5447
5448fn render_docs_slash_command_trailer(
5449    row: MultiBufferRow,
5450    command: PendingSlashCommand,
5451    cx: &mut WindowContext,
5452) -> AnyElement {
5453    if command.arguments.is_empty() {
5454        return Empty.into_any();
5455    }
5456    let args = DocsSlashCommandArgs::parse(&command.arguments);
5457
5458    let Some(store) = args
5459        .provider()
5460        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
5461    else {
5462        return Empty.into_any();
5463    };
5464
5465    let Some(package) = args.package() else {
5466        return Empty.into_any();
5467    };
5468
5469    let mut children = Vec::new();
5470
5471    if store.is_indexing(&package) {
5472        children.push(
5473            div()
5474                .id(("crates-being-indexed", row.0))
5475                .child(Icon::new(IconName::ArrowCircle).with_animation(
5476                    "arrow-circle",
5477                    Animation::new(Duration::from_secs(4)).repeat(),
5478                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
5479                ))
5480                .tooltip({
5481                    let package = package.clone();
5482                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
5483                })
5484                .into_any_element(),
5485        );
5486    }
5487
5488    if let Some(latest_error) = store.latest_error_for_package(&package) {
5489        children.push(
5490            div()
5491                .id(("latest-error", row.0))
5492                .child(
5493                    Icon::new(IconName::Warning)
5494                        .size(IconSize::Small)
5495                        .color(Color::Warning),
5496                )
5497                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
5498                .into_any_element(),
5499        )
5500    }
5501
5502    let is_indexing = store.is_indexing(&package);
5503    let latest_error = store.latest_error_for_package(&package);
5504
5505    if !is_indexing && latest_error.is_none() {
5506        return Empty.into_any();
5507    }
5508
5509    h_flex().gap_2().children(children).into_any_element()
5510}
5511
5512fn make_lsp_adapter_delegate(
5513    project: &Model<Project>,
5514    cx: &mut AppContext,
5515) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
5516    project.update(cx, |project, cx| {
5517        // TODO: Find the right worktree.
5518        let Some(worktree) = project.worktrees(cx).next() else {
5519            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
5520        };
5521        let http_client = project.client().http_client().clone();
5522        project.lsp_store().update(cx, |lsp_store, cx| {
5523            Ok(Some(LocalLspAdapterDelegate::new(
5524                lsp_store,
5525                &worktree,
5526                http_client,
5527                project.fs().clone(),
5528                cx,
5529            ) as Arc<dyn LspAdapterDelegate>))
5530        })
5531    })
5532}
5533
5534fn slash_command_error_block_renderer(message: String) -> RenderBlock {
5535    Box::new(move |_| {
5536        div()
5537            .pl_6()
5538            .child(
5539                Label::new(format!("error: {}", message))
5540                    .single_line()
5541                    .color(Color::Error),
5542            )
5543            .into_any()
5544    })
5545}
5546
5547enum TokenState {
5548    NoTokensLeft {
5549        max_token_count: usize,
5550        token_count: usize,
5551    },
5552    HasMoreTokens {
5553        max_token_count: usize,
5554        token_count: usize,
5555        over_warn_threshold: bool,
5556    },
5557}
5558
5559fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
5560    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
5561
5562    let model = LanguageModelRegistry::read_global(cx).active_model()?;
5563    let token_count = context.read(cx).token_count()?;
5564    let max_token_count = model.max_token_count();
5565
5566    let remaining_tokens = max_token_count as isize - token_count as isize;
5567    let token_state = if remaining_tokens <= 0 {
5568        TokenState::NoTokensLeft {
5569            max_token_count,
5570            token_count,
5571        }
5572    } else {
5573        let over_warn_threshold =
5574            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
5575        TokenState::HasMoreTokens {
5576            max_token_count,
5577            token_count,
5578            over_warn_threshold,
5579        }
5580    };
5581    Some(token_state)
5582}
5583
5584fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
5585    let image_size = data
5586        .size(0)
5587        .map(|dimension| Pixels::from(u32::from(dimension)));
5588    let image_ratio = image_size.width / image_size.height;
5589    let bounds_ratio = max_size.width / max_size.height;
5590
5591    if image_size.width > max_size.width || image_size.height > max_size.height {
5592        if bounds_ratio > image_ratio {
5593            size(
5594                image_size.width * (max_size.height / image_size.height),
5595                max_size.height,
5596            )
5597        } else {
5598            size(
5599                max_size.width,
5600                image_size.height * (max_size.width / image_size.width),
5601            )
5602        }
5603    } else {
5604        size(image_size.width, image_size.height)
5605    }
5606}
5607
5608enum ConfigurationError {
5609    NoProvider,
5610    ProviderNotAuthenticated,
5611}
5612
5613fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
5614    let provider = LanguageModelRegistry::read_global(cx).active_provider();
5615    let is_authenticated = provider
5616        .as_ref()
5617        .map_or(false, |provider| provider.is_authenticated(cx));
5618
5619    if provider.is_some() && is_authenticated {
5620        return None;
5621    }
5622
5623    if provider.is_none() {
5624        return Some(ConfigurationError::NoProvider);
5625    }
5626
5627    if !is_authenticated {
5628        return Some(ConfigurationError::ProviderNotAuthenticated);
5629    }
5630
5631    None
5632}
5633
5634#[cfg(test)]
5635mod tests {
5636    use super::*;
5637    use gpui::{AppContext, Context};
5638    use language::Buffer;
5639    use unindent::Unindent;
5640
5641    #[gpui::test]
5642    fn test_find_code_blocks(cx: &mut AppContext) {
5643        let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
5644
5645        let buffer = cx.new_model(|cx| {
5646            let text = r#"
5647                line 0
5648                line 1
5649                ```rust
5650                fn main() {}
5651                ```
5652                line 5
5653                line 6
5654                line 7
5655                ```go
5656                func main() {}
5657                ```
5658                line 11
5659                ```
5660                this is plain text code block
5661                ```
5662
5663                ```go
5664                func another() {}
5665                ```
5666                line 19
5667            "#
5668            .unindent();
5669            let mut buffer = Buffer::local(text, cx);
5670            buffer.set_language(Some(markdown.clone()), cx);
5671            buffer
5672        });
5673        let snapshot = buffer.read(cx).snapshot();
5674
5675        let code_blocks = vec![
5676            Point::new(3, 0)..Point::new(4, 0),
5677            Point::new(9, 0)..Point::new(10, 0),
5678            Point::new(13, 0)..Point::new(14, 0),
5679            Point::new(17, 0)..Point::new(18, 0),
5680        ]
5681        .into_iter()
5682        .map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
5683        .collect::<Vec<_>>();
5684
5685        let expected_results = vec![
5686            (0, None),
5687            (1, None),
5688            (2, Some(code_blocks[0].clone())),
5689            (3, Some(code_blocks[0].clone())),
5690            (4, Some(code_blocks[0].clone())),
5691            (5, None),
5692            (6, None),
5693            (7, None),
5694            (8, Some(code_blocks[1].clone())),
5695            (9, Some(code_blocks[1].clone())),
5696            (10, Some(code_blocks[1].clone())),
5697            (11, None),
5698            (12, Some(code_blocks[2].clone())),
5699            (13, Some(code_blocks[2].clone())),
5700            (14, Some(code_blocks[2].clone())),
5701            (15, None),
5702            (16, Some(code_blocks[3].clone())),
5703            (17, Some(code_blocks[3].clone())),
5704            (18, Some(code_blocks[3].clone())),
5705            (19, None),
5706        ];
5707
5708        for (row, expected) in expected_results {
5709            let offset = snapshot.point_to_offset(Point::new(row, 0));
5710            let range = find_surrounding_code_block(&snapshot, offset);
5711            assert_eq!(range, expected, "unexpected result on row {:?}", row);
5712        }
5713    }
5714}