workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::{call_settings::CallSettings, ActiveCall};
  16use client::{
  17    proto::{self, ErrorCode, PeerId},
  18    Client, ErrorExt, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use derive_more::{Deref, DerefMut};
  22use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  23use futures::{
  24    channel::{mpsc, oneshot},
  25    future::try_join_all,
  26    Future, FutureExt, StreamExt,
  27};
  28use gpui::{
  29    actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
  30    AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
  31    DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
  32    FocusableView, Global, GlobalPixels, InteractiveElement, IntoElement, KeyContext, Keystroke,
  33    LayoutId, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point,
  34    PromptLevel, Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext,
  35    VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
  36};
  37use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  38use itertools::Itertools;
  39use language::{LanguageRegistry, Rope};
  40use lazy_static::lazy_static;
  41pub use modal_layer::*;
  42use node_runtime::NodeRuntime;
  43use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  44pub use pane::*;
  45pub use pane_group::*;
  46use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  47pub use persistence::{
  48    model::{ItemId, WorkspaceLocation},
  49    WorkspaceDb, DB as WORKSPACE_DB,
  50};
  51use postage::stream::Stream;
  52use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  53use serde::Deserialize;
  54use settings::Settings;
  55use shared_screen::SharedScreen;
  56use status_bar::StatusBar;
  57pub use status_bar::StatusItemView;
  58use std::{
  59    any::TypeId,
  60    borrow::Cow,
  61    cell::RefCell,
  62    cmp, env,
  63    path::{Path, PathBuf},
  64    rc::Rc,
  65    sync::{atomic::AtomicUsize, Arc, Weak},
  66    time::Duration,
  67};
  68use task::SpawnInTerminal;
  69use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  70pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  71pub use ui;
  72use ui::Label;
  73use util::ResultExt;
  74use uuid::Uuid;
  75pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  76
  77use crate::persistence::{
  78    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  79    SerializedAxis,
  80};
  81
  82lazy_static! {
  83    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  84        .ok()
  85        .as_deref()
  86        .and_then(parse_pixel_size_env_var);
  87    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  88        .ok()
  89        .as_deref()
  90        .and_then(parse_pixel_position_env_var);
  91}
  92
  93#[derive(Clone, PartialEq)]
  94pub struct RemoveWorktreeFromProject(pub WorktreeId);
  95
  96actions!(
  97    workspace,
  98    [
  99        Open,
 100        NewFile,
 101        NewWindow,
 102        CloseWindow,
 103        CloseInactiveTabsAndPanes,
 104        AddFolderToProject,
 105        Unfollow,
 106        SaveAs,
 107        ReloadActiveItem,
 108        ActivatePreviousPane,
 109        ActivateNextPane,
 110        FollowNextCollaborator,
 111        NewTerminal,
 112        NewCenterTerminal,
 113        NewSearch,
 114        Feedback,
 115        Restart,
 116        Welcome,
 117        ToggleZoom,
 118        ToggleLeftDock,
 119        ToggleRightDock,
 120        ToggleBottomDock,
 121        CloseAllDocks,
 122        ToggleGraphicsProfiler,
 123    ]
 124);
 125
 126#[derive(Clone, PartialEq)]
 127pub struct OpenPaths {
 128    pub paths: Vec<PathBuf>,
 129}
 130
 131#[derive(Clone, Deserialize, PartialEq)]
 132pub struct ActivatePane(pub usize);
 133
 134#[derive(Clone, Deserialize, PartialEq)]
 135pub struct ActivatePaneInDirection(pub SplitDirection);
 136
 137#[derive(Clone, Deserialize, PartialEq)]
 138pub struct SwapPaneInDirection(pub SplitDirection);
 139
 140#[derive(Clone, Deserialize, PartialEq)]
 141pub struct NewFileInDirection(pub SplitDirection);
 142
 143#[derive(Clone, PartialEq, Debug, Deserialize)]
 144#[serde(rename_all = "camelCase")]
 145pub struct SaveAll {
 146    pub save_intent: Option<SaveIntent>,
 147}
 148
 149#[derive(Clone, PartialEq, Debug, Deserialize)]
 150#[serde(rename_all = "camelCase")]
 151pub struct Save {
 152    pub save_intent: Option<SaveIntent>,
 153}
 154
 155#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 156#[serde(rename_all = "camelCase")]
 157pub struct CloseAllItemsAndPanes {
 158    pub save_intent: Option<SaveIntent>,
 159}
 160
 161#[derive(Clone, Deserialize, PartialEq)]
 162pub struct SendKeystrokes(pub String);
 163
 164impl_actions!(
 165    workspace,
 166    [
 167        ActivatePane,
 168        ActivatePaneInDirection,
 169        CloseAllItemsAndPanes,
 170        NewFileInDirection,
 171        OpenTerminal,
 172        Save,
 173        SaveAll,
 174        SwapPaneInDirection,
 175        SendKeystrokes,
 176    ]
 177);
 178
 179#[derive(Deserialize)]
 180pub struct Toast {
 181    id: usize,
 182    msg: Cow<'static, str>,
 183    #[serde(skip)]
 184    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 185}
 186
 187impl Toast {
 188    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 189        Toast {
 190            id,
 191            msg: msg.into(),
 192            on_click: None,
 193        }
 194    }
 195
 196    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 197    where
 198        M: Into<Cow<'static, str>>,
 199        F: Fn(&mut WindowContext) + 'static,
 200    {
 201        self.on_click = Some((message.into(), Arc::new(on_click)));
 202        self
 203    }
 204}
 205
 206impl PartialEq for Toast {
 207    fn eq(&self, other: &Self) -> bool {
 208        self.id == other.id
 209            && self.msg == other.msg
 210            && self.on_click.is_some() == other.on_click.is_some()
 211    }
 212}
 213
 214impl Clone for Toast {
 215    fn clone(&self) -> Self {
 216        Toast {
 217            id: self.id,
 218            msg: self.msg.to_owned(),
 219            on_click: self.on_click.clone(),
 220        }
 221    }
 222}
 223
 224#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 225pub struct OpenTerminal {
 226    pub working_directory: PathBuf,
 227}
 228
 229pub type WorkspaceId = i64;
 230
 231pub fn init_settings(cx: &mut AppContext) {
 232    WorkspaceSettings::register(cx);
 233    ItemSettings::register(cx);
 234}
 235
 236pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 237    init_settings(cx);
 238    notifications::init(cx);
 239
 240    cx.on_action(Workspace::close_global);
 241    cx.on_action(restart);
 242
 243    cx.on_action({
 244        let app_state = Arc::downgrade(&app_state);
 245        move |_: &Open, cx: &mut AppContext| {
 246            let paths = cx.prompt_for_paths(PathPromptOptions {
 247                files: true,
 248                directories: true,
 249                multiple: true,
 250            });
 251
 252            if let Some(app_state) = app_state.upgrade() {
 253                cx.spawn(move |cx| async move {
 254                    if let Some(paths) = paths.await.log_err().flatten() {
 255                        cx.update(|cx| {
 256                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 257                        })
 258                        .ok();
 259                    }
 260                })
 261                .detach();
 262            }
 263        }
 264    });
 265}
 266
 267#[derive(Clone, Default, Deref, DerefMut)]
 268struct ProjectItemBuilders(
 269    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>,
 270);
 271
 272impl Global for ProjectItemBuilders {}
 273
 274pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 275    let builders = cx.default_global::<ProjectItemBuilders>();
 276    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 277        let item = model.downcast::<I::Item>().unwrap();
 278        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 279    });
 280}
 281
 282type FollowableItemBuilder = fn(
 283    View<Pane>,
 284    View<Workspace>,
 285    ViewId,
 286    &mut Option<proto::view::Variant>,
 287    &mut WindowContext,
 288) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 289
 290#[derive(Default, Deref, DerefMut)]
 291struct FollowableItemBuilders(
 292    HashMap<
 293        TypeId,
 294        (
 295            FollowableItemBuilder,
 296            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 297        ),
 298    >,
 299);
 300
 301impl Global for FollowableItemBuilders {}
 302
 303pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 304    let builders = cx.default_global::<FollowableItemBuilders>();
 305    builders.insert(
 306        TypeId::of::<I>(),
 307        (
 308            |pane, workspace, id, state, cx| {
 309                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 310                    cx.foreground_executor()
 311                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 312                })
 313            },
 314            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 315        ),
 316    );
 317}
 318
 319#[derive(Default, Deref, DerefMut)]
 320struct ItemDeserializers(
 321    HashMap<
 322        Arc<str>,
 323        fn(
 324            Model<Project>,
 325            WeakView<Workspace>,
 326            WorkspaceId,
 327            ItemId,
 328            &mut ViewContext<Pane>,
 329        ) -> Task<Result<Box<dyn ItemHandle>>>,
 330    >,
 331);
 332
 333impl Global for ItemDeserializers {}
 334
 335pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 336    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 337        let deserializers = cx.default_global::<ItemDeserializers>();
 338        deserializers.insert(
 339            Arc::from(serialized_item_kind),
 340            |project, workspace, workspace_id, item_id, cx| {
 341                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 342                cx.foreground_executor()
 343                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 344            },
 345        );
 346    }
 347}
 348
 349pub struct AppState {
 350    pub languages: Arc<LanguageRegistry>,
 351    pub client: Arc<Client>,
 352    pub user_store: Model<UserStore>,
 353    pub workspace_store: Model<WorkspaceStore>,
 354    pub fs: Arc<dyn fs::Fs>,
 355    pub build_window_options:
 356        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 357    pub node_runtime: Arc<dyn NodeRuntime>,
 358}
 359
 360struct GlobalAppState(Weak<AppState>);
 361
 362impl Global for GlobalAppState {}
 363
 364pub struct WorkspaceStore {
 365    workspaces: HashSet<WindowHandle<Workspace>>,
 366    client: Arc<Client>,
 367    _subscriptions: Vec<client::Subscription>,
 368}
 369
 370#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 371struct Follower {
 372    project_id: Option<u64>,
 373    peer_id: PeerId,
 374}
 375
 376impl AppState {
 377    pub fn global(cx: &AppContext) -> Weak<Self> {
 378        cx.global::<GlobalAppState>().0.clone()
 379    }
 380    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 381        cx.try_global::<GlobalAppState>()
 382            .map(|state| state.0.clone())
 383    }
 384    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 385        cx.set_global(GlobalAppState(state));
 386    }
 387
 388    #[cfg(any(test, feature = "test-support"))]
 389    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 390        use node_runtime::FakeNodeRuntime;
 391        use settings::SettingsStore;
 392
 393        if !cx.has_global::<SettingsStore>() {
 394            let settings_store = SettingsStore::test(cx);
 395            cx.set_global(settings_store);
 396        }
 397
 398        let fs = fs::FakeFs::new(cx.background_executor().clone());
 399        let languages = Arc::new(LanguageRegistry::test());
 400        let http_client = util::http::FakeHttpClient::with_404_response();
 401        let client = Client::new(http_client.clone(), cx);
 402        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 403        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 404
 405        theme::init(theme::LoadThemes::JustBase, cx);
 406        client::init(&client, cx);
 407        crate::init_settings(cx);
 408
 409        Arc::new(Self {
 410            client,
 411            fs,
 412            languages,
 413            user_store,
 414            workspace_store,
 415            node_runtime: FakeNodeRuntime::new(),
 416            build_window_options: |_, _, _| Default::default(),
 417        })
 418    }
 419}
 420
 421struct DelayedDebouncedEditAction {
 422    task: Option<Task<()>>,
 423    cancel_channel: Option<oneshot::Sender<()>>,
 424}
 425
 426impl DelayedDebouncedEditAction {
 427    fn new() -> DelayedDebouncedEditAction {
 428        DelayedDebouncedEditAction {
 429            task: None,
 430            cancel_channel: None,
 431        }
 432    }
 433
 434    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 435    where
 436        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 437    {
 438        if let Some(channel) = self.cancel_channel.take() {
 439            _ = channel.send(());
 440        }
 441
 442        let (sender, mut receiver) = oneshot::channel::<()>();
 443        self.cancel_channel = Some(sender);
 444
 445        let previous_task = self.task.take();
 446        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 447            let mut timer = cx.background_executor().timer(delay).fuse();
 448            if let Some(previous_task) = previous_task {
 449                previous_task.await;
 450            }
 451
 452            futures::select_biased! {
 453                _ = receiver => return,
 454                    _ = timer => {}
 455            }
 456
 457            if let Some(result) = workspace
 458                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 459                .log_err()
 460            {
 461                result.await.log_err();
 462            }
 463        }));
 464    }
 465}
 466
 467pub enum Event {
 468    PaneAdded(View<Pane>),
 469    ContactRequestedJoin(u64),
 470    WorkspaceCreated(WeakView<Workspace>),
 471    SpawnTask(SpawnInTerminal),
 472}
 473
 474pub enum OpenVisible {
 475    All,
 476    None,
 477    OnlyFiles,
 478    OnlyDirectories,
 479}
 480
 481pub struct Workspace {
 482    weak_self: WeakView<Self>,
 483    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 484    zoomed: Option<AnyWeakView>,
 485    zoomed_position: Option<DockPosition>,
 486    center: PaneGroup,
 487    left_dock: View<Dock>,
 488    bottom_dock: View<Dock>,
 489    right_dock: View<Dock>,
 490    panes: Vec<View<Pane>>,
 491    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 492    active_pane: View<Pane>,
 493    last_active_center_pane: Option<WeakView<Pane>>,
 494    last_active_view_id: Option<proto::ViewId>,
 495    status_bar: View<StatusBar>,
 496    modal_layer: View<ModalLayer>,
 497    titlebar_item: Option<AnyView>,
 498    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 499    project: Model<Project>,
 500    follower_states: HashMap<View<Pane>, FollowerState>,
 501    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 502    window_edited: bool,
 503    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 504    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 505    database_id: WorkspaceId,
 506    app_state: Arc<AppState>,
 507    dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
 508    _subscriptions: Vec<Subscription>,
 509    _apply_leader_updates: Task<Result<()>>,
 510    _observe_current_user: Task<Result<()>>,
 511    _schedule_serialize: Option<Task<()>>,
 512    pane_history_timestamp: Arc<AtomicUsize>,
 513    bounds: Bounds<Pixels>,
 514}
 515
 516impl EventEmitter<Event> for Workspace {}
 517
 518#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 519pub struct ViewId {
 520    pub creator: PeerId,
 521    pub id: u64,
 522}
 523
 524#[derive(Default)]
 525struct FollowerState {
 526    leader_id: PeerId,
 527    active_view_id: Option<ViewId>,
 528    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 529}
 530
 531impl Workspace {
 532    pub fn new(
 533        workspace_id: WorkspaceId,
 534        project: Model<Project>,
 535        app_state: Arc<AppState>,
 536        cx: &mut ViewContext<Self>,
 537    ) -> Self {
 538        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 539        cx.subscribe(&project, move |this, _, event, cx| {
 540            match event {
 541                project::Event::RemoteIdChanged(_) => {
 542                    this.update_window_title(cx);
 543                }
 544
 545                project::Event::CollaboratorLeft(peer_id) => {
 546                    this.collaborator_left(*peer_id, cx);
 547                }
 548
 549                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 550                    this.update_window_title(cx);
 551                    this.serialize_workspace(cx);
 552                }
 553
 554                project::Event::DisconnectedFromHost => {
 555                    this.update_window_edited(cx);
 556                    let panes_to_unfollow: Vec<View<Pane>> =
 557                        this.follower_states.keys().map(|k| k.clone()).collect();
 558                    for pane in panes_to_unfollow {
 559                        this.unfollow(&pane, cx);
 560                    }
 561                    cx.disable_focus();
 562                }
 563
 564                project::Event::Closed => {
 565                    cx.remove_window();
 566                }
 567
 568                project::Event::DeletedEntry(entry_id) => {
 569                    for pane in this.panes.iter() {
 570                        pane.update(cx, |pane, cx| {
 571                            pane.handle_deleted_project_item(*entry_id, cx)
 572                        });
 573                    }
 574                }
 575
 576                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 577                    cx.new_view(|_| MessageNotification::new(message.clone()))
 578                }),
 579
 580                project::Event::LanguageServerPrompt(request) => {
 581                    let request = request.clone();
 582
 583                    cx.spawn(|_, mut cx| async move {
 584                        let messages = request
 585                            .actions
 586                            .iter()
 587                            .map(|action| action.title.as_str())
 588                            .collect::<Vec<_>>();
 589                        let index = cx
 590                            .update(|cx| {
 591                                cx.prompt(request.level, "", Some(&request.message), &messages)
 592                            })?
 593                            .await?;
 594                        request.respond(index).await;
 595
 596                        Result::<(), anyhow::Error>::Ok(())
 597                    })
 598                    .detach()
 599                }
 600
 601                _ => {}
 602            }
 603            cx.notify()
 604        })
 605        .detach();
 606
 607        cx.on_focus_lost(|this, cx| {
 608            let focus_handle = this.focus_handle(cx);
 609            cx.focus(&focus_handle);
 610        })
 611        .detach();
 612
 613        let weak_handle = cx.view().downgrade();
 614        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 615
 616        let center_pane = cx.new_view(|cx| {
 617            Pane::new(
 618                weak_handle.clone(),
 619                project.clone(),
 620                pane_history_timestamp.clone(),
 621                None,
 622                cx,
 623            )
 624        });
 625        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 626
 627        cx.focus_view(&center_pane);
 628        cx.emit(Event::PaneAdded(center_pane.clone()));
 629
 630        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 631        app_state.workspace_store.update(cx, |store, _| {
 632            store.workspaces.insert(window_handle);
 633        });
 634
 635        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 636        let mut connection_status = app_state.client.status();
 637        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 638            current_user.next().await;
 639            connection_status.next().await;
 640            let mut stream =
 641                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 642
 643            while stream.recv().await.is_some() {
 644                this.update(&mut cx, |_, cx| cx.notify())?;
 645            }
 646            anyhow::Ok(())
 647        });
 648
 649        // All leader updates are enqueued and then processed in a single task, so
 650        // that each asynchronous operation can be run in order.
 651        let (leader_updates_tx, mut leader_updates_rx) =
 652            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 653        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 654            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 655                Self::process_leader_update(&this, leader_id, update, &mut cx)
 656                    .await
 657                    .log_err();
 658            }
 659
 660            Ok(())
 661        });
 662
 663        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 664
 665        let left_dock = Dock::new(DockPosition::Left, cx);
 666        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 667        let right_dock = Dock::new(DockPosition::Right, cx);
 668        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 669        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 670        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 671        let status_bar = cx.new_view(|cx| {
 672            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 673            status_bar.add_left_item(left_dock_buttons, cx);
 674            status_bar.add_right_item(right_dock_buttons, cx);
 675            status_bar.add_right_item(bottom_dock_buttons, cx);
 676            status_bar
 677        });
 678
 679        let modal_layer = cx.new_view(|_| ModalLayer::new());
 680
 681        let mut active_call = None;
 682        if let Some(call) = ActiveCall::try_global(cx) {
 683            let call = call.clone();
 684            let mut subscriptions = Vec::new();
 685            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 686            active_call = Some((call, subscriptions));
 687        }
 688
 689        let subscriptions = vec![
 690            cx.observe_window_activation(Self::on_window_activation_changed),
 691            cx.observe_window_bounds(move |_, cx| {
 692                if let Some(display) = cx.display() {
 693                    // Transform fixed bounds to be stored in terms of the containing display
 694                    let mut bounds = cx.window_bounds();
 695                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 696                        let display_bounds = display.bounds();
 697                        window_bounds.origin.x -= display_bounds.origin.x;
 698                        window_bounds.origin.y -= display_bounds.origin.y;
 699                    }
 700
 701                    if let Some(display_uuid) = display.uuid().log_err() {
 702                        cx.background_executor()
 703                            .spawn(DB.set_window_bounds(
 704                                workspace_id,
 705                                SerializedWindowsBounds(bounds),
 706                                display_uuid,
 707                            ))
 708                            .detach_and_log_err(cx);
 709                    }
 710                }
 711                cx.notify();
 712            }),
 713            cx.observe_window_appearance(|_, cx| {
 714                let window_appearance = cx.appearance();
 715
 716                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 717
 718                ThemeSettings::reload_current_theme(cx);
 719            }),
 720            cx.observe(&left_dock, |this, _, cx| {
 721                this.serialize_workspace(cx);
 722                cx.notify();
 723            }),
 724            cx.observe(&bottom_dock, |this, _, cx| {
 725                this.serialize_workspace(cx);
 726                cx.notify();
 727            }),
 728            cx.observe(&right_dock, |this, _, cx| {
 729                this.serialize_workspace(cx);
 730                cx.notify();
 731            }),
 732            cx.on_release(|this, window, cx| {
 733                this.app_state.workspace_store.update(cx, |store, _| {
 734                    let window = window.downcast::<Self>().unwrap();
 735                    store.workspaces.remove(&window);
 736                })
 737            }),
 738        ];
 739
 740        cx.defer(|this, cx| {
 741            this.update_window_title(cx);
 742        });
 743        Workspace {
 744            weak_self: weak_handle.clone(),
 745            zoomed: None,
 746            zoomed_position: None,
 747            center: PaneGroup::new(center_pane.clone()),
 748            panes: vec![center_pane.clone()],
 749            panes_by_item: Default::default(),
 750            active_pane: center_pane.clone(),
 751            last_active_center_pane: Some(center_pane.downgrade()),
 752            last_active_view_id: None,
 753            status_bar,
 754            modal_layer,
 755            titlebar_item: None,
 756            notifications: Default::default(),
 757            left_dock,
 758            bottom_dock,
 759            right_dock,
 760            project: project.clone(),
 761            follower_states: Default::default(),
 762            last_leaders_by_pane: Default::default(),
 763            dispatching_keystrokes: Default::default(),
 764            window_edited: false,
 765            active_call,
 766            database_id: workspace_id,
 767            app_state,
 768            _observe_current_user,
 769            _apply_leader_updates,
 770            _schedule_serialize: None,
 771            leader_updates_tx,
 772            _subscriptions: subscriptions,
 773            pane_history_timestamp,
 774            workspace_actions: Default::default(),
 775            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 776            bounds: Default::default(),
 777        }
 778    }
 779
 780    fn new_local(
 781        abs_paths: Vec<PathBuf>,
 782        app_state: Arc<AppState>,
 783        requesting_window: Option<WindowHandle<Workspace>>,
 784        cx: &mut AppContext,
 785    ) -> Task<
 786        anyhow::Result<(
 787            WindowHandle<Workspace>,
 788            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 789        )>,
 790    > {
 791        let project_handle = Project::local(
 792            app_state.client.clone(),
 793            app_state.node_runtime.clone(),
 794            app_state.user_store.clone(),
 795            app_state.languages.clone(),
 796            app_state.fs.clone(),
 797            cx,
 798        );
 799
 800        cx.spawn(|mut cx| async move {
 801            let serialized_workspace: Option<SerializedWorkspace> =
 802                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 803
 804            let paths_to_open = Arc::new(abs_paths);
 805
 806            // Get project paths for all of the abs_paths
 807            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 808            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 809                Vec::with_capacity(paths_to_open.len());
 810            for path in paths_to_open.iter().cloned() {
 811                if let Some((worktree, project_entry)) = cx
 812                    .update(|cx| {
 813                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 814                    })?
 815                    .await
 816                    .log_err()
 817                {
 818                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 819                    project_paths.push((path, Some(project_entry)));
 820                } else {
 821                    project_paths.push((path, None));
 822                }
 823            }
 824
 825            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 826                serialized_workspace.id
 827            } else {
 828                DB.next_id().await.unwrap_or(0)
 829            };
 830
 831            let window = if let Some(window) = requesting_window {
 832                cx.update_window(window.into(), |_, cx| {
 833                    cx.replace_root_view(|cx| {
 834                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 835                    });
 836                })?;
 837                window
 838            } else {
 839                let window_bounds_override = window_bounds_env_override(&cx);
 840                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 841                    (Some(bounds), None)
 842                } else {
 843                    serialized_workspace
 844                        .as_ref()
 845                        .and_then(|serialized_workspace| {
 846                            let serialized_display = serialized_workspace.display?;
 847                            let mut bounds = serialized_workspace.bounds?;
 848
 849                            // Stored bounds are relative to the containing display.
 850                            // So convert back to global coordinates if that screen still exists
 851                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 852                                let screen = cx
 853                                    .update(|cx| {
 854                                        cx.displays().into_iter().find(|display| {
 855                                            display.uuid().ok() == Some(serialized_display)
 856                                        })
 857                                    })
 858                                    .ok()??;
 859                                let screen_bounds = screen.bounds();
 860                                window_bounds.origin.x += screen_bounds.origin.x;
 861                                window_bounds.origin.y += screen_bounds.origin.y;
 862                                bounds = WindowBounds::Fixed(window_bounds);
 863                            }
 864
 865                            Some((bounds, serialized_display))
 866                        })
 867                        .unzip()
 868                };
 869
 870                // Use the serialized workspace to construct the new window
 871                let options =
 872                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 873
 874                cx.open_window(options, {
 875                    let app_state = app_state.clone();
 876                    let workspace_id = workspace_id.clone();
 877                    let project_handle = project_handle.clone();
 878                    move |cx| {
 879                        cx.new_view(|cx| {
 880                            Workspace::new(workspace_id, project_handle, app_state, cx)
 881                        })
 882                    }
 883                })?
 884            };
 885
 886            window
 887                .update(&mut cx, |_, cx| cx.activate_window())
 888                .log_err();
 889
 890            notify_if_database_failed(window, &mut cx);
 891            let opened_items = window
 892                .update(&mut cx, |_workspace, cx| {
 893                    open_items(serialized_workspace, project_paths, app_state, cx)
 894                })?
 895                .await
 896                .unwrap_or_default();
 897
 898            Ok((window, opened_items))
 899        })
 900    }
 901
 902    pub fn weak_handle(&self) -> WeakView<Self> {
 903        self.weak_self.clone()
 904    }
 905
 906    pub fn left_dock(&self) -> &View<Dock> {
 907        &self.left_dock
 908    }
 909
 910    pub fn bottom_dock(&self) -> &View<Dock> {
 911        &self.bottom_dock
 912    }
 913
 914    pub fn right_dock(&self) -> &View<Dock> {
 915        &self.right_dock
 916    }
 917
 918    pub fn is_edited(&self) -> bool {
 919        self.window_edited
 920    }
 921
 922    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
 923        let dock = match panel.position(cx) {
 924            DockPosition::Left => &self.left_dock,
 925            DockPosition::Bottom => &self.bottom_dock,
 926            DockPosition::Right => &self.right_dock,
 927        };
 928
 929        dock.update(cx, |dock, cx| {
 930            dock.add_panel(panel, self.weak_self.clone(), cx)
 931        });
 932    }
 933
 934    pub fn status_bar(&self) -> &View<StatusBar> {
 935        &self.status_bar
 936    }
 937
 938    pub fn app_state(&self) -> &Arc<AppState> {
 939        &self.app_state
 940    }
 941
 942    pub fn user_store(&self) -> &Model<UserStore> {
 943        &self.app_state.user_store
 944    }
 945
 946    pub fn project(&self) -> &Model<Project> {
 947        &self.project
 948    }
 949
 950    pub fn recent_navigation_history(
 951        &self,
 952        limit: Option<usize>,
 953        cx: &AppContext,
 954    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 955        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 956        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 957        for pane in &self.panes {
 958            let pane = pane.read(cx);
 959            pane.nav_history()
 960                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 961                    if let Some(fs_path) = &fs_path {
 962                        abs_paths_opened
 963                            .entry(fs_path.clone())
 964                            .or_default()
 965                            .insert(project_path.clone());
 966                    }
 967                    let timestamp = entry.timestamp;
 968                    match history.entry(project_path) {
 969                        hash_map::Entry::Occupied(mut entry) => {
 970                            let (_, old_timestamp) = entry.get();
 971                            if &timestamp > old_timestamp {
 972                                entry.insert((fs_path, timestamp));
 973                            }
 974                        }
 975                        hash_map::Entry::Vacant(entry) => {
 976                            entry.insert((fs_path, timestamp));
 977                        }
 978                    }
 979                });
 980        }
 981
 982        history
 983            .into_iter()
 984            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 985            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 986            .rev()
 987            .filter(|(history_path, abs_path)| {
 988                let latest_project_path_opened = abs_path
 989                    .as_ref()
 990                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 991                    .and_then(|project_paths| {
 992                        project_paths
 993                            .iter()
 994                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 995                    });
 996
 997                match latest_project_path_opened {
 998                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 999                    None => true,
1000                }
1001            })
1002            .take(limit.unwrap_or(usize::MAX))
1003            .collect()
1004    }
1005
1006    fn navigate_history(
1007        &mut self,
1008        pane: WeakView<Pane>,
1009        mode: NavigationMode,
1010        cx: &mut ViewContext<Workspace>,
1011    ) -> Task<Result<()>> {
1012        let to_load = if let Some(pane) = pane.upgrade() {
1013            pane.update(cx, |pane, cx| {
1014                pane.focus(cx);
1015                loop {
1016                    // Retrieve the weak item handle from the history.
1017                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1018
1019                    // If the item is still present in this pane, then activate it.
1020                    if let Some(index) = entry
1021                        .item
1022                        .upgrade()
1023                        .and_then(|v| pane.index_for_item(v.as_ref()))
1024                    {
1025                        let prev_active_item_index = pane.active_item_index();
1026                        pane.nav_history_mut().set_mode(mode);
1027                        pane.activate_item(index, true, true, cx);
1028                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1029
1030                        let mut navigated = prev_active_item_index != pane.active_item_index();
1031                        if let Some(data) = entry.data {
1032                            navigated |= pane.active_item()?.navigate(data, cx);
1033                        }
1034
1035                        if navigated {
1036                            break None;
1037                        }
1038                    }
1039                    // If the item is no longer present in this pane, then retrieve its
1040                    // project path in order to reopen it.
1041                    else {
1042                        break pane
1043                            .nav_history()
1044                            .path_for_item(entry.item.id())
1045                            .map(|(project_path, _)| (project_path, entry));
1046                    }
1047                }
1048            })
1049        } else {
1050            None
1051        };
1052
1053        if let Some((project_path, entry)) = to_load {
1054            // If the item was no longer present, then load it again from its previous path.
1055            let task = self.load_path(project_path, cx);
1056            cx.spawn(|workspace, mut cx| async move {
1057                let task = task.await;
1058                let mut navigated = false;
1059                if let Some((project_entry_id, build_item)) = task.log_err() {
1060                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1061                        pane.nav_history_mut().set_mode(mode);
1062                        pane.active_item().map(|p| p.item_id())
1063                    })?;
1064
1065                    pane.update(&mut cx, |pane, cx| {
1066                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1067                        navigated |= Some(item.item_id()) != prev_active_item_id;
1068                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1069                        if let Some(data) = entry.data {
1070                            navigated |= item.navigate(data, cx);
1071                        }
1072                    })?;
1073                }
1074
1075                if !navigated {
1076                    workspace
1077                        .update(&mut cx, |workspace, cx| {
1078                            Self::navigate_history(workspace, pane, mode, cx)
1079                        })?
1080                        .await?;
1081                }
1082
1083                Ok(())
1084            })
1085        } else {
1086            Task::ready(Ok(()))
1087        }
1088    }
1089
1090    pub fn go_back(
1091        &mut self,
1092        pane: WeakView<Pane>,
1093        cx: &mut ViewContext<Workspace>,
1094    ) -> Task<Result<()>> {
1095        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1096    }
1097
1098    pub fn go_forward(
1099        &mut self,
1100        pane: WeakView<Pane>,
1101        cx: &mut ViewContext<Workspace>,
1102    ) -> Task<Result<()>> {
1103        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1104    }
1105
1106    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1107        self.navigate_history(
1108            self.active_pane().downgrade(),
1109            NavigationMode::ReopeningClosedItem,
1110            cx,
1111        )
1112    }
1113
1114    pub fn client(&self) -> &Client {
1115        &self.app_state.client
1116    }
1117
1118    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1119        self.titlebar_item = Some(item);
1120        cx.notify();
1121    }
1122
1123    pub fn titlebar_item(&self) -> Option<AnyView> {
1124        self.titlebar_item.clone()
1125    }
1126
1127    /// Call the given callback with a workspace whose project is local.
1128    ///
1129    /// If the given workspace has a local project, then it will be passed
1130    /// to the callback. Otherwise, a new empty window will be created.
1131    pub fn with_local_workspace<T, F>(
1132        &mut self,
1133        cx: &mut ViewContext<Self>,
1134        callback: F,
1135    ) -> Task<Result<T>>
1136    where
1137        T: 'static,
1138        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1139    {
1140        if self.project.read(cx).is_local() {
1141            Task::Ready(Some(Ok(callback(self, cx))))
1142        } else {
1143            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1144            cx.spawn(|_vh, mut cx| async move {
1145                let (workspace, _) = task.await?;
1146                workspace.update(&mut cx, callback)
1147            })
1148        }
1149    }
1150
1151    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1152        self.project.read(cx).worktrees()
1153    }
1154
1155    pub fn visible_worktrees<'a>(
1156        &self,
1157        cx: &'a AppContext,
1158    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1159        self.project.read(cx).visible_worktrees(cx)
1160    }
1161
1162    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1163        let futures = self
1164            .worktrees(cx)
1165            .filter_map(|worktree| worktree.read(cx).as_local())
1166            .map(|worktree| worktree.scan_complete())
1167            .collect::<Vec<_>>();
1168        async move {
1169            for future in futures {
1170                future.await;
1171            }
1172        }
1173    }
1174
1175    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1176        cx.defer(|cx| {
1177            cx.windows().iter().find(|window| {
1178                window
1179                    .update(cx, |_, window| {
1180                        if window.is_window_active() {
1181                            //This can only get called when the window's project connection has been lost
1182                            //so we don't need to prompt the user for anything and instead just close the window
1183                            window.remove_window();
1184                            true
1185                        } else {
1186                            false
1187                        }
1188                    })
1189                    .unwrap_or(false)
1190            });
1191        });
1192    }
1193
1194    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1195        let window = cx.window_handle();
1196        let prepare = self.prepare_to_close(false, cx);
1197        cx.spawn(|_, mut cx| async move {
1198            if prepare.await? {
1199                window.update(&mut cx, |_, cx| {
1200                    cx.remove_window();
1201                })?;
1202            }
1203            anyhow::Ok(())
1204        })
1205        .detach_and_log_err(cx)
1206    }
1207
1208    pub fn prepare_to_close(
1209        &mut self,
1210        quitting: bool,
1211        cx: &mut ViewContext<Self>,
1212    ) -> Task<Result<bool>> {
1213        let active_call = self.active_call().cloned();
1214        let window = cx.window_handle();
1215
1216        cx.spawn(|this, mut cx| async move {
1217            let workspace_count = (*cx).update(|cx| {
1218                cx.windows()
1219                    .iter()
1220                    .filter(|window| window.downcast::<Workspace>().is_some())
1221                    .count()
1222            })?;
1223
1224            if let Some(active_call) = active_call {
1225                if !quitting
1226                    && workspace_count == 1
1227                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1228                {
1229                    let answer = window.update(&mut cx, |_, cx| {
1230                        cx.prompt(
1231                            PromptLevel::Warning,
1232                            "Do you want to leave the current call?",
1233                            None,
1234                            &["Close window and hang up", "Cancel"],
1235                        )
1236                    })?;
1237
1238                    if answer.await.log_err() == Some(1) {
1239                        return anyhow::Ok(false);
1240                    } else {
1241                        active_call
1242                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1243                            .await
1244                            .log_err();
1245                    }
1246                }
1247            }
1248
1249            Ok(this
1250                .update(&mut cx, |this, cx| {
1251                    this.save_all_internal(SaveIntent::Close, cx)
1252                })?
1253                .await?)
1254        })
1255    }
1256
1257    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1258        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1259            .detach_and_log_err(cx);
1260    }
1261
1262    fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
1263        let mut keystrokes: Vec<Keystroke> = action
1264            .0
1265            .split(" ")
1266            .flat_map(|k| Keystroke::parse(k).log_err())
1267            .collect();
1268        keystrokes.reverse();
1269
1270        self.dispatching_keystrokes
1271            .borrow_mut()
1272            .append(&mut keystrokes);
1273
1274        let keystrokes = self.dispatching_keystrokes.clone();
1275        cx.window_context()
1276            .spawn(|mut cx| async move {
1277                // limit to 100 keystrokes to avoid infinite recursion.
1278                for _ in 0..100 {
1279                    let Some(keystroke) = keystrokes.borrow_mut().pop() else {
1280                        return Ok(());
1281                    };
1282                    cx.update(|cx| {
1283                        let focused = cx.focused();
1284                        cx.dispatch_keystroke(keystroke.clone());
1285                        if cx.focused() != focused {
1286                            // dispatch_keystroke may cause the focus to change.
1287                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
1288                            // And we need that to happen before the next keystroke to keep vim mode happy...
1289                            // (Note that the tests always do this implicitly, so you must manually test with something like:
1290                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
1291                            // )
1292                            cx.draw();
1293                        }
1294                    })?;
1295                }
1296                keystrokes.borrow_mut().clear();
1297                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
1298            })
1299            .detach_and_log_err(cx);
1300    }
1301
1302    fn save_all_internal(
1303        &mut self,
1304        mut save_intent: SaveIntent,
1305        cx: &mut ViewContext<Self>,
1306    ) -> Task<Result<bool>> {
1307        if self.project.read(cx).is_disconnected() {
1308            return Task::ready(Ok(true));
1309        }
1310        let dirty_items = self
1311            .panes
1312            .iter()
1313            .flat_map(|pane| {
1314                pane.read(cx).items().filter_map(|item| {
1315                    if item.is_dirty(cx) {
1316                        Some((pane.downgrade(), item.boxed_clone()))
1317                    } else {
1318                        None
1319                    }
1320                })
1321            })
1322            .collect::<Vec<_>>();
1323
1324        let project = self.project.clone();
1325        cx.spawn(|workspace, mut cx| async move {
1326            // Override save mode and display "Save all files" prompt
1327            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1328                let answer = workspace.update(&mut cx, |_, cx| {
1329                    let (prompt, detail) = Pane::file_names_for_prompt(
1330                        &mut dirty_items.iter().map(|(_, handle)| handle),
1331                        dirty_items.len(),
1332                        cx,
1333                    );
1334                    cx.prompt(
1335                        PromptLevel::Warning,
1336                        &prompt,
1337                        Some(&detail),
1338                        &["Save all", "Discard all", "Cancel"],
1339                    )
1340                })?;
1341                match answer.await.log_err() {
1342                    Some(0) => save_intent = SaveIntent::SaveAll,
1343                    Some(1) => save_intent = SaveIntent::Skip,
1344                    _ => {}
1345                }
1346            }
1347            for (pane, item) in dirty_items {
1348                let (singleton, project_entry_ids) =
1349                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1350                if singleton || !project_entry_ids.is_empty() {
1351                    if let Some(ix) =
1352                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1353                    {
1354                        if !Pane::save_item(
1355                            project.clone(),
1356                            &pane,
1357                            ix,
1358                            &*item,
1359                            save_intent,
1360                            &mut cx,
1361                        )
1362                        .await?
1363                        {
1364                            return Ok(false);
1365                        }
1366                    }
1367                }
1368            }
1369            Ok(true)
1370        })
1371    }
1372
1373    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1374        self.client()
1375            .telemetry()
1376            .report_app_event("open project".to_string());
1377        let paths = cx.prompt_for_paths(PathPromptOptions {
1378            files: true,
1379            directories: true,
1380            multiple: true,
1381        });
1382
1383        cx.spawn(|this, mut cx| async move {
1384            let Some(paths) = paths.await.log_err().flatten() else {
1385                return;
1386            };
1387
1388            if let Some(task) = this
1389                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1390                .log_err()
1391            {
1392                task.await.log_err();
1393            }
1394        })
1395        .detach()
1396    }
1397
1398    pub fn open_workspace_for_paths(
1399        &mut self,
1400        paths: Vec<PathBuf>,
1401        cx: &mut ViewContext<Self>,
1402    ) -> Task<Result<()>> {
1403        let window = cx.window_handle().downcast::<Self>();
1404        let is_remote = self.project.read(cx).is_remote();
1405        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1406        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1407        let window_to_replace = if is_remote || has_worktree || has_dirty_items {
1408            None
1409        } else {
1410            window
1411        };
1412        let app_state = self.app_state.clone();
1413
1414        cx.spawn(|_, mut cx| async move {
1415            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1416                .await?;
1417            Ok(())
1418        })
1419    }
1420
1421    #[allow(clippy::type_complexity)]
1422    pub fn open_paths(
1423        &mut self,
1424        mut abs_paths: Vec<PathBuf>,
1425        visible: OpenVisible,
1426        pane: Option<WeakView<Pane>>,
1427        cx: &mut ViewContext<Self>,
1428    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1429        log::info!("open paths {abs_paths:?}");
1430
1431        let fs = self.app_state.fs.clone();
1432
1433        // Sort the paths to ensure we add worktrees for parents before their children.
1434        abs_paths.sort_unstable();
1435        cx.spawn(move |this, mut cx| async move {
1436            let mut tasks = Vec::with_capacity(abs_paths.len());
1437
1438            for abs_path in &abs_paths {
1439                let visible = match visible {
1440                    OpenVisible::All => Some(true),
1441                    OpenVisible::None => Some(false),
1442                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1443                        Some(Some(metadata)) => Some(!metadata.is_dir),
1444                        Some(None) => {
1445                            log::error!("No metadata for file {abs_path:?}");
1446                            None
1447                        }
1448                        None => None,
1449                    },
1450                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1451                        Some(Some(metadata)) => Some(metadata.is_dir),
1452                        Some(None) => {
1453                            log::error!("No metadata for file {abs_path:?}");
1454                            None
1455                        }
1456                        None => None,
1457                    },
1458                };
1459                let project_path = match visible {
1460                    Some(visible) => match this
1461                        .update(&mut cx, |this, cx| {
1462                            Workspace::project_path_for_path(
1463                                this.project.clone(),
1464                                abs_path,
1465                                visible,
1466                                cx,
1467                            )
1468                        })
1469                        .log_err()
1470                    {
1471                        Some(project_path) => project_path.await.log_err(),
1472                        None => None,
1473                    },
1474                    None => None,
1475                };
1476
1477                let this = this.clone();
1478                let abs_path = abs_path.clone();
1479                let fs = fs.clone();
1480                let pane = pane.clone();
1481                let task = cx.spawn(move |mut cx| async move {
1482                    let (worktree, project_path) = project_path?;
1483                    if fs.is_file(&abs_path).await {
1484                        Some(
1485                            this.update(&mut cx, |this, cx| {
1486                                this.open_path(project_path, pane, true, cx)
1487                            })
1488                            .log_err()?
1489                            .await,
1490                        )
1491                    } else {
1492                        this.update(&mut cx, |workspace, cx| {
1493                            let worktree = worktree.read(cx);
1494                            let worktree_abs_path = worktree.abs_path();
1495                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1496                                worktree.root_entry()
1497                            } else {
1498                                abs_path
1499                                    .strip_prefix(worktree_abs_path.as_ref())
1500                                    .ok()
1501                                    .and_then(|relative_path| {
1502                                        worktree.entry_for_path(relative_path)
1503                                    })
1504                            }
1505                            .map(|entry| entry.id);
1506                            if let Some(entry_id) = entry_id {
1507                                workspace.project.update(cx, |_, cx| {
1508                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1509                                })
1510                            }
1511                        })
1512                        .log_err()?;
1513                        None
1514                    }
1515                });
1516                tasks.push(task);
1517            }
1518
1519            futures::future::join_all(tasks).await
1520        })
1521    }
1522
1523    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1524        let paths = cx.prompt_for_paths(PathPromptOptions {
1525            files: false,
1526            directories: true,
1527            multiple: true,
1528        });
1529        cx.spawn(|this, mut cx| async move {
1530            if let Some(paths) = paths.await.log_err().flatten() {
1531                let results = this
1532                    .update(&mut cx, |this, cx| {
1533                        this.open_paths(paths, OpenVisible::All, None, cx)
1534                    })?
1535                    .await;
1536                for result in results.into_iter().flatten() {
1537                    result.log_err();
1538                }
1539            }
1540            anyhow::Ok(())
1541        })
1542        .detach_and_log_err(cx);
1543    }
1544
1545    fn project_path_for_path(
1546        project: Model<Project>,
1547        abs_path: &Path,
1548        visible: bool,
1549        cx: &mut AppContext,
1550    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1551        let entry = project.update(cx, |project, cx| {
1552            project.find_or_create_local_worktree(abs_path, visible, cx)
1553        });
1554        cx.spawn(|mut cx| async move {
1555            let (worktree, path) = entry.await?;
1556            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1557            Ok((
1558                worktree,
1559                ProjectPath {
1560                    worktree_id,
1561                    path: path.into(),
1562                },
1563            ))
1564        })
1565    }
1566
1567    pub fn items<'a>(
1568        &'a self,
1569        cx: &'a AppContext,
1570    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1571        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1572    }
1573
1574    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1575        self.items_of_type(cx).max_by_key(|item| item.item_id())
1576    }
1577
1578    pub fn items_of_type<'a, T: Item>(
1579        &'a self,
1580        cx: &'a AppContext,
1581    ) -> impl 'a + Iterator<Item = View<T>> {
1582        self.panes
1583            .iter()
1584            .flat_map(|pane| pane.read(cx).items_of_type())
1585    }
1586
1587    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1588        self.active_pane().read(cx).active_item()
1589    }
1590
1591    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1592        let item = self.active_item(cx)?;
1593        item.to_any().downcast::<I>().ok()
1594    }
1595
1596    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1597        self.active_item(cx).and_then(|item| item.project_path(cx))
1598    }
1599
1600    pub fn save_active_item(
1601        &mut self,
1602        save_intent: SaveIntent,
1603        cx: &mut WindowContext,
1604    ) -> Task<Result<()>> {
1605        let project = self.project.clone();
1606        let pane = self.active_pane();
1607        let item_ix = pane.read(cx).active_item_index();
1608        let item = pane.read(cx).active_item();
1609        let pane = pane.downgrade();
1610
1611        cx.spawn(|mut cx| async move {
1612            if let Some(item) = item {
1613                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1614                    .await
1615                    .map(|_| ())
1616            } else {
1617                Ok(())
1618            }
1619        })
1620    }
1621
1622    pub fn close_inactive_items_and_panes(
1623        &mut self,
1624        _: &CloseInactiveTabsAndPanes,
1625        cx: &mut ViewContext<Self>,
1626    ) {
1627        self.close_all_internal(true, SaveIntent::Close, cx)
1628            .map(|task| task.detach_and_log_err(cx));
1629    }
1630
1631    pub fn close_all_items_and_panes(
1632        &mut self,
1633        action: &CloseAllItemsAndPanes,
1634        cx: &mut ViewContext<Self>,
1635    ) {
1636        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1637            .map(|task| task.detach_and_log_err(cx));
1638    }
1639
1640    fn close_all_internal(
1641        &mut self,
1642        retain_active_pane: bool,
1643        save_intent: SaveIntent,
1644        cx: &mut ViewContext<Self>,
1645    ) -> Option<Task<Result<()>>> {
1646        let current_pane = self.active_pane();
1647
1648        let mut tasks = Vec::new();
1649
1650        if retain_active_pane {
1651            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1652                pane.close_inactive_items(&CloseInactiveItems, cx)
1653            }) {
1654                tasks.push(current_pane_close);
1655            };
1656        }
1657
1658        for pane in self.panes() {
1659            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1660                continue;
1661            }
1662
1663            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1664                pane.close_all_items(
1665                    &CloseAllItems {
1666                        save_intent: Some(save_intent),
1667                    },
1668                    cx,
1669                )
1670            }) {
1671                tasks.push(close_pane_items)
1672            }
1673        }
1674
1675        if tasks.is_empty() {
1676            None
1677        } else {
1678            Some(cx.spawn(|_, _| async move {
1679                for task in tasks {
1680                    task.await?
1681                }
1682                Ok(())
1683            }))
1684        }
1685    }
1686
1687    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1688        let dock = match dock_side {
1689            DockPosition::Left => &self.left_dock,
1690            DockPosition::Bottom => &self.bottom_dock,
1691            DockPosition::Right => &self.right_dock,
1692        };
1693        let mut focus_center = false;
1694        let mut reveal_dock = false;
1695        dock.update(cx, |dock, cx| {
1696            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1697            let was_visible = dock.is_open() && !other_is_zoomed;
1698            dock.set_open(!was_visible, cx);
1699
1700            if let Some(active_panel) = dock.active_panel() {
1701                if was_visible {
1702                    if active_panel.focus_handle(cx).contains_focused(cx) {
1703                        focus_center = true;
1704                    }
1705                } else {
1706                    let focus_handle = &active_panel.focus_handle(cx);
1707                    cx.focus(focus_handle);
1708                    reveal_dock = true;
1709                }
1710            }
1711        });
1712
1713        if reveal_dock {
1714            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1715        }
1716
1717        if focus_center {
1718            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1719        }
1720
1721        cx.notify();
1722        self.serialize_workspace(cx);
1723    }
1724
1725    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1726        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1727
1728        for dock in docks {
1729            dock.update(cx, |dock, cx| {
1730                dock.set_open(false, cx);
1731            });
1732        }
1733
1734        cx.focus_self();
1735        cx.notify();
1736        self.serialize_workspace(cx);
1737    }
1738
1739    /// Transfer focus to the panel of the given type.
1740    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1741        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1742        panel.to_any().downcast().ok()
1743    }
1744
1745    /// Focus the panel of the given type if it isn't already focused. If it is
1746    /// already focused, then transfer focus back to the workspace center.
1747    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1748        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1749            !panel.focus_handle(cx).contains_focused(cx)
1750        });
1751    }
1752
1753    /// Focus or unfocus the given panel type, depending on the given callback.
1754    fn focus_or_unfocus_panel<T: Panel>(
1755        &mut self,
1756        cx: &mut ViewContext<Self>,
1757        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1758    ) -> Option<Arc<dyn PanelHandle>> {
1759        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1760            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1761                let mut focus_center = false;
1762                let panel = dock.update(cx, |dock, cx| {
1763                    dock.activate_panel(panel_index, cx);
1764
1765                    let panel = dock.active_panel().cloned();
1766                    if let Some(panel) = panel.as_ref() {
1767                        if should_focus(&**panel, cx) {
1768                            dock.set_open(true, cx);
1769                            panel.focus_handle(cx).focus(cx);
1770                        } else {
1771                            focus_center = true;
1772                        }
1773                    }
1774                    panel
1775                });
1776
1777                if focus_center {
1778                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1779                }
1780
1781                self.serialize_workspace(cx);
1782                cx.notify();
1783                return panel;
1784            }
1785        }
1786        None
1787    }
1788
1789    /// Open the panel of the given type
1790    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1791        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1792            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1793                dock.update(cx, |dock, cx| {
1794                    dock.activate_panel(panel_index, cx);
1795                    dock.set_open(true, cx);
1796                });
1797            }
1798        }
1799    }
1800
1801    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1802        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1803            let dock = dock.read(cx);
1804            if let Some(panel) = dock.panel::<T>() {
1805                return Some(panel);
1806            }
1807        }
1808        None
1809    }
1810
1811    fn dismiss_zoomed_items_to_reveal(
1812        &mut self,
1813        dock_to_reveal: Option<DockPosition>,
1814        cx: &mut ViewContext<Self>,
1815    ) {
1816        // If a center pane is zoomed, unzoom it.
1817        for pane in &self.panes {
1818            if pane != &self.active_pane || dock_to_reveal.is_some() {
1819                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1820            }
1821        }
1822
1823        // If another dock is zoomed, hide it.
1824        let mut focus_center = false;
1825        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1826            dock.update(cx, |dock, cx| {
1827                if Some(dock.position()) != dock_to_reveal {
1828                    if let Some(panel) = dock.active_panel() {
1829                        if panel.is_zoomed(cx) {
1830                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
1831                            dock.set_open(false, cx);
1832                        }
1833                    }
1834                }
1835            });
1836        }
1837
1838        if focus_center {
1839            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1840        }
1841
1842        if self.zoomed_position != dock_to_reveal {
1843            self.zoomed = None;
1844            self.zoomed_position = None;
1845        }
1846
1847        cx.notify();
1848    }
1849
1850    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1851        let pane = cx.new_view(|cx| {
1852            Pane::new(
1853                self.weak_handle(),
1854                self.project.clone(),
1855                self.pane_history_timestamp.clone(),
1856                None,
1857                cx,
1858            )
1859        });
1860        cx.subscribe(&pane, Self::handle_pane_event).detach();
1861        self.panes.push(pane.clone());
1862        cx.focus_view(&pane);
1863        cx.emit(Event::PaneAdded(pane.clone()));
1864        pane
1865    }
1866
1867    pub fn add_item_to_center(
1868        &mut self,
1869        item: Box<dyn ItemHandle>,
1870        cx: &mut ViewContext<Self>,
1871    ) -> bool {
1872        if let Some(center_pane) = self.last_active_center_pane.clone() {
1873            if let Some(center_pane) = center_pane.upgrade() {
1874                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1875                true
1876            } else {
1877                false
1878            }
1879        } else {
1880            false
1881        }
1882    }
1883
1884    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
1885        if let Some(text) = item.telemetry_event_text(cx) {
1886            self.client()
1887                .telemetry()
1888                .report_app_event(format!("{}: open", text));
1889        }
1890
1891        self.active_pane
1892            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1893    }
1894
1895    pub fn split_item(
1896        &mut self,
1897        split_direction: SplitDirection,
1898        item: Box<dyn ItemHandle>,
1899        cx: &mut ViewContext<Self>,
1900    ) {
1901        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1902        new_pane.update(cx, move |new_pane, cx| {
1903            new_pane.add_item(item, true, true, None, cx)
1904        })
1905    }
1906
1907    pub fn open_abs_path(
1908        &mut self,
1909        abs_path: PathBuf,
1910        visible: bool,
1911        cx: &mut ViewContext<Self>,
1912    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1913        cx.spawn(|workspace, mut cx| async move {
1914            let open_paths_task_result = workspace
1915                .update(&mut cx, |workspace, cx| {
1916                    workspace.open_paths(
1917                        vec![abs_path.clone()],
1918                        if visible {
1919                            OpenVisible::All
1920                        } else {
1921                            OpenVisible::None
1922                        },
1923                        None,
1924                        cx,
1925                    )
1926                })
1927                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1928                .await;
1929            anyhow::ensure!(
1930                open_paths_task_result.len() == 1,
1931                "open abs path {abs_path:?} task returned incorrect number of results"
1932            );
1933            match open_paths_task_result
1934                .into_iter()
1935                .next()
1936                .expect("ensured single task result")
1937            {
1938                Some(open_result) => {
1939                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1940                }
1941                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1942            }
1943        })
1944    }
1945
1946    pub fn split_abs_path(
1947        &mut self,
1948        abs_path: PathBuf,
1949        visible: bool,
1950        cx: &mut ViewContext<Self>,
1951    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1952        let project_path_task =
1953            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1954        cx.spawn(|this, mut cx| async move {
1955            let (_, path) = project_path_task.await?;
1956            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1957                .await
1958        })
1959    }
1960
1961    pub fn open_path(
1962        &mut self,
1963        path: impl Into<ProjectPath>,
1964        pane: Option<WeakView<Pane>>,
1965        focus_item: bool,
1966        cx: &mut WindowContext,
1967    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1968        let pane = pane.unwrap_or_else(|| {
1969            self.last_active_center_pane.clone().unwrap_or_else(|| {
1970                self.panes
1971                    .first()
1972                    .expect("There must be an active pane")
1973                    .downgrade()
1974            })
1975        });
1976
1977        let task = self.load_path(path.into(), cx);
1978        cx.spawn(move |mut cx| async move {
1979            let (project_entry_id, build_item) = task.await?;
1980            pane.update(&mut cx, |pane, cx| {
1981                pane.open_item(project_entry_id, focus_item, cx, build_item)
1982            })
1983        })
1984    }
1985
1986    pub fn split_path(
1987        &mut self,
1988        path: impl Into<ProjectPath>,
1989        cx: &mut ViewContext<Self>,
1990    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1991        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1992            self.panes
1993                .first()
1994                .expect("There must be an active pane")
1995                .downgrade()
1996        });
1997
1998        if let Member::Pane(center_pane) = &self.center.root {
1999            if center_pane.read(cx).items_len() == 0 {
2000                return self.open_path(path, Some(pane), true, cx);
2001            }
2002        }
2003
2004        let task = self.load_path(path.into(), cx);
2005        cx.spawn(|this, mut cx| async move {
2006            let (project_entry_id, build_item) = task.await?;
2007            this.update(&mut cx, move |this, cx| -> Option<_> {
2008                let pane = pane.upgrade()?;
2009                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2010                new_pane.update(cx, |new_pane, cx| {
2011                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2012                })
2013            })
2014            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2015        })
2016    }
2017
2018    fn load_path(
2019        &mut self,
2020        path: ProjectPath,
2021        cx: &mut WindowContext,
2022    ) -> Task<
2023        Result<(
2024            Option<ProjectEntryId>,
2025            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2026        )>,
2027    > {
2028        let project = self.project().clone();
2029        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2030        cx.spawn(|mut cx| async move {
2031            let (project_entry_id, project_item) = project_item.await?;
2032            let build_item = cx.update(|cx| {
2033                cx.default_global::<ProjectItemBuilders>()
2034                    .get(&project_item.entity_type())
2035                    .ok_or_else(|| anyhow!("no item builder for project item"))
2036                    .cloned()
2037            })??;
2038            let build_item =
2039                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2040            Ok((project_entry_id, build_item))
2041        })
2042    }
2043
2044    pub fn open_project_item<T>(
2045        &mut self,
2046        project_item: Model<T::Item>,
2047        cx: &mut ViewContext<Self>,
2048    ) -> View<T>
2049    where
2050        T: ProjectItem,
2051    {
2052        use project::Item as _;
2053
2054        let entry_id = project_item.read(cx).entry_id(cx);
2055        if let Some(item) = entry_id
2056            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2057            .and_then(|item| item.downcast())
2058        {
2059            self.activate_item(&item, cx);
2060            return item;
2061        }
2062
2063        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2064        self.add_item(Box::new(item.clone()), cx);
2065        item
2066    }
2067
2068    pub fn split_project_item<T>(
2069        &mut self,
2070        project_item: Model<T::Item>,
2071        cx: &mut ViewContext<Self>,
2072    ) -> View<T>
2073    where
2074        T: ProjectItem,
2075    {
2076        use project::Item as _;
2077
2078        let entry_id = project_item.read(cx).entry_id(cx);
2079        if let Some(item) = entry_id
2080            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2081            .and_then(|item| item.downcast())
2082        {
2083            self.activate_item(&item, cx);
2084            return item;
2085        }
2086
2087        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2088        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2089        item
2090    }
2091
2092    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2093        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2094            self.active_pane.update(cx, |pane, cx| {
2095                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2096            });
2097        }
2098    }
2099
2100    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2101        let result = self.panes.iter().find_map(|pane| {
2102            pane.read(cx)
2103                .index_for_item(item)
2104                .map(|ix| (pane.clone(), ix))
2105        });
2106        if let Some((pane, ix)) = result {
2107            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2108            true
2109        } else {
2110            false
2111        }
2112    }
2113
2114    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2115        let panes = self.center.panes();
2116        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2117            cx.focus_view(&pane);
2118        } else {
2119            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2120        }
2121    }
2122
2123    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2124        let panes = self.center.panes();
2125        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2126            let next_ix = (ix + 1) % panes.len();
2127            let next_pane = panes[next_ix].clone();
2128            cx.focus_view(&next_pane);
2129        }
2130    }
2131
2132    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2133        let panes = self.center.panes();
2134        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2135            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2136            let prev_pane = panes[prev_ix].clone();
2137            cx.focus_view(&prev_pane);
2138        }
2139    }
2140
2141    pub fn activate_pane_in_direction(
2142        &mut self,
2143        direction: SplitDirection,
2144        cx: &mut WindowContext,
2145    ) {
2146        use ActivateInDirectionTarget as Target;
2147        enum Origin {
2148            LeftDock,
2149            RightDock,
2150            BottomDock,
2151            Center,
2152        }
2153
2154        let origin: Origin = [
2155            (&self.left_dock, Origin::LeftDock),
2156            (&self.right_dock, Origin::RightDock),
2157            (&self.bottom_dock, Origin::BottomDock),
2158        ]
2159        .into_iter()
2160        .find_map(|(dock, origin)| {
2161            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2162                Some(origin)
2163            } else {
2164                None
2165            }
2166        })
2167        .unwrap_or(Origin::Center);
2168
2169        let get_last_active_pane = || {
2170            self.last_active_center_pane.as_ref().and_then(|p| {
2171                let p = p.upgrade()?;
2172                (p.read(cx).items_len() != 0).then_some(p)
2173            })
2174        };
2175
2176        let try_dock =
2177            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2178
2179        let target = match (origin, direction) {
2180            // We're in the center, so we first try to go to a different pane,
2181            // otherwise try to go to a dock.
2182            (Origin::Center, direction) => {
2183                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2184                    Some(Target::Pane(pane))
2185                } else {
2186                    match direction {
2187                        SplitDirection::Up => None,
2188                        SplitDirection::Down => try_dock(&self.bottom_dock),
2189                        SplitDirection::Left => try_dock(&self.left_dock),
2190                        SplitDirection::Right => try_dock(&self.right_dock),
2191                    }
2192                }
2193            }
2194
2195            (Origin::LeftDock, SplitDirection::Right) => {
2196                if let Some(last_active_pane) = get_last_active_pane() {
2197                    Some(Target::Pane(last_active_pane))
2198                } else {
2199                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2200                }
2201            }
2202
2203            (Origin::LeftDock, SplitDirection::Down)
2204            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2205
2206            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2207            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2208            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2209
2210            (Origin::RightDock, SplitDirection::Left) => {
2211                if let Some(last_active_pane) = get_last_active_pane() {
2212                    Some(Target::Pane(last_active_pane))
2213                } else {
2214                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2215                }
2216            }
2217
2218            _ => None,
2219        };
2220
2221        match target {
2222            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2223            Some(ActivateInDirectionTarget::Dock(dock)) => {
2224                if let Some(panel) = dock.read(cx).active_panel() {
2225                    panel.focus_handle(cx).focus(cx);
2226                } else {
2227                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2228                }
2229            }
2230            None => {}
2231        }
2232    }
2233
2234    fn find_pane_in_direction(
2235        &mut self,
2236        direction: SplitDirection,
2237        cx: &WindowContext,
2238    ) -> Option<View<Pane>> {
2239        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2240            return None;
2241        };
2242        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2243        let center = match cursor {
2244            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2245            _ => bounding_box.center(),
2246        };
2247
2248        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2249
2250        let target = match direction {
2251            SplitDirection::Left => {
2252                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2253            }
2254            SplitDirection::Right => {
2255                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2256            }
2257            SplitDirection::Up => {
2258                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2259            }
2260            SplitDirection::Down => {
2261                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2262            }
2263        };
2264        self.center.pane_at_pixel_position(target).cloned()
2265    }
2266
2267    pub fn swap_pane_in_direction(
2268        &mut self,
2269        direction: SplitDirection,
2270        cx: &mut ViewContext<Self>,
2271    ) {
2272        if let Some(to) = self
2273            .find_pane_in_direction(direction, cx)
2274            .map(|pane| pane.clone())
2275        {
2276            self.center.swap(&self.active_pane.clone(), &to);
2277            cx.notify();
2278        }
2279    }
2280
2281    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2282        if self.active_pane != pane {
2283            self.active_pane = pane.clone();
2284            self.status_bar.update(cx, |status_bar, cx| {
2285                status_bar.set_active_pane(&self.active_pane, cx);
2286            });
2287            self.active_item_path_changed(cx);
2288            self.last_active_center_pane = Some(pane.downgrade());
2289        }
2290
2291        self.dismiss_zoomed_items_to_reveal(None, cx);
2292        if pane.read(cx).is_zoomed() {
2293            self.zoomed = Some(pane.downgrade().into());
2294        } else {
2295            self.zoomed = None;
2296        }
2297        self.zoomed_position = None;
2298        self.update_active_view_for_followers(cx);
2299
2300        cx.notify();
2301    }
2302
2303    fn handle_pane_event(
2304        &mut self,
2305        pane: View<Pane>,
2306        event: &pane::Event,
2307        cx: &mut ViewContext<Self>,
2308    ) {
2309        match event {
2310            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2311            pane::Event::Split(direction) => {
2312                self.split_and_clone(pane, *direction, cx);
2313            }
2314            pane::Event::Remove => self.remove_pane(pane, cx),
2315            pane::Event::ActivateItem { local } => {
2316                if *local {
2317                    self.unfollow(&pane, cx);
2318                }
2319                if &pane == self.active_pane() {
2320                    self.active_item_path_changed(cx);
2321                    self.update_active_view_for_followers(cx);
2322                }
2323            }
2324            pane::Event::ChangeItemTitle => {
2325                if pane == self.active_pane {
2326                    self.active_item_path_changed(cx);
2327                }
2328                self.update_window_edited(cx);
2329            }
2330            pane::Event::RemoveItem { item_id } => {
2331                self.update_window_edited(cx);
2332                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2333                    if entry.get().entity_id() == pane.entity_id() {
2334                        entry.remove();
2335                    }
2336                }
2337            }
2338            pane::Event::Focus => {
2339                self.handle_pane_focused(pane.clone(), cx);
2340            }
2341            pane::Event::ZoomIn => {
2342                if pane == self.active_pane {
2343                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2344                    if pane.read(cx).has_focus(cx) {
2345                        self.zoomed = Some(pane.downgrade().into());
2346                        self.zoomed_position = None;
2347                    }
2348                    cx.notify();
2349                }
2350            }
2351            pane::Event::ZoomOut => {
2352                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2353                if self.zoomed_position.is_none() {
2354                    self.zoomed = None;
2355                }
2356                cx.notify();
2357            }
2358        }
2359
2360        self.serialize_workspace(cx);
2361    }
2362
2363    pub fn split_pane(
2364        &mut self,
2365        pane_to_split: View<Pane>,
2366        split_direction: SplitDirection,
2367        cx: &mut ViewContext<Self>,
2368    ) -> View<Pane> {
2369        let new_pane = self.add_pane(cx);
2370        self.center
2371            .split(&pane_to_split, &new_pane, split_direction)
2372            .unwrap();
2373        cx.notify();
2374        new_pane
2375    }
2376
2377    pub fn split_and_clone(
2378        &mut self,
2379        pane: View<Pane>,
2380        direction: SplitDirection,
2381        cx: &mut ViewContext<Self>,
2382    ) -> Option<View<Pane>> {
2383        let item = pane.read(cx).active_item()?;
2384        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2385            let new_pane = self.add_pane(cx);
2386            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2387            self.center.split(&pane, &new_pane, direction).unwrap();
2388            Some(new_pane)
2389        } else {
2390            None
2391        };
2392        cx.notify();
2393        maybe_pane_handle
2394    }
2395
2396    pub fn split_pane_with_item(
2397        &mut self,
2398        pane_to_split: WeakView<Pane>,
2399        split_direction: SplitDirection,
2400        from: WeakView<Pane>,
2401        item_id_to_move: EntityId,
2402        cx: &mut ViewContext<Self>,
2403    ) {
2404        let Some(pane_to_split) = pane_to_split.upgrade() else {
2405            return;
2406        };
2407        let Some(from) = from.upgrade() else {
2408            return;
2409        };
2410
2411        let new_pane = self.add_pane(cx);
2412        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2413        self.center
2414            .split(&pane_to_split, &new_pane, split_direction)
2415            .unwrap();
2416        cx.notify();
2417    }
2418
2419    pub fn split_pane_with_project_entry(
2420        &mut self,
2421        pane_to_split: WeakView<Pane>,
2422        split_direction: SplitDirection,
2423        project_entry: ProjectEntryId,
2424        cx: &mut ViewContext<Self>,
2425    ) -> Option<Task<Result<()>>> {
2426        let pane_to_split = pane_to_split.upgrade()?;
2427        let new_pane = self.add_pane(cx);
2428        self.center
2429            .split(&pane_to_split, &new_pane, split_direction)
2430            .unwrap();
2431
2432        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2433        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2434        Some(cx.foreground_executor().spawn(async move {
2435            task.await?;
2436            Ok(())
2437        }))
2438    }
2439
2440    pub fn move_item(
2441        &mut self,
2442        source: View<Pane>,
2443        destination: View<Pane>,
2444        item_id_to_move: EntityId,
2445        destination_index: usize,
2446        cx: &mut ViewContext<Self>,
2447    ) {
2448        let Some((item_ix, item_handle)) = source
2449            .read(cx)
2450            .items()
2451            .enumerate()
2452            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2453        else {
2454            // Tab was closed during drag
2455            return;
2456        };
2457
2458        let item_handle = item_handle.clone();
2459
2460        if source != destination {
2461            // Close item from previous pane
2462            source.update(cx, |source, cx| {
2463                source.remove_item(item_ix, false, cx);
2464            });
2465        }
2466
2467        // This automatically removes duplicate items in the pane
2468        destination.update(cx, |destination, cx| {
2469            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2470            destination.focus(cx)
2471        });
2472    }
2473
2474    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2475        if self.center.remove(&pane).unwrap() {
2476            self.force_remove_pane(&pane, cx);
2477            self.unfollow(&pane, cx);
2478            self.last_leaders_by_pane.remove(&pane.downgrade());
2479            for removed_item in pane.read(cx).items() {
2480                self.panes_by_item.remove(&removed_item.item_id());
2481            }
2482
2483            cx.notify();
2484        } else {
2485            self.active_item_path_changed(cx);
2486        }
2487    }
2488
2489    pub fn panes(&self) -> &[View<Pane>] {
2490        &self.panes
2491    }
2492
2493    pub fn active_pane(&self) -> &View<Pane> {
2494        &self.active_pane
2495    }
2496
2497    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2498        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2499        weak_pane.upgrade()
2500    }
2501
2502    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2503        self.follower_states.retain(|_, state| {
2504            if state.leader_id == peer_id {
2505                for item in state.items_by_leader_view_id.values() {
2506                    item.set_leader_peer_id(None, cx);
2507                }
2508                false
2509            } else {
2510                true
2511            }
2512        });
2513        cx.notify();
2514    }
2515
2516    pub fn start_following(
2517        &mut self,
2518        leader_id: PeerId,
2519        cx: &mut ViewContext<Self>,
2520    ) -> Option<Task<Result<()>>> {
2521        let pane = self.active_pane().clone();
2522
2523        self.last_leaders_by_pane
2524            .insert(pane.downgrade(), leader_id);
2525        self.unfollow(&pane, cx);
2526        self.follower_states.insert(
2527            pane.clone(),
2528            FollowerState {
2529                leader_id,
2530                active_view_id: None,
2531                items_by_leader_view_id: Default::default(),
2532            },
2533        );
2534        cx.notify();
2535
2536        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2537        let project_id = self.project.read(cx).remote_id();
2538        let request = self.app_state.client.request(proto::Follow {
2539            room_id,
2540            project_id,
2541            leader_id: Some(leader_id),
2542        });
2543
2544        Some(cx.spawn(|this, mut cx| async move {
2545            let response = request.await?;
2546            this.update(&mut cx, |this, _| {
2547                let state = this
2548                    .follower_states
2549                    .get_mut(&pane)
2550                    .ok_or_else(|| anyhow!("following interrupted"))?;
2551                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2552                    Some(ViewId::from_proto(active_view_id)?)
2553                } else {
2554                    None
2555                };
2556                Ok::<_, anyhow::Error>(())
2557            })??;
2558            if let Some(view) = response.active_view {
2559                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2560                    .await?;
2561            }
2562            Self::add_views_from_leader(
2563                this.clone(),
2564                leader_id,
2565                vec![pane],
2566                response.views,
2567                &mut cx,
2568            )
2569            .await?;
2570            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2571            Ok(())
2572        }))
2573    }
2574
2575    pub fn follow_next_collaborator(
2576        &mut self,
2577        _: &FollowNextCollaborator,
2578        cx: &mut ViewContext<Self>,
2579    ) {
2580        let collaborators = self.project.read(cx).collaborators();
2581        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2582            let mut collaborators = collaborators.keys().copied();
2583            for peer_id in collaborators.by_ref() {
2584                if peer_id == leader_id {
2585                    break;
2586                }
2587            }
2588            collaborators.next()
2589        } else if let Some(last_leader_id) =
2590            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2591        {
2592            if collaborators.contains_key(last_leader_id) {
2593                Some(*last_leader_id)
2594            } else {
2595                None
2596            }
2597        } else {
2598            None
2599        };
2600
2601        let pane = self.active_pane.clone();
2602        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2603        else {
2604            return;
2605        };
2606        if Some(leader_id) == self.unfollow(&pane, cx) {
2607            return;
2608        }
2609        self.start_following(leader_id, cx)
2610            .map(|task| task.detach_and_log_err(cx));
2611    }
2612
2613    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2614        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2615            return;
2616        };
2617        let room = room.read(cx);
2618        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2619            return;
2620        };
2621
2622        let project = self.project.read(cx);
2623
2624        let other_project_id = match remote_participant.location {
2625            call::ParticipantLocation::External => None,
2626            call::ParticipantLocation::UnsharedProject => None,
2627            call::ParticipantLocation::SharedProject { project_id } => {
2628                if Some(project_id) == project.remote_id() {
2629                    None
2630                } else {
2631                    Some(project_id)
2632                }
2633            }
2634        };
2635
2636        // if they are active in another project, follow there.
2637        if let Some(project_id) = other_project_id {
2638            let app_state = self.app_state.clone();
2639            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2640                .detach_and_log_err(cx);
2641        }
2642
2643        // if you're already following, find the right pane and focus it.
2644        for (pane, state) in &self.follower_states {
2645            if leader_id == state.leader_id {
2646                cx.focus_view(pane);
2647                return;
2648            }
2649        }
2650
2651        // Otherwise, follow.
2652        self.start_following(leader_id, cx)
2653            .map(|task| task.detach_and_log_err(cx));
2654    }
2655
2656    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2657        let state = self.follower_states.remove(pane)?;
2658        let leader_id = state.leader_id;
2659        for (_, item) in state.items_by_leader_view_id {
2660            item.set_leader_peer_id(None, cx);
2661        }
2662
2663        if self
2664            .follower_states
2665            .values()
2666            .all(|state| state.leader_id != state.leader_id)
2667        {
2668            let project_id = self.project.read(cx).remote_id();
2669            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2670            self.app_state
2671                .client
2672                .send(proto::Unfollow {
2673                    room_id,
2674                    project_id,
2675                    leader_id: Some(leader_id),
2676                })
2677                .log_err();
2678        }
2679
2680        cx.notify();
2681        Some(leader_id)
2682    }
2683
2684    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2685        self.follower_states
2686            .values()
2687            .any(|state| state.leader_id == peer_id)
2688    }
2689
2690    fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
2691        let active_entry = self.active_project_path(cx);
2692        self.project
2693            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2694        self.update_window_title(cx);
2695    }
2696
2697    fn update_window_title(&mut self, cx: &mut WindowContext) {
2698        let project = self.project().read(cx);
2699        let mut title = String::new();
2700
2701        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2702            let filename = path
2703                .path
2704                .file_name()
2705                .map(|s| s.to_string_lossy())
2706                .or_else(|| {
2707                    Some(Cow::Borrowed(
2708                        project
2709                            .worktree_for_id(path.worktree_id, cx)?
2710                            .read(cx)
2711                            .root_name(),
2712                    ))
2713                });
2714
2715            if let Some(filename) = filename {
2716                title.push_str(filename.as_ref());
2717                title.push_str("");
2718            }
2719        }
2720
2721        for (i, name) in project.worktree_root_names(cx).enumerate() {
2722            if i > 0 {
2723                title.push_str(", ");
2724            }
2725            title.push_str(name);
2726        }
2727
2728        if title.is_empty() {
2729            title = "empty project".to_string();
2730        }
2731
2732        if project.is_remote() {
2733            title.push_str("");
2734        } else if project.is_shared() {
2735            title.push_str("");
2736        }
2737
2738        cx.set_window_title(&title);
2739    }
2740
2741    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2742        let is_edited = !self.project.read(cx).is_disconnected()
2743            && self
2744                .items(cx)
2745                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2746        if is_edited != self.window_edited {
2747            self.window_edited = is_edited;
2748            cx.set_window_edited(self.window_edited)
2749        }
2750    }
2751
2752    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2753        if self.notifications.is_empty() {
2754            None
2755        } else {
2756            Some(
2757                div()
2758                    .absolute()
2759                    .z_index(100)
2760                    .right_3()
2761                    .bottom_3()
2762                    .w_96()
2763                    .h_full()
2764                    .flex()
2765                    .flex_col()
2766                    .justify_end()
2767                    .gap_2()
2768                    .children(
2769                        self.notifications
2770                            .iter()
2771                            .map(|(_, _, notification)| notification.to_any()),
2772                    ),
2773            )
2774        }
2775    }
2776
2777    // RPC handlers
2778
2779    fn active_view_for_follower(&self, cx: &mut ViewContext<Self>) -> Option<proto::View> {
2780        let item = self.active_item(cx)?;
2781        let leader_id = self
2782            .pane_for(&*item)
2783            .and_then(|pane| self.leader_for_pane(&pane));
2784
2785        let item_handle = item.to_followable_item_handle(cx)?;
2786        let id = item_handle.remote_id(&self.app_state.client, cx)?;
2787        let variant = item_handle.to_state_proto(cx)?;
2788
2789        Some(proto::View {
2790            id: Some(id.to_proto()),
2791            leader_id,
2792            variant: Some(variant),
2793        })
2794    }
2795
2796    fn handle_follow(
2797        &mut self,
2798        follower_project_id: Option<u64>,
2799        cx: &mut ViewContext<Self>,
2800    ) -> proto::FollowResponse {
2801        let client = &self.app_state.client;
2802        let project_id = self.project.read(cx).remote_id();
2803
2804        let active_view = self.active_view_for_follower(cx);
2805        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
2806
2807        cx.notify();
2808
2809        proto::FollowResponse {
2810            active_view,
2811            // TODO: once v0.124.0 is retired we can stop sending these
2812            active_view_id,
2813            views: self
2814                .panes()
2815                .iter()
2816                .flat_map(|pane| {
2817                    let leader_id = self.leader_for_pane(pane);
2818                    pane.read(cx).items().filter_map({
2819                        let cx = &cx;
2820                        move |item| {
2821                            let item = item.to_followable_item_handle(cx)?;
2822
2823                            // If the item belongs to a particular project, then it should
2824                            // only be included if this project is shared, and the follower
2825                            // is in the project.
2826                            //
2827                            // Some items, like channel notes, do not belong to a particular
2828                            // project, so they should be included regardless of whether the
2829                            // current project is shared, or what project the follower is in.
2830                            if item.is_project_item(cx)
2831                                && (project_id.is_none() || project_id != follower_project_id)
2832                            {
2833                                return None;
2834                            }
2835
2836                            let id = item.remote_id(client, cx)?.to_proto();
2837                            let variant = item.to_state_proto(cx)?;
2838                            Some(proto::View {
2839                                id: Some(id),
2840                                leader_id,
2841                                variant: Some(variant),
2842                            })
2843                        }
2844                    })
2845                })
2846                .collect(),
2847        }
2848    }
2849
2850    fn handle_update_followers(
2851        &mut self,
2852        leader_id: PeerId,
2853        message: proto::UpdateFollowers,
2854        _cx: &mut ViewContext<Self>,
2855    ) {
2856        self.leader_updates_tx
2857            .unbounded_send((leader_id, message))
2858            .ok();
2859    }
2860
2861    async fn process_leader_update(
2862        this: &WeakView<Self>,
2863        leader_id: PeerId,
2864        update: proto::UpdateFollowers,
2865        cx: &mut AsyncWindowContext,
2866    ) -> Result<()> {
2867        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2868            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2869                let panes_missing_view = this.update(cx, |this, _| {
2870                    let mut panes = vec![];
2871                    for (pane, state) in &mut this.follower_states {
2872                        if state.leader_id != leader_id {
2873                            continue;
2874                        }
2875
2876                        state.active_view_id =
2877                            if let Some(active_view_id) = update_active_view.id.clone() {
2878                                Some(ViewId::from_proto(active_view_id)?)
2879                            } else {
2880                                None
2881                            };
2882
2883                        if state.active_view_id.is_some_and(|view_id| {
2884                            !state.items_by_leader_view_id.contains_key(&view_id)
2885                        }) {
2886                            panes.push(pane.clone())
2887                        }
2888                    }
2889                    anyhow::Ok(panes)
2890                })??;
2891
2892                if let Some(view) = update_active_view.view {
2893                    for pane in panes_missing_view {
2894                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
2895                            .await?
2896                    }
2897                }
2898            }
2899            proto::update_followers::Variant::UpdateView(update_view) => {
2900                let variant = update_view
2901                    .variant
2902                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2903                let id = update_view
2904                    .id
2905                    .ok_or_else(|| anyhow!("missing update view id"))?;
2906                let mut tasks = Vec::new();
2907                this.update(cx, |this, cx| {
2908                    let project = this.project.clone();
2909                    for (_, state) in &mut this.follower_states {
2910                        if state.leader_id == leader_id {
2911                            let view_id = ViewId::from_proto(id.clone())?;
2912                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2913                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2914                            }
2915                        }
2916                    }
2917                    anyhow::Ok(())
2918                })??;
2919                try_join_all(tasks).await.log_err();
2920            }
2921            proto::update_followers::Variant::CreateView(view) => {
2922                let panes = this.update(cx, |this, _| {
2923                    this.follower_states
2924                        .iter()
2925                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2926                        .cloned()
2927                        .collect()
2928                })?;
2929                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2930            }
2931        }
2932        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2933        Ok(())
2934    }
2935
2936    async fn add_view_from_leader(
2937        this: WeakView<Self>,
2938        leader_id: PeerId,
2939        pane: View<Pane>,
2940        view: &proto::View,
2941        cx: &mut AsyncWindowContext,
2942    ) -> Result<()> {
2943        let this = this.upgrade().context("workspace dropped")?;
2944
2945        let item_builders = cx.update(|cx| {
2946            cx.default_global::<FollowableItemBuilders>()
2947                .values()
2948                .map(|b| b.0)
2949                .collect::<Vec<_>>()
2950        })?;
2951
2952        let Some(id) = view.id.clone() else {
2953            return Err(anyhow!("no id for view")).into();
2954        };
2955        let id = ViewId::from_proto(id)?;
2956
2957        let mut variant = view.variant.clone();
2958        if variant.is_none() {
2959            Err(anyhow!("missing view variant"))?;
2960        }
2961
2962        let task = item_builders.iter().find_map(|build_item| {
2963            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
2964                .log_err()
2965                .flatten()
2966        });
2967        let Some(task) = task else {
2968            return Err(anyhow!(
2969                "failed to construct view from leader (maybe from a different version of zed?)"
2970            ));
2971        };
2972
2973        let item = task.await?;
2974
2975        this.update(cx, |this, cx| {
2976            let state = this.follower_states.get_mut(&pane)?;
2977            item.set_leader_peer_id(Some(leader_id), cx);
2978            state.items_by_leader_view_id.insert(id, item);
2979
2980            Some(())
2981        })?;
2982
2983        Ok(())
2984    }
2985
2986    async fn add_views_from_leader(
2987        this: WeakView<Self>,
2988        leader_id: PeerId,
2989        panes: Vec<View<Pane>>,
2990        views: Vec<proto::View>,
2991        cx: &mut AsyncWindowContext,
2992    ) -> Result<()> {
2993        let this = this.upgrade().context("workspace dropped")?;
2994
2995        let item_builders = cx.update(|cx| {
2996            cx.default_global::<FollowableItemBuilders>()
2997                .values()
2998                .map(|b| b.0)
2999                .collect::<Vec<_>>()
3000        })?;
3001
3002        let mut item_tasks_by_pane = HashMap::default();
3003        for pane in panes {
3004            let mut item_tasks = Vec::new();
3005            let mut leader_view_ids = Vec::new();
3006            for view in &views {
3007                let Some(id) = &view.id else {
3008                    continue;
3009                };
3010                let id = ViewId::from_proto(id.clone())?;
3011                let mut variant = view.variant.clone();
3012                if variant.is_none() {
3013                    Err(anyhow!("missing view variant"))?;
3014                }
3015                for build_item in &item_builders {
3016                    let task = cx.update(|cx| {
3017                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3018                    })?;
3019                    if let Some(task) = task {
3020                        item_tasks.push(task);
3021                        leader_view_ids.push(id);
3022                        break;
3023                    } else if variant.is_none() {
3024                        Err(anyhow!(
3025                            "failed to construct view from leader (maybe from a different version of zed?)"
3026                        ))?;
3027                    }
3028                }
3029            }
3030
3031            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3032        }
3033
3034        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3035            let items = futures::future::try_join_all(item_tasks).await?;
3036            this.update(cx, |this, cx| {
3037                let state = this.follower_states.get_mut(&pane)?;
3038                for (id, item) in leader_view_ids.into_iter().zip(items) {
3039                    item.set_leader_peer_id(Some(leader_id), cx);
3040                    state.items_by_leader_view_id.insert(id, item);
3041                }
3042
3043                Some(())
3044            })?;
3045        }
3046        Ok(())
3047    }
3048
3049    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3050        let mut is_project_item = true;
3051        let mut update = proto::UpdateActiveView::default();
3052        if cx.is_window_active() {
3053            if let Some(item) = self.active_item(cx) {
3054                if item.focus_handle(cx).contains_focused(cx) {
3055                    let leader_id = self
3056                        .pane_for(&*item)
3057                        .and_then(|pane| self.leader_for_pane(&pane));
3058
3059                    if let Some(item) = item.to_followable_item_handle(cx) {
3060                        let id = item
3061                            .remote_id(&self.app_state.client, cx)
3062                            .map(|id| id.to_proto());
3063
3064                        if let Some(id) = id.clone() {
3065                            if let Some(variant) = item.to_state_proto(cx) {
3066                                let view = Some(proto::View {
3067                                    id: Some(id.clone()),
3068                                    leader_id,
3069                                    variant: Some(variant),
3070                                });
3071
3072                                is_project_item = item.is_project_item(cx);
3073                                update = proto::UpdateActiveView {
3074                                    view,
3075                                    // TODO: once v0.124.0 is retired we can stop sending these
3076                                    id: Some(id),
3077                                    leader_id,
3078                                };
3079                            }
3080                        };
3081                    }
3082                }
3083            }
3084        }
3085
3086        if &update.id != &self.last_active_view_id {
3087            self.last_active_view_id = update.id.clone();
3088            self.update_followers(
3089                is_project_item,
3090                proto::update_followers::Variant::UpdateActiveView(update),
3091                cx,
3092            );
3093        }
3094    }
3095
3096    fn update_followers(
3097        &self,
3098        project_only: bool,
3099        update: proto::update_followers::Variant,
3100        cx: &mut WindowContext,
3101    ) -> Option<()> {
3102        // If this update only applies to for followers in the current project,
3103        // then skip it unless this project is shared. If it applies to all
3104        // followers, regardless of project, then set `project_id` to none,
3105        // indicating that it goes to all followers.
3106        let project_id = if project_only {
3107            Some(self.project.read(cx).remote_id()?)
3108        } else {
3109            None
3110        };
3111        self.app_state().workspace_store.update(cx, |store, cx| {
3112            store.update_followers(project_id, update, cx)
3113        })
3114    }
3115
3116    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3117        self.follower_states.get(pane).map(|state| state.leader_id)
3118    }
3119
3120    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3121        cx.notify();
3122
3123        let call = self.active_call()?;
3124        let room = call.read(cx).room()?.read(cx);
3125        let participant = room.remote_participant_for_peer_id(leader_id)?;
3126        let mut items_to_activate = Vec::new();
3127
3128        let leader_in_this_app;
3129        let leader_in_this_project;
3130        match participant.location {
3131            call::ParticipantLocation::SharedProject { project_id } => {
3132                leader_in_this_app = true;
3133                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3134            }
3135            call::ParticipantLocation::UnsharedProject => {
3136                leader_in_this_app = true;
3137                leader_in_this_project = false;
3138            }
3139            call::ParticipantLocation::External => {
3140                leader_in_this_app = false;
3141                leader_in_this_project = false;
3142            }
3143        };
3144
3145        for (pane, state) in &self.follower_states {
3146            if state.leader_id != leader_id {
3147                continue;
3148            }
3149            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3150                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3151                    if leader_in_this_project || !item.is_project_item(cx) {
3152                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3153                    }
3154                }
3155                continue;
3156            }
3157
3158            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3159                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3160            }
3161        }
3162
3163        for (pane, item) in items_to_activate {
3164            let pane_was_focused = pane.read(cx).has_focus(cx);
3165            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3166                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3167            } else {
3168                pane.update(cx, |pane, cx| {
3169                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3170                });
3171            }
3172
3173            if pane_was_focused {
3174                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3175            }
3176        }
3177
3178        None
3179    }
3180
3181    fn shared_screen_for_peer(
3182        &self,
3183        peer_id: PeerId,
3184        pane: &View<Pane>,
3185        cx: &mut WindowContext,
3186    ) -> Option<View<SharedScreen>> {
3187        let call = self.active_call()?;
3188        let room = call.read(cx).room()?.read(cx);
3189        let participant = room.remote_participant_for_peer_id(peer_id)?;
3190        let track = participant.video_tracks.values().next()?.clone();
3191        let user = participant.user.clone();
3192
3193        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3194            if item.read(cx).peer_id == peer_id {
3195                return Some(item);
3196            }
3197        }
3198
3199        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3200    }
3201
3202    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3203        if cx.is_window_active() {
3204            self.update_active_view_for_followers(cx);
3205            cx.background_executor()
3206                .spawn(persistence::DB.update_timestamp(self.database_id()))
3207                .detach();
3208        } else {
3209            for pane in &self.panes {
3210                pane.update(cx, |pane, cx| {
3211                    if let Some(item) = pane.active_item() {
3212                        item.workspace_deactivated(cx);
3213                    }
3214                    if matches!(
3215                        WorkspaceSettings::get_global(cx).autosave,
3216                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3217                    ) {
3218                        for item in pane.items() {
3219                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3220                                .detach_and_log_err(cx);
3221                        }
3222                    }
3223                });
3224            }
3225        }
3226    }
3227
3228    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3229        self.active_call.as_ref().map(|(call, _)| call)
3230    }
3231
3232    fn on_active_call_event(
3233        &mut self,
3234        _: Model<ActiveCall>,
3235        event: &call::room::Event,
3236        cx: &mut ViewContext<Self>,
3237    ) {
3238        match event {
3239            call::room::Event::ParticipantLocationChanged { participant_id }
3240            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3241                self.leader_updated(*participant_id, cx);
3242            }
3243            _ => {}
3244        }
3245    }
3246
3247    pub fn database_id(&self) -> WorkspaceId {
3248        self.database_id
3249    }
3250
3251    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3252        let project = self.project().read(cx);
3253
3254        if project.is_local() {
3255            Some(
3256                project
3257                    .visible_worktrees(cx)
3258                    .map(|worktree| worktree.read(cx).abs_path())
3259                    .collect::<Vec<_>>()
3260                    .into(),
3261            )
3262        } else {
3263            None
3264        }
3265    }
3266
3267    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3268        match member {
3269            Member::Axis(PaneAxis { members, .. }) => {
3270                for child in members.iter() {
3271                    self.remove_panes(child.clone(), cx)
3272                }
3273            }
3274            Member::Pane(pane) => {
3275                self.force_remove_pane(&pane, cx);
3276            }
3277        }
3278    }
3279
3280    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3281        self.panes.retain(|p| p != pane);
3282        self.panes
3283            .last()
3284            .unwrap()
3285            .update(cx, |pane, cx| pane.focus(cx));
3286        if self.last_active_center_pane == Some(pane.downgrade()) {
3287            self.last_active_center_pane = None;
3288        }
3289        cx.notify();
3290    }
3291
3292    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3293        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3294            cx.background_executor()
3295                .timer(Duration::from_millis(100))
3296                .await;
3297            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3298                .log_err();
3299        }));
3300    }
3301
3302    fn serialize_workspace(&self, cx: &mut WindowContext) {
3303        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3304            let (items, active) = {
3305                let pane = pane_handle.read(cx);
3306                let active_item_id = pane.active_item().map(|item| item.item_id());
3307                (
3308                    pane.items()
3309                        .filter_map(|item_handle| {
3310                            Some(SerializedItem {
3311                                kind: Arc::from(item_handle.serialized_item_kind()?),
3312                                item_id: item_handle.item_id().as_u64(),
3313                                active: Some(item_handle.item_id()) == active_item_id,
3314                            })
3315                        })
3316                        .collect::<Vec<_>>(),
3317                    pane.has_focus(cx),
3318                )
3319            };
3320
3321            SerializedPane::new(items, active)
3322        }
3323
3324        fn build_serialized_pane_group(
3325            pane_group: &Member,
3326            cx: &WindowContext,
3327        ) -> SerializedPaneGroup {
3328            match pane_group {
3329                Member::Axis(PaneAxis {
3330                    axis,
3331                    members,
3332                    flexes,
3333                    bounding_boxes: _,
3334                }) => SerializedPaneGroup::Group {
3335                    axis: SerializedAxis(*axis),
3336                    children: members
3337                        .iter()
3338                        .map(|member| build_serialized_pane_group(member, cx))
3339                        .collect::<Vec<_>>(),
3340                    flexes: Some(flexes.lock().clone()),
3341                },
3342                Member::Pane(pane_handle) => {
3343                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3344                }
3345            }
3346        }
3347
3348        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3349            let left_dock = this.left_dock.read(cx);
3350            let left_visible = left_dock.is_open();
3351            let left_active_panel = left_dock
3352                .visible_panel()
3353                .and_then(|panel| Some(panel.persistent_name().to_string()));
3354            let left_dock_zoom = left_dock
3355                .visible_panel()
3356                .map(|panel| panel.is_zoomed(cx))
3357                .unwrap_or(false);
3358
3359            let right_dock = this.right_dock.read(cx);
3360            let right_visible = right_dock.is_open();
3361            let right_active_panel = right_dock
3362                .visible_panel()
3363                .and_then(|panel| Some(panel.persistent_name().to_string()));
3364            let right_dock_zoom = right_dock
3365                .visible_panel()
3366                .map(|panel| panel.is_zoomed(cx))
3367                .unwrap_or(false);
3368
3369            let bottom_dock = this.bottom_dock.read(cx);
3370            let bottom_visible = bottom_dock.is_open();
3371            let bottom_active_panel = bottom_dock
3372                .visible_panel()
3373                .and_then(|panel| Some(panel.persistent_name().to_string()));
3374            let bottom_dock_zoom = bottom_dock
3375                .visible_panel()
3376                .map(|panel| panel.is_zoomed(cx))
3377                .unwrap_or(false);
3378
3379            DockStructure {
3380                left: DockData {
3381                    visible: left_visible,
3382                    active_panel: left_active_panel,
3383                    zoom: left_dock_zoom,
3384                },
3385                right: DockData {
3386                    visible: right_visible,
3387                    active_panel: right_active_panel,
3388                    zoom: right_dock_zoom,
3389                },
3390                bottom: DockData {
3391                    visible: bottom_visible,
3392                    active_panel: bottom_active_panel,
3393                    zoom: bottom_dock_zoom,
3394                },
3395            }
3396        }
3397
3398        if let Some(location) = self.location(cx) {
3399            // Load bearing special case:
3400            //  - with_local_workspace() relies on this to not have other stuff open
3401            //    when you open your log
3402            if !location.paths().is_empty() {
3403                let center_group = build_serialized_pane_group(&self.center.root, cx);
3404                let docks = build_serialized_docks(self, cx);
3405
3406                let serialized_workspace = SerializedWorkspace {
3407                    id: self.database_id,
3408                    location,
3409                    center_group,
3410                    bounds: Default::default(),
3411                    display: Default::default(),
3412                    docks,
3413                };
3414
3415                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3416                    .detach();
3417            }
3418        }
3419    }
3420
3421    pub(crate) fn load_workspace(
3422        serialized_workspace: SerializedWorkspace,
3423        paths_to_open: Vec<Option<ProjectPath>>,
3424        cx: &mut ViewContext<Workspace>,
3425    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3426        cx.spawn(|workspace, mut cx| async move {
3427            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3428
3429            let mut center_group = None;
3430            let mut center_items = None;
3431
3432            // Traverse the splits tree and add to things
3433            if let Some((group, active_pane, items)) = serialized_workspace
3434                .center_group
3435                .deserialize(
3436                    &project,
3437                    serialized_workspace.id,
3438                    workspace.clone(),
3439                    &mut cx,
3440                )
3441                .await
3442            {
3443                center_items = Some(items);
3444                center_group = Some((group, active_pane))
3445            }
3446
3447            let mut items_by_project_path = cx.update(|cx| {
3448                center_items
3449                    .unwrap_or_default()
3450                    .into_iter()
3451                    .filter_map(|item| {
3452                        let item = item?;
3453                        let project_path = item.project_path(cx)?;
3454                        Some((project_path, item))
3455                    })
3456                    .collect::<HashMap<_, _>>()
3457            })?;
3458
3459            let opened_items = paths_to_open
3460                .into_iter()
3461                .map(|path_to_open| {
3462                    path_to_open
3463                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3464                })
3465                .collect::<Vec<_>>();
3466
3467            // Remove old panes from workspace panes list
3468            workspace.update(&mut cx, |workspace, cx| {
3469                if let Some((center_group, active_pane)) = center_group {
3470                    workspace.remove_panes(workspace.center.root.clone(), cx);
3471
3472                    // Swap workspace center group
3473                    workspace.center = PaneGroup::with_root(center_group);
3474                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3475                    if let Some(active_pane) = active_pane {
3476                        workspace.active_pane = active_pane;
3477                        cx.focus_self();
3478                    } else {
3479                        workspace.active_pane = workspace.center.first_pane().clone();
3480                    }
3481                }
3482
3483                let docks = serialized_workspace.docks;
3484
3485                let right = docks.right.clone();
3486                workspace
3487                    .right_dock
3488                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3489                let left = docks.left.clone();
3490                workspace
3491                    .left_dock
3492                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3493                let bottom = docks.bottom.clone();
3494                workspace
3495                    .bottom_dock
3496                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3497
3498                cx.notify();
3499            })?;
3500
3501            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3502            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3503
3504            Ok(opened_items)
3505        })
3506    }
3507
3508    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3509        self.add_workspace_actions_listeners(div, cx)
3510            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3511            .on_action(cx.listener(Self::close_all_items_and_panes))
3512            .on_action(cx.listener(Self::save_all))
3513            .on_action(cx.listener(Self::send_keystrokes))
3514            .on_action(cx.listener(Self::add_folder_to_project))
3515            .on_action(cx.listener(Self::follow_next_collaborator))
3516            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3517                let pane = workspace.active_pane().clone();
3518                workspace.unfollow(&pane, cx);
3519            }))
3520            .on_action(cx.listener(|workspace, action: &Save, cx| {
3521                workspace
3522                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3523                    .detach_and_log_err(cx);
3524            }))
3525            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3526                workspace
3527                    .save_active_item(SaveIntent::SaveAs, cx)
3528                    .detach_and_log_err(cx);
3529            }))
3530            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3531                workspace.activate_previous_pane(cx)
3532            }))
3533            .on_action(
3534                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3535            )
3536            .on_action(
3537                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3538                    workspace.activate_pane_in_direction(action.0, cx)
3539                }),
3540            )
3541            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3542                workspace.swap_pane_in_direction(action.0, cx)
3543            }))
3544            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3545                this.toggle_dock(DockPosition::Left, cx);
3546            }))
3547            .on_action(
3548                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3549                    workspace.toggle_dock(DockPosition::Right, cx);
3550                }),
3551            )
3552            .on_action(
3553                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3554                    workspace.toggle_dock(DockPosition::Bottom, cx);
3555                }),
3556            )
3557            .on_action(
3558                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3559                    workspace.close_all_docks(cx);
3560                }),
3561            )
3562            .on_action(cx.listener(Workspace::open))
3563            .on_action(cx.listener(Workspace::close_window))
3564            .on_action(cx.listener(Workspace::activate_pane_at_index))
3565            .on_action(
3566                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3567                    workspace.reopen_closed_item(cx).detach();
3568                }),
3569            )
3570            .on_action(|_: &ToggleGraphicsProfiler, cx| cx.toggle_graphics_profiler())
3571    }
3572
3573    #[cfg(any(test, feature = "test-support"))]
3574    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3575        use node_runtime::FakeNodeRuntime;
3576
3577        let client = project.read(cx).client();
3578        let user_store = project.read(cx).user_store();
3579
3580        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3581        cx.activate_window();
3582        let app_state = Arc::new(AppState {
3583            languages: project.read(cx).languages().clone(),
3584            workspace_store,
3585            client,
3586            user_store,
3587            fs: project.read(cx).fs().clone(),
3588            build_window_options: |_, _, _| Default::default(),
3589            node_runtime: FakeNodeRuntime::new(),
3590        });
3591        let workspace = Self::new(0, project, app_state, cx);
3592        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3593        workspace
3594    }
3595
3596    pub fn register_action<A: Action>(
3597        &mut self,
3598        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3599    ) -> &mut Self {
3600        let callback = Arc::new(callback);
3601
3602        self.workspace_actions.push(Box::new(move |div, cx| {
3603            let callback = callback.clone();
3604            div.on_action(
3605                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3606            )
3607        }));
3608        self
3609    }
3610
3611    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3612        let mut div = div
3613            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3614            .on_action(cx.listener(Self::close_all_items_and_panes))
3615            .on_action(cx.listener(Self::add_folder_to_project))
3616            .on_action(cx.listener(Self::save_all))
3617            .on_action(cx.listener(Self::open));
3618        for action in self.workspace_actions.iter() {
3619            div = (action)(div, cx)
3620        }
3621        div
3622    }
3623
3624    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3625        self.modal_layer.read(cx).has_active_modal()
3626    }
3627
3628    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3629        self.modal_layer.read(cx).active_modal()
3630    }
3631
3632    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3633    where
3634        B: FnOnce(&mut ViewContext<V>) -> V,
3635    {
3636        self.modal_layer
3637            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3638    }
3639}
3640
3641fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3642    let display_origin = cx
3643        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3644        .ok()??;
3645    ZED_WINDOW_POSITION
3646        .zip(*ZED_WINDOW_SIZE)
3647        .map(|(position, size)| {
3648            WindowBounds::Fixed(Bounds {
3649                origin: display_origin + position,
3650                size,
3651            })
3652        })
3653}
3654
3655fn open_items(
3656    serialized_workspace: Option<SerializedWorkspace>,
3657    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3658    app_state: Arc<AppState>,
3659    cx: &mut ViewContext<Workspace>,
3660) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3661    let restored_items = serialized_workspace.map(|serialized_workspace| {
3662        Workspace::load_workspace(
3663            serialized_workspace,
3664            project_paths_to_open
3665                .iter()
3666                .map(|(_, project_path)| project_path)
3667                .cloned()
3668                .collect(),
3669            cx,
3670        )
3671    });
3672
3673    cx.spawn(|workspace, mut cx| async move {
3674        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3675
3676        if let Some(restored_items) = restored_items {
3677            let restored_items = restored_items.await?;
3678
3679            let restored_project_paths = restored_items
3680                .iter()
3681                .filter_map(|item| {
3682                    cx.update(|cx| item.as_ref()?.project_path(cx))
3683                        .ok()
3684                        .flatten()
3685                })
3686                .collect::<HashSet<_>>();
3687
3688            for restored_item in restored_items {
3689                opened_items.push(restored_item.map(Ok));
3690            }
3691
3692            project_paths_to_open
3693                .iter_mut()
3694                .for_each(|(_, project_path)| {
3695                    if let Some(project_path_to_open) = project_path {
3696                        if restored_project_paths.contains(project_path_to_open) {
3697                            *project_path = None;
3698                        }
3699                    }
3700                });
3701        } else {
3702            for _ in 0..project_paths_to_open.len() {
3703                opened_items.push(None);
3704            }
3705        }
3706        assert!(opened_items.len() == project_paths_to_open.len());
3707
3708        let tasks =
3709            project_paths_to_open
3710                .into_iter()
3711                .enumerate()
3712                .map(|(i, (abs_path, project_path))| {
3713                    let workspace = workspace.clone();
3714                    cx.spawn(|mut cx| {
3715                        let fs = app_state.fs.clone();
3716                        async move {
3717                            let file_project_path = project_path?;
3718                            if fs.is_file(&abs_path).await {
3719                                Some((
3720                                    i,
3721                                    workspace
3722                                        .update(&mut cx, |workspace, cx| {
3723                                            workspace.open_path(file_project_path, None, true, cx)
3724                                        })
3725                                        .log_err()?
3726                                        .await,
3727                                ))
3728                            } else {
3729                                None
3730                            }
3731                        }
3732                    })
3733                });
3734
3735        let tasks = tasks.collect::<Vec<_>>();
3736
3737        let tasks = futures::future::join_all(tasks.into_iter());
3738        for maybe_opened_path in tasks.await.into_iter() {
3739            if let Some((i, path_open_result)) = maybe_opened_path {
3740                opened_items[i] = Some(path_open_result);
3741            }
3742        }
3743
3744        Ok(opened_items)
3745    })
3746}
3747
3748enum ActivateInDirectionTarget {
3749    Pane(View<Pane>),
3750    Dock(View<Dock>),
3751}
3752
3753fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3754    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3755
3756    workspace
3757        .update(cx, |workspace, cx| {
3758            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3759                workspace.show_notification_once(0, cx, |cx| {
3760                    cx.new_view(|_| {
3761                        MessageNotification::new("Failed to load the database file.")
3762                            .with_click_message("Click to let us know about this error")
3763                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3764                    })
3765                });
3766            }
3767        })
3768        .log_err();
3769}
3770
3771impl FocusableView for Workspace {
3772    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3773        self.active_pane.focus_handle(cx)
3774    }
3775}
3776
3777#[derive(Clone, Render)]
3778struct DraggedDock(DockPosition);
3779
3780impl Render for Workspace {
3781    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3782        let mut context = KeyContext::default();
3783        context.add("Workspace");
3784
3785        let (ui_font, ui_font_size) = {
3786            let theme_settings = ThemeSettings::get_global(cx);
3787            (
3788                theme_settings.ui_font.family.clone(),
3789                theme_settings.ui_font_size.clone(),
3790            )
3791        };
3792
3793        let theme = cx.theme().clone();
3794        let colors = theme.colors();
3795        cx.set_rem_size(ui_font_size);
3796
3797        self.actions(div(), cx)
3798            .key_context(context)
3799            .relative()
3800            .size_full()
3801            .flex()
3802            .flex_col()
3803            .font(ui_font)
3804            .gap_0()
3805            .justify_start()
3806            .items_start()
3807            .text_color(colors.text)
3808            .bg(colors.background)
3809            .border()
3810            .border_color(colors.border)
3811            .children(self.titlebar_item.clone())
3812            .child(
3813                div()
3814                    .id("workspace")
3815                    .relative()
3816                    .flex_1()
3817                    .w_full()
3818                    .flex()
3819                    .flex_col()
3820                    .overflow_hidden()
3821                    .border_t()
3822                    .border_b()
3823                    .border_color(colors.border)
3824                    .child(
3825                        canvas({
3826                            let this = cx.view().clone();
3827                            move |bounds, cx| {
3828                                this.update(cx, |this, _cx| {
3829                                    this.bounds = *bounds;
3830                                })
3831                            }
3832                        })
3833                        .absolute()
3834                        .size_full(),
3835                    )
3836                    .on_drag_move(
3837                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3838                            match e.drag(cx).0 {
3839                                DockPosition::Left => {
3840                                    let size = workspace.bounds.left() + e.event.position.x;
3841                                    workspace.left_dock.update(cx, |left_dock, cx| {
3842                                        left_dock.resize_active_panel(Some(size), cx);
3843                                    });
3844                                }
3845                                DockPosition::Right => {
3846                                    let size = workspace.bounds.right() - e.event.position.x;
3847                                    workspace.right_dock.update(cx, |right_dock, cx| {
3848                                        right_dock.resize_active_panel(Some(size), cx);
3849                                    });
3850                                }
3851                                DockPosition::Bottom => {
3852                                    let size = workspace.bounds.bottom() - e.event.position.y;
3853                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3854                                        bottom_dock.resize_active_panel(Some(size), cx);
3855                                    });
3856                                }
3857                            }
3858                        }),
3859                    )
3860                    .child(self.modal_layer.clone())
3861                    .child(
3862                        div()
3863                            .flex()
3864                            .flex_row()
3865                            .h_full()
3866                            // Left Dock
3867                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3868                                || {
3869                                    div()
3870                                        .flex()
3871                                        .flex_none()
3872                                        .overflow_hidden()
3873                                        .child(self.left_dock.clone())
3874                                },
3875                            ))
3876                            // Panes
3877                            .child(
3878                                div()
3879                                    .flex()
3880                                    .flex_col()
3881                                    .flex_1()
3882                                    .overflow_hidden()
3883                                    .child(self.center.render(
3884                                        &self.project,
3885                                        &self.follower_states,
3886                                        self.active_call(),
3887                                        &self.active_pane,
3888                                        self.zoomed.as_ref(),
3889                                        &self.app_state,
3890                                        cx,
3891                                    ))
3892                                    .children(
3893                                        self.zoomed_position
3894                                            .ne(&Some(DockPosition::Bottom))
3895                                            .then(|| self.bottom_dock.clone()),
3896                                    ),
3897                            )
3898                            // Right Dock
3899                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3900                                || {
3901                                    div()
3902                                        .flex()
3903                                        .flex_none()
3904                                        .overflow_hidden()
3905                                        .child(self.right_dock.clone())
3906                                },
3907                            )),
3908                    )
3909                    .children(self.render_notifications(cx))
3910                    .children(self.zoomed.as_ref().and_then(|view| {
3911                        let zoomed_view = view.upgrade()?;
3912                        let div = div()
3913                            .z_index(1)
3914                            .absolute()
3915                            .overflow_hidden()
3916                            .border_color(colors.border)
3917                            .bg(colors.background)
3918                            .child(zoomed_view)
3919                            .inset_0()
3920                            .shadow_lg();
3921
3922                        Some(match self.zoomed_position {
3923                            Some(DockPosition::Left) => div.right_2().border_r(),
3924                            Some(DockPosition::Right) => div.left_2().border_l(),
3925                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3926                            None => div.top_2().bottom_2().left_2().right_2().border(),
3927                        })
3928                    })),
3929            )
3930            .child(self.status_bar.clone())
3931            .children(if self.project.read(cx).is_disconnected() {
3932                Some(DisconnectedOverlay)
3933            } else {
3934                None
3935            })
3936    }
3937}
3938
3939impl WorkspaceStore {
3940    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3941        Self {
3942            workspaces: Default::default(),
3943            _subscriptions: vec![
3944                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3945                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3946            ],
3947            client,
3948        }
3949    }
3950
3951    pub fn update_followers(
3952        &self,
3953        project_id: Option<u64>,
3954        update: proto::update_followers::Variant,
3955        cx: &AppContext,
3956    ) -> Option<()> {
3957        let active_call = ActiveCall::try_global(cx)?;
3958        let room_id = active_call.read(cx).room()?.read(cx).id();
3959        self.client
3960            .send(proto::UpdateFollowers {
3961                room_id,
3962                project_id,
3963                variant: Some(update),
3964            })
3965            .log_err()
3966    }
3967
3968    pub async fn handle_follow(
3969        this: Model<Self>,
3970        envelope: TypedEnvelope<proto::Follow>,
3971        _: Arc<Client>,
3972        mut cx: AsyncAppContext,
3973    ) -> Result<proto::FollowResponse> {
3974        this.update(&mut cx, |this, cx| {
3975            let follower = Follower {
3976                project_id: envelope.payload.project_id,
3977                peer_id: envelope.original_sender_id()?,
3978            };
3979            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3980
3981            let mut response = proto::FollowResponse::default();
3982            this.workspaces.retain(|workspace| {
3983                workspace
3984                    .update(cx, |workspace, cx| {
3985                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3986                        if response.views.is_empty() {
3987                            response.views = handler_response.views;
3988                        } else {
3989                            response.views.extend_from_slice(&handler_response.views);
3990                        }
3991
3992                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3993                            if response.active_view_id.is_none()
3994                                || Some(workspace.project.downgrade()) == active_project
3995                            {
3996                                response.active_view_id = Some(active_view_id);
3997                            }
3998                        }
3999
4000                        if let Some(active_view) = handler_response.active_view.clone() {
4001                            if workspace.project.read(cx).remote_id() == follower.project_id {
4002                                response.active_view = Some(active_view)
4003                            }
4004                        }
4005                    })
4006                    .is_ok()
4007            });
4008
4009            Ok(response)
4010        })?
4011    }
4012
4013    async fn handle_update_followers(
4014        this: Model<Self>,
4015        envelope: TypedEnvelope<proto::UpdateFollowers>,
4016        _: Arc<Client>,
4017        mut cx: AsyncAppContext,
4018    ) -> Result<()> {
4019        let leader_id = envelope.original_sender_id()?;
4020        let update = envelope.payload;
4021
4022        this.update(&mut cx, |this, cx| {
4023            this.workspaces.retain(|workspace| {
4024                workspace
4025                    .update(cx, |workspace, cx| {
4026                        let project_id = workspace.project.read(cx).remote_id();
4027                        if update.project_id != project_id && update.project_id.is_some() {
4028                            return;
4029                        }
4030                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4031                    })
4032                    .is_ok()
4033            });
4034            Ok(())
4035        })?
4036    }
4037}
4038
4039impl ViewId {
4040    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4041        Ok(Self {
4042            creator: message
4043                .creator
4044                .ok_or_else(|| anyhow!("creator is missing"))?,
4045            id: message.id,
4046        })
4047    }
4048
4049    pub(crate) fn to_proto(&self) -> proto::ViewId {
4050        proto::ViewId {
4051            creator: Some(self.creator),
4052            id: self.id,
4053        }
4054    }
4055}
4056
4057pub trait WorkspaceHandle {
4058    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4059}
4060
4061impl WorkspaceHandle for View<Workspace> {
4062    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4063        self.read(cx)
4064            .worktrees(cx)
4065            .flat_map(|worktree| {
4066                let worktree_id = worktree.read(cx).id();
4067                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4068                    worktree_id,
4069                    path: f.path.clone(),
4070                })
4071            })
4072            .collect::<Vec<_>>()
4073    }
4074}
4075
4076impl std::fmt::Debug for OpenPaths {
4077    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4078        f.debug_struct("OpenPaths")
4079            .field("paths", &self.paths)
4080            .finish()
4081    }
4082}
4083
4084pub fn activate_workspace_for_project(
4085    cx: &mut AppContext,
4086    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4087) -> Option<WindowHandle<Workspace>> {
4088    for window in cx.windows() {
4089        let Some(workspace) = window.downcast::<Workspace>() else {
4090            continue;
4091        };
4092
4093        let predicate = workspace
4094            .update(cx, |workspace, cx| {
4095                let project = workspace.project.read(cx);
4096                if predicate(project, cx) {
4097                    cx.activate_window();
4098                    true
4099                } else {
4100                    false
4101                }
4102            })
4103            .log_err()
4104            .unwrap_or(false);
4105
4106        if predicate {
4107            return Some(workspace);
4108        }
4109    }
4110
4111    None
4112}
4113
4114pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4115    DB.last_workspace().await.log_err().flatten()
4116}
4117
4118actions!(collab, [OpenChannelNotes]);
4119
4120async fn join_channel_internal(
4121    channel_id: u64,
4122    app_state: &Arc<AppState>,
4123    requesting_window: Option<WindowHandle<Workspace>>,
4124    active_call: &Model<ActiveCall>,
4125    cx: &mut AsyncAppContext,
4126) -> Result<bool> {
4127    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4128        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4129            return (false, None);
4130        };
4131
4132        let already_in_channel = room.channel_id() == Some(channel_id);
4133        let should_prompt = room.is_sharing_project()
4134            && room.remote_participants().len() > 0
4135            && !already_in_channel;
4136        let open_room = if already_in_channel {
4137            active_call.room().cloned()
4138        } else {
4139            None
4140        };
4141        (should_prompt, open_room)
4142    })?;
4143
4144    if let Some(room) = open_room {
4145        let task = room.update(cx, |room, cx| {
4146            if let Some((project, host)) = room.most_active_project(cx) {
4147                return Some(join_remote_project(project, host, app_state.clone(), cx));
4148            }
4149
4150            None
4151        })?;
4152        if let Some(task) = task {
4153            task.await?;
4154        }
4155        return anyhow::Ok(true);
4156    }
4157
4158    if should_prompt {
4159        if let Some(workspace) = requesting_window {
4160            let answer = workspace
4161                .update(cx, |_, cx| {
4162                    cx.prompt(
4163                        PromptLevel::Warning,
4164                        "Do you want to switch channels?",
4165                        Some("Leaving this call will unshare your current project."),
4166                        &["Yes, Join Channel", "Cancel"],
4167                    )
4168                })?
4169                .await;
4170
4171            if answer == Ok(1) {
4172                return Ok(false);
4173            }
4174        } else {
4175            return Ok(false); // unreachable!() hopefully
4176        }
4177    }
4178
4179    let client = cx.update(|cx| active_call.read(cx).client())?;
4180
4181    let mut client_status = client.status();
4182
4183    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4184    'outer: loop {
4185        let Some(status) = client_status.recv().await else {
4186            return Err(anyhow!("error connecting"));
4187        };
4188
4189        match status {
4190            Status::Connecting
4191            | Status::Authenticating
4192            | Status::Reconnecting
4193            | Status::Reauthenticating => continue,
4194            Status::Connected { .. } => break 'outer,
4195            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4196            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4197            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4198                return Err(ErrorCode::Disconnected.into());
4199            }
4200        }
4201    }
4202
4203    let room = active_call
4204        .update(cx, |active_call, cx| {
4205            active_call.join_channel(channel_id, cx)
4206        })?
4207        .await?;
4208
4209    let Some(room) = room else {
4210        return anyhow::Ok(true);
4211    };
4212
4213    room.update(cx, |room, _| room.room_update_completed())?
4214        .await;
4215
4216    let task = room.update(cx, |room, cx| {
4217        if let Some((project, host)) = room.most_active_project(cx) {
4218            return Some(join_remote_project(project, host, app_state.clone(), cx));
4219        }
4220
4221        // if you are the first to join a channel, share your project
4222        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4223            if let Some(workspace) = requesting_window {
4224                let project = workspace.update(cx, |workspace, cx| {
4225                    if !CallSettings::get_global(cx).share_on_join {
4226                        return None;
4227                    }
4228                    let project = workspace.project.read(cx);
4229                    if project.is_local()
4230                        && project.visible_worktrees(cx).any(|tree| {
4231                            tree.read(cx)
4232                                .root_entry()
4233                                .map_or(false, |entry| entry.is_dir())
4234                        })
4235                    {
4236                        Some(workspace.project.clone())
4237                    } else {
4238                        None
4239                    }
4240                });
4241                if let Ok(Some(project)) = project {
4242                    return Some(cx.spawn(|room, mut cx| async move {
4243                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4244                            .await?;
4245                        Ok(())
4246                    }));
4247                }
4248            }
4249        }
4250
4251        None
4252    })?;
4253    if let Some(task) = task {
4254        task.await?;
4255        return anyhow::Ok(true);
4256    }
4257    anyhow::Ok(false)
4258}
4259
4260pub fn join_channel(
4261    channel_id: u64,
4262    app_state: Arc<AppState>,
4263    requesting_window: Option<WindowHandle<Workspace>>,
4264    cx: &mut AppContext,
4265) -> Task<Result<()>> {
4266    let active_call = ActiveCall::global(cx);
4267    cx.spawn(|mut cx| async move {
4268        let result = join_channel_internal(
4269            channel_id,
4270            &app_state,
4271            requesting_window,
4272            &active_call,
4273            &mut cx,
4274        )
4275            .await;
4276
4277        // join channel succeeded, and opened a window
4278        if matches!(result, Ok(true)) {
4279            return anyhow::Ok(());
4280        }
4281
4282        // find an existing workspace to focus and show call controls
4283        let mut active_window =
4284            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4285        if active_window.is_none() {
4286            // no open workspaces, make one to show the error in (blergh)
4287            let (window_handle, _) = cx
4288                .update(|cx| {
4289                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4290                })?
4291                .await?;
4292
4293            if result.is_ok() {
4294                cx.update(|cx| {
4295                    cx.dispatch_action(&OpenChannelNotes);
4296                }).log_err();
4297            }
4298
4299            active_window = Some(window_handle);
4300        }
4301
4302        if let Err(err) = result {
4303            log::error!("failed to join channel: {}", err);
4304            if let Some(active_window) = active_window {
4305                active_window
4306                    .update(&mut cx, |_, cx| {
4307                        let detail: SharedString = match err.error_code() {
4308                            ErrorCode::SignedOut => {
4309                                "Please sign in to continue.".into()
4310                            }
4311                            ErrorCode::UpgradeRequired => {
4312                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4313                            }
4314                            ErrorCode::NoSuchChannel => {
4315                                "No matching channel was found. Please check the link and try again.".into()
4316                            }
4317                            ErrorCode::Forbidden => {
4318                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4319                            }
4320                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4321                            _ => format!("{}\n\nPlease try again.", err).into(),
4322                        };
4323                        cx.prompt(
4324                            PromptLevel::Critical,
4325                            "Failed to join channel",
4326                            Some(&detail),
4327                            &["Ok"],
4328                        )
4329                    })?
4330                    .await
4331                    .ok();
4332            }
4333        }
4334
4335        // return ok, we showed the error to the user.
4336        return anyhow::Ok(());
4337    })
4338}
4339
4340pub async fn get_any_active_workspace(
4341    app_state: Arc<AppState>,
4342    mut cx: AsyncAppContext,
4343) -> anyhow::Result<WindowHandle<Workspace>> {
4344    // find an existing workspace to focus and show call controls
4345    let active_window = activate_any_workspace_window(&mut cx);
4346    if active_window.is_none() {
4347        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4348            .await?;
4349    }
4350    activate_any_workspace_window(&mut cx).context("could not open zed")
4351}
4352
4353fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4354    cx.update(|cx| {
4355        for window in cx.windows() {
4356            if let Some(workspace_window) = window.downcast::<Workspace>() {
4357                workspace_window
4358                    .update(cx, |_, cx| cx.activate_window())
4359                    .ok();
4360                return Some(workspace_window);
4361            }
4362        }
4363        None
4364    })
4365    .ok()
4366    .flatten()
4367}
4368
4369#[allow(clippy::type_complexity)]
4370pub fn open_paths(
4371    abs_paths: &[PathBuf],
4372    app_state: &Arc<AppState>,
4373    requesting_window: Option<WindowHandle<Workspace>>,
4374    cx: &mut AppContext,
4375) -> Task<
4376    anyhow::Result<(
4377        WindowHandle<Workspace>,
4378        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4379    )>,
4380> {
4381    let app_state = app_state.clone();
4382    let abs_paths = abs_paths.to_vec();
4383    // Open paths in existing workspace if possible
4384    let existing = activate_workspace_for_project(cx, {
4385        let abs_paths = abs_paths.clone();
4386        move |project, cx| project.contains_paths(&abs_paths, cx)
4387    });
4388    cx.spawn(move |mut cx| async move {
4389        if let Some(existing) = existing {
4390            Ok((
4391                existing.clone(),
4392                existing
4393                    .update(&mut cx, |workspace, cx| {
4394                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4395                    })?
4396                    .await,
4397            ))
4398        } else {
4399            cx.update(move |cx| {
4400                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4401            })?
4402            .await
4403        }
4404    })
4405}
4406
4407pub fn open_new(
4408    app_state: &Arc<AppState>,
4409    cx: &mut AppContext,
4410    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4411) -> Task<()> {
4412    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4413    cx.spawn(|mut cx| async move {
4414        if let Some((workspace, opened_paths)) = task.await.log_err() {
4415            workspace
4416                .update(&mut cx, |workspace, cx| {
4417                    if opened_paths.is_empty() {
4418                        init(workspace, cx)
4419                    }
4420                })
4421                .log_err();
4422        }
4423    })
4424}
4425
4426pub fn create_and_open_local_file(
4427    path: &'static Path,
4428    cx: &mut ViewContext<Workspace>,
4429    default_content: impl 'static + Send + FnOnce() -> Rope,
4430) -> Task<Result<Box<dyn ItemHandle>>> {
4431    cx.spawn(|workspace, mut cx| async move {
4432        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4433        if !fs.is_file(path).await {
4434            fs.create_file(path, Default::default()).await?;
4435            fs.save(path, &default_content(), Default::default())
4436                .await?;
4437        }
4438
4439        let mut items = workspace
4440            .update(&mut cx, |workspace, cx| {
4441                workspace.with_local_workspace(cx, |workspace, cx| {
4442                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4443                })
4444            })?
4445            .await?
4446            .await;
4447
4448        let item = items.pop().flatten();
4449        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4450    })
4451}
4452
4453pub fn join_remote_project(
4454    project_id: u64,
4455    follow_user_id: u64,
4456    app_state: Arc<AppState>,
4457    cx: &mut AppContext,
4458) -> Task<Result<()>> {
4459    let windows = cx.windows();
4460    cx.spawn(|mut cx| async move {
4461        let existing_workspace = windows.into_iter().find_map(|window| {
4462            window.downcast::<Workspace>().and_then(|window| {
4463                window
4464                    .update(&mut cx, |workspace, cx| {
4465                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4466                            Some(window)
4467                        } else {
4468                            None
4469                        }
4470                    })
4471                    .unwrap_or(None)
4472            })
4473        });
4474
4475        let workspace = if let Some(existing_workspace) = existing_workspace {
4476            existing_workspace
4477        } else {
4478            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4479            let room = active_call
4480                .read_with(&cx, |call, _| call.room().cloned())?
4481                .ok_or_else(|| anyhow!("not in a call"))?;
4482            let project = room
4483                .update(&mut cx, |room, cx| {
4484                    room.join_project(
4485                        project_id,
4486                        app_state.languages.clone(),
4487                        app_state.fs.clone(),
4488                        cx,
4489                    )
4490                })?
4491                .await?;
4492
4493            let window_bounds_override = window_bounds_env_override(&cx);
4494            cx.update(|cx| {
4495                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4496                cx.open_window(options, |cx| {
4497                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4498                })
4499            })?
4500        };
4501
4502        workspace.update(&mut cx, |workspace, cx| {
4503            cx.activate(true);
4504            cx.activate_window();
4505
4506            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4507                let follow_peer_id = room
4508                    .read(cx)
4509                    .remote_participants()
4510                    .iter()
4511                    .find(|(_, participant)| participant.user.id == follow_user_id)
4512                    .map(|(_, p)| p.peer_id)
4513                    .or_else(|| {
4514                        // If we couldn't follow the given user, follow the host instead.
4515                        let collaborator = workspace
4516                            .project()
4517                            .read(cx)
4518                            .collaborators()
4519                            .values()
4520                            .find(|collaborator| collaborator.replica_id == 0)?;
4521                        Some(collaborator.peer_id)
4522                    });
4523
4524                if let Some(follow_peer_id) = follow_peer_id {
4525                    workspace.follow(follow_peer_id, cx);
4526                }
4527            }
4528        })?;
4529
4530        anyhow::Ok(())
4531    })
4532}
4533
4534pub fn restart(_: &Restart, cx: &mut AppContext) {
4535    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4536    let mut workspace_windows = cx
4537        .windows()
4538        .into_iter()
4539        .filter_map(|window| window.downcast::<Workspace>())
4540        .collect::<Vec<_>>();
4541
4542    // If multiple windows have unsaved changes, and need a save prompt,
4543    // prompt in the active window before switching to a different window.
4544    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4545
4546    let mut prompt = None;
4547    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4548        prompt = window
4549            .update(cx, |_, cx| {
4550                cx.prompt(
4551                    PromptLevel::Info,
4552                    "Are you sure you want to restart?",
4553                    None,
4554                    &["Restart", "Cancel"],
4555                )
4556            })
4557            .ok();
4558    }
4559
4560    cx.spawn(|mut cx| async move {
4561        if let Some(prompt) = prompt {
4562            let answer = prompt.await?;
4563            if answer != 0 {
4564                return Ok(());
4565            }
4566        }
4567
4568        // If the user cancels any save prompt, then keep the app open.
4569        for window in workspace_windows {
4570            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4571                workspace.prepare_to_close(true, cx)
4572            }) {
4573                if !should_close.await? {
4574                    return Ok(());
4575                }
4576            }
4577        }
4578
4579        cx.update(|cx| cx.restart())
4580    })
4581    .detach_and_log_err(cx);
4582}
4583
4584fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4585    let mut parts = value.split(',');
4586    let x: usize = parts.next()?.parse().ok()?;
4587    let y: usize = parts.next()?.parse().ok()?;
4588    Some(point((x as f64).into(), (y as f64).into()))
4589}
4590
4591fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4592    let mut parts = value.split(',');
4593    let width: usize = parts.next()?.parse().ok()?;
4594    let height: usize = parts.next()?.parse().ok()?;
4595    Some(size((width as f64).into(), (height as f64).into()))
4596}
4597
4598pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4599    (1.75 * cx.rem_size()).max(px(32.))
4600}
4601
4602struct DisconnectedOverlay;
4603
4604impl Element for DisconnectedOverlay {
4605    type State = AnyElement;
4606
4607    fn request_layout(
4608        &mut self,
4609        _: Option<Self::State>,
4610        cx: &mut ElementContext,
4611    ) -> (LayoutId, Self::State) {
4612        let mut background = cx.theme().colors().elevated_surface_background;
4613        background.fade_out(0.2);
4614        let mut overlay = div()
4615            .bg(background)
4616            .absolute()
4617            .left_0()
4618            .top(titlebar_height(cx))
4619            .size_full()
4620            .flex()
4621            .items_center()
4622            .justify_center()
4623            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4624            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4625            .child(Label::new(
4626                "Your connection to the remote project has been lost.",
4627            ))
4628            .into_any();
4629        (overlay.request_layout(cx), overlay)
4630    }
4631
4632    fn paint(
4633        &mut self,
4634        bounds: Bounds<Pixels>,
4635        overlay: &mut Self::State,
4636        cx: &mut ElementContext,
4637    ) {
4638        cx.with_z_index(u16::MAX, |cx| {
4639            cx.add_opaque_layer(bounds);
4640            overlay.paint(cx);
4641        })
4642    }
4643}
4644
4645impl IntoElement for DisconnectedOverlay {
4646    type Element = Self;
4647
4648    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4649        None
4650    }
4651
4652    fn into_element(self) -> Self::Element {
4653        self
4654    }
4655}
4656
4657#[cfg(test)]
4658mod tests {
4659    use std::{cell::RefCell, rc::Rc};
4660
4661    use super::*;
4662    use crate::{
4663        dock::{test::TestPanel, PanelEvent},
4664        item::{
4665            test::{TestItem, TestProjectItem},
4666            ItemEvent,
4667        },
4668    };
4669    use fs::FakeFs;
4670    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4671    use project::{Project, ProjectEntryId};
4672    use serde_json::json;
4673    use settings::SettingsStore;
4674
4675    #[gpui::test]
4676    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4677        init_test(cx);
4678
4679        let fs = FakeFs::new(cx.executor());
4680        let project = Project::test(fs, [], cx).await;
4681        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4682
4683        // Adding an item with no ambiguity renders the tab without detail.
4684        let item1 = cx.new_view(|cx| {
4685            let mut item = TestItem::new(cx);
4686            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4687            item
4688        });
4689        workspace.update(cx, |workspace, cx| {
4690            workspace.add_item(Box::new(item1.clone()), cx);
4691        });
4692        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4693
4694        // Adding an item that creates ambiguity increases the level of detail on
4695        // both tabs.
4696        let item2 = cx.new_view(|cx| {
4697            let mut item = TestItem::new(cx);
4698            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4699            item
4700        });
4701        workspace.update(cx, |workspace, cx| {
4702            workspace.add_item(Box::new(item2.clone()), cx);
4703        });
4704        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4705        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4706
4707        // Adding an item that creates ambiguity increases the level of detail only
4708        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4709        // we stop at the highest detail available.
4710        let item3 = cx.new_view(|cx| {
4711            let mut item = TestItem::new(cx);
4712            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4713            item
4714        });
4715        workspace.update(cx, |workspace, cx| {
4716            workspace.add_item(Box::new(item3.clone()), cx);
4717        });
4718        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4719        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4720        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4721    }
4722
4723    #[gpui::test]
4724    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4725        init_test(cx);
4726
4727        let fs = FakeFs::new(cx.executor());
4728        fs.insert_tree(
4729            "/root1",
4730            json!({
4731                "one.txt": "",
4732                "two.txt": "",
4733            }),
4734        )
4735        .await;
4736        fs.insert_tree(
4737            "/root2",
4738            json!({
4739                "three.txt": "",
4740            }),
4741        )
4742        .await;
4743
4744        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4745        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4746        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4747        let worktree_id = project.update(cx, |project, cx| {
4748            project.worktrees().next().unwrap().read(cx).id()
4749        });
4750
4751        let item1 = cx.new_view(|cx| {
4752            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4753        });
4754        let item2 = cx.new_view(|cx| {
4755            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4756        });
4757
4758        // Add an item to an empty pane
4759        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4760        project.update(cx, |project, cx| {
4761            assert_eq!(
4762                project.active_entry(),
4763                project
4764                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4765                    .map(|e| e.id)
4766            );
4767        });
4768        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4769
4770        // Add a second item to a non-empty pane
4771        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4772        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4773        project.update(cx, |project, cx| {
4774            assert_eq!(
4775                project.active_entry(),
4776                project
4777                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4778                    .map(|e| e.id)
4779            );
4780        });
4781
4782        // Close the active item
4783        pane.update(cx, |pane, cx| {
4784            pane.close_active_item(&Default::default(), cx).unwrap()
4785        })
4786        .await
4787        .unwrap();
4788        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4789        project.update(cx, |project, cx| {
4790            assert_eq!(
4791                project.active_entry(),
4792                project
4793                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4794                    .map(|e| e.id)
4795            );
4796        });
4797
4798        // Add a project folder
4799        project
4800            .update(cx, |project, cx| {
4801                project.find_or_create_local_worktree("/root2", true, cx)
4802            })
4803            .await
4804            .unwrap();
4805        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4806
4807        // Remove a project folder
4808        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4809        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4810    }
4811
4812    #[gpui::test]
4813    async fn test_close_window(cx: &mut TestAppContext) {
4814        init_test(cx);
4815
4816        let fs = FakeFs::new(cx.executor());
4817        fs.insert_tree("/root", json!({ "one": "" })).await;
4818
4819        let project = Project::test(fs, ["root".as_ref()], cx).await;
4820        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4821
4822        // When there are no dirty items, there's nothing to do.
4823        let item1 = cx.new_view(|cx| TestItem::new(cx));
4824        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4825        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4826        assert!(task.await.unwrap());
4827
4828        // When there are dirty untitled items, prompt to save each one. If the user
4829        // cancels any prompt, then abort.
4830        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4831        let item3 = cx.new_view(|cx| {
4832            TestItem::new(cx)
4833                .with_dirty(true)
4834                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4835        });
4836        workspace.update(cx, |w, cx| {
4837            w.add_item(Box::new(item2.clone()), cx);
4838            w.add_item(Box::new(item3.clone()), cx);
4839        });
4840        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4841        cx.executor().run_until_parked();
4842        cx.simulate_prompt_answer(2); // cancel save all
4843        cx.executor().run_until_parked();
4844        cx.simulate_prompt_answer(2); // cancel save all
4845        cx.executor().run_until_parked();
4846        assert!(!cx.has_pending_prompt());
4847        assert!(!task.await.unwrap());
4848    }
4849
4850    #[gpui::test]
4851    async fn test_close_pane_items(cx: &mut TestAppContext) {
4852        init_test(cx);
4853
4854        let fs = FakeFs::new(cx.executor());
4855
4856        let project = Project::test(fs, None, cx).await;
4857        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4858
4859        let item1 = cx.new_view(|cx| {
4860            TestItem::new(cx)
4861                .with_dirty(true)
4862                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4863        });
4864        let item2 = cx.new_view(|cx| {
4865            TestItem::new(cx)
4866                .with_dirty(true)
4867                .with_conflict(true)
4868                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4869        });
4870        let item3 = cx.new_view(|cx| {
4871            TestItem::new(cx)
4872                .with_dirty(true)
4873                .with_conflict(true)
4874                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4875        });
4876        let item4 = cx.new_view(|cx| {
4877            TestItem::new(cx)
4878                .with_dirty(true)
4879                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4880        });
4881        let pane = workspace.update(cx, |workspace, cx| {
4882            workspace.add_item(Box::new(item1.clone()), cx);
4883            workspace.add_item(Box::new(item2.clone()), cx);
4884            workspace.add_item(Box::new(item3.clone()), cx);
4885            workspace.add_item(Box::new(item4.clone()), cx);
4886            workspace.active_pane().clone()
4887        });
4888
4889        let close_items = pane.update(cx, |pane, cx| {
4890            pane.activate_item(1, true, true, cx);
4891            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4892            let item1_id = item1.item_id();
4893            let item3_id = item3.item_id();
4894            let item4_id = item4.item_id();
4895            pane.close_items(cx, SaveIntent::Close, move |id| {
4896                [item1_id, item3_id, item4_id].contains(&id)
4897            })
4898        });
4899        cx.executor().run_until_parked();
4900
4901        assert!(cx.has_pending_prompt());
4902        // Ignore "Save all" prompt
4903        cx.simulate_prompt_answer(2);
4904        cx.executor().run_until_parked();
4905        // There's a prompt to save item 1.
4906        pane.update(cx, |pane, _| {
4907            assert_eq!(pane.items_len(), 4);
4908            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4909        });
4910        // Confirm saving item 1.
4911        cx.simulate_prompt_answer(0);
4912        cx.executor().run_until_parked();
4913
4914        // Item 1 is saved. There's a prompt to save item 3.
4915        pane.update(cx, |pane, cx| {
4916            assert_eq!(item1.read(cx).save_count, 1);
4917            assert_eq!(item1.read(cx).save_as_count, 0);
4918            assert_eq!(item1.read(cx).reload_count, 0);
4919            assert_eq!(pane.items_len(), 3);
4920            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4921        });
4922        assert!(cx.has_pending_prompt());
4923
4924        // Cancel saving item 3.
4925        cx.simulate_prompt_answer(1);
4926        cx.executor().run_until_parked();
4927
4928        // Item 3 is reloaded. There's a prompt to save item 4.
4929        pane.update(cx, |pane, cx| {
4930            assert_eq!(item3.read(cx).save_count, 0);
4931            assert_eq!(item3.read(cx).save_as_count, 0);
4932            assert_eq!(item3.read(cx).reload_count, 1);
4933            assert_eq!(pane.items_len(), 2);
4934            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4935        });
4936        assert!(cx.has_pending_prompt());
4937
4938        // Confirm saving item 4.
4939        cx.simulate_prompt_answer(0);
4940        cx.executor().run_until_parked();
4941
4942        // There's a prompt for a path for item 4.
4943        cx.simulate_new_path_selection(|_| Some(Default::default()));
4944        close_items.await.unwrap();
4945
4946        // The requested items are closed.
4947        pane.update(cx, |pane, cx| {
4948            assert_eq!(item4.read(cx).save_count, 0);
4949            assert_eq!(item4.read(cx).save_as_count, 1);
4950            assert_eq!(item4.read(cx).reload_count, 0);
4951            assert_eq!(pane.items_len(), 1);
4952            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4953        });
4954    }
4955
4956    #[gpui::test]
4957    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4958        init_test(cx);
4959
4960        let fs = FakeFs::new(cx.executor());
4961        let project = Project::test(fs, [], cx).await;
4962        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4963
4964        // Create several workspace items with single project entries, and two
4965        // workspace items with multiple project entries.
4966        let single_entry_items = (0..=4)
4967            .map(|project_entry_id| {
4968                cx.new_view(|cx| {
4969                    TestItem::new(cx)
4970                        .with_dirty(true)
4971                        .with_project_items(&[TestProjectItem::new(
4972                            project_entry_id,
4973                            &format!("{project_entry_id}.txt"),
4974                            cx,
4975                        )])
4976                })
4977            })
4978            .collect::<Vec<_>>();
4979        let item_2_3 = cx.new_view(|cx| {
4980            TestItem::new(cx)
4981                .with_dirty(true)
4982                .with_singleton(false)
4983                .with_project_items(&[
4984                    single_entry_items[2].read(cx).project_items[0].clone(),
4985                    single_entry_items[3].read(cx).project_items[0].clone(),
4986                ])
4987        });
4988        let item_3_4 = cx.new_view(|cx| {
4989            TestItem::new(cx)
4990                .with_dirty(true)
4991                .with_singleton(false)
4992                .with_project_items(&[
4993                    single_entry_items[3].read(cx).project_items[0].clone(),
4994                    single_entry_items[4].read(cx).project_items[0].clone(),
4995                ])
4996        });
4997
4998        // Create two panes that contain the following project entries:
4999        //   left pane:
5000        //     multi-entry items:   (2, 3)
5001        //     single-entry items:  0, 1, 2, 3, 4
5002        //   right pane:
5003        //     single-entry items:  1
5004        //     multi-entry items:   (3, 4)
5005        let left_pane = workspace.update(cx, |workspace, cx| {
5006            let left_pane = workspace.active_pane().clone();
5007            workspace.add_item(Box::new(item_2_3.clone()), cx);
5008            for item in single_entry_items {
5009                workspace.add_item(Box::new(item), cx);
5010            }
5011            left_pane.update(cx, |pane, cx| {
5012                pane.activate_item(2, true, true, cx);
5013            });
5014
5015            let right_pane = workspace
5016                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5017                .unwrap();
5018
5019            right_pane.update(cx, |pane, cx| {
5020                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5021            });
5022
5023            left_pane
5024        });
5025
5026        cx.focus_view(&left_pane);
5027
5028        // When closing all of the items in the left pane, we should be prompted twice:
5029        // once for project entry 0, and once for project entry 2. Project entries 1,
5030        // 3, and 4 are all still open in the other paten. After those two
5031        // prompts, the task should complete.
5032
5033        let close = left_pane.update(cx, |pane, cx| {
5034            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5035        });
5036        cx.executor().run_until_parked();
5037
5038        // Discard "Save all" prompt
5039        cx.simulate_prompt_answer(2);
5040
5041        cx.executor().run_until_parked();
5042        left_pane.update(cx, |pane, cx| {
5043            assert_eq!(
5044                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5045                &[ProjectEntryId::from_proto(0)]
5046            );
5047        });
5048        cx.simulate_prompt_answer(0);
5049
5050        cx.executor().run_until_parked();
5051        left_pane.update(cx, |pane, cx| {
5052            assert_eq!(
5053                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5054                &[ProjectEntryId::from_proto(2)]
5055            );
5056        });
5057        cx.simulate_prompt_answer(0);
5058
5059        cx.executor().run_until_parked();
5060        close.await.unwrap();
5061        left_pane.update(cx, |pane, _| {
5062            assert_eq!(pane.items_len(), 0);
5063        });
5064    }
5065
5066    #[gpui::test]
5067    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5068        init_test(cx);
5069
5070        let fs = FakeFs::new(cx.executor());
5071        let project = Project::test(fs, [], cx).await;
5072        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5073        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5074
5075        let item = cx.new_view(|cx| {
5076            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5077        });
5078        let item_id = item.entity_id();
5079        workspace.update(cx, |workspace, cx| {
5080            workspace.add_item(Box::new(item.clone()), cx);
5081        });
5082
5083        // Autosave on window change.
5084        item.update(cx, |item, cx| {
5085            cx.update_global(|settings: &mut SettingsStore, cx| {
5086                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5087                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5088                })
5089            });
5090            item.is_dirty = true;
5091        });
5092
5093        // Deactivating the window saves the file.
5094        cx.deactivate_window();
5095        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5096
5097        // Autosave on focus change.
5098        item.update(cx, |item, cx| {
5099            cx.focus_self();
5100            cx.update_global(|settings: &mut SettingsStore, cx| {
5101                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5102                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5103                })
5104            });
5105            item.is_dirty = true;
5106        });
5107
5108        // Blurring the item saves the file.
5109        item.update(cx, |_, cx| cx.blur());
5110        cx.executor().run_until_parked();
5111        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5112
5113        // Deactivating the window still saves the file.
5114        cx.update(|cx| cx.activate_window());
5115        item.update(cx, |item, cx| {
5116            cx.focus_self();
5117            item.is_dirty = true;
5118        });
5119        cx.deactivate_window();
5120
5121        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5122
5123        // Autosave after delay.
5124        item.update(cx, |item, cx| {
5125            cx.update_global(|settings: &mut SettingsStore, cx| {
5126                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5127                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5128                })
5129            });
5130            item.is_dirty = true;
5131            cx.emit(ItemEvent::Edit);
5132        });
5133
5134        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5135        cx.executor().advance_clock(Duration::from_millis(250));
5136        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5137
5138        // After delay expires, the file is saved.
5139        cx.executor().advance_clock(Duration::from_millis(250));
5140        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5141
5142        // Autosave on focus change, ensuring closing the tab counts as such.
5143        item.update(cx, |item, cx| {
5144            cx.update_global(|settings: &mut SettingsStore, cx| {
5145                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5146                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5147                })
5148            });
5149            item.is_dirty = true;
5150        });
5151
5152        pane.update(cx, |pane, cx| {
5153            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5154        })
5155        .await
5156        .unwrap();
5157        assert!(!cx.has_pending_prompt());
5158        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5159
5160        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5161        workspace.update(cx, |workspace, cx| {
5162            workspace.add_item(Box::new(item.clone()), cx);
5163        });
5164        item.update(cx, |item, cx| {
5165            item.project_items[0].update(cx, |item, _| {
5166                item.entry_id = None;
5167            });
5168            item.is_dirty = true;
5169            cx.blur();
5170        });
5171        cx.run_until_parked();
5172        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5173
5174        // Ensure autosave is prevented for deleted files also when closing the buffer.
5175        let _close_items = pane.update(cx, |pane, cx| {
5176            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5177        });
5178        cx.run_until_parked();
5179        assert!(cx.has_pending_prompt());
5180        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5181    }
5182
5183    #[gpui::test]
5184    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5185        init_test(cx);
5186
5187        let fs = FakeFs::new(cx.executor());
5188
5189        let project = Project::test(fs, [], cx).await;
5190        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5191
5192        let item = cx.new_view(|cx| {
5193            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5194        });
5195        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5196        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5197        let toolbar_notify_count = Rc::new(RefCell::new(0));
5198
5199        workspace.update(cx, |workspace, cx| {
5200            workspace.add_item(Box::new(item.clone()), cx);
5201            let toolbar_notification_count = toolbar_notify_count.clone();
5202            cx.observe(&toolbar, move |_, _, _| {
5203                *toolbar_notification_count.borrow_mut() += 1
5204            })
5205            .detach();
5206        });
5207
5208        pane.update(cx, |pane, _| {
5209            assert!(!pane.can_navigate_backward());
5210            assert!(!pane.can_navigate_forward());
5211        });
5212
5213        item.update(cx, |item, cx| {
5214            item.set_state("one".to_string(), cx);
5215        });
5216
5217        // Toolbar must be notified to re-render the navigation buttons
5218        assert_eq!(*toolbar_notify_count.borrow(), 1);
5219
5220        pane.update(cx, |pane, _| {
5221            assert!(pane.can_navigate_backward());
5222            assert!(!pane.can_navigate_forward());
5223        });
5224
5225        workspace
5226            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5227            .await
5228            .unwrap();
5229
5230        assert_eq!(*toolbar_notify_count.borrow(), 2);
5231        pane.update(cx, |pane, _| {
5232            assert!(!pane.can_navigate_backward());
5233            assert!(pane.can_navigate_forward());
5234        });
5235    }
5236
5237    #[gpui::test]
5238    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5239        init_test(cx);
5240        let fs = FakeFs::new(cx.executor());
5241
5242        let project = Project::test(fs, [], cx).await;
5243        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5244
5245        let panel = workspace.update(cx, |workspace, cx| {
5246            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5247            workspace.add_panel(panel.clone(), cx);
5248
5249            workspace
5250                .right_dock()
5251                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5252
5253            panel
5254        });
5255
5256        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5257        pane.update(cx, |pane, cx| {
5258            let item = cx.new_view(|cx| TestItem::new(cx));
5259            pane.add_item(Box::new(item), true, true, None, cx);
5260        });
5261
5262        // Transfer focus from center to panel
5263        workspace.update(cx, |workspace, cx| {
5264            workspace.toggle_panel_focus::<TestPanel>(cx);
5265        });
5266
5267        workspace.update(cx, |workspace, cx| {
5268            assert!(workspace.right_dock().read(cx).is_open());
5269            assert!(!panel.is_zoomed(cx));
5270            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5271        });
5272
5273        // Transfer focus from panel to center
5274        workspace.update(cx, |workspace, cx| {
5275            workspace.toggle_panel_focus::<TestPanel>(cx);
5276        });
5277
5278        workspace.update(cx, |workspace, cx| {
5279            assert!(workspace.right_dock().read(cx).is_open());
5280            assert!(!panel.is_zoomed(cx));
5281            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5282        });
5283
5284        // Close the dock
5285        workspace.update(cx, |workspace, cx| {
5286            workspace.toggle_dock(DockPosition::Right, cx);
5287        });
5288
5289        workspace.update(cx, |workspace, cx| {
5290            assert!(!workspace.right_dock().read(cx).is_open());
5291            assert!(!panel.is_zoomed(cx));
5292            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5293        });
5294
5295        // Open the dock
5296        workspace.update(cx, |workspace, cx| {
5297            workspace.toggle_dock(DockPosition::Right, cx);
5298        });
5299
5300        workspace.update(cx, |workspace, cx| {
5301            assert!(workspace.right_dock().read(cx).is_open());
5302            assert!(!panel.is_zoomed(cx));
5303            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5304        });
5305
5306        // Focus and zoom panel
5307        panel.update(cx, |panel, cx| {
5308            cx.focus_self();
5309            panel.set_zoomed(true, cx)
5310        });
5311
5312        workspace.update(cx, |workspace, cx| {
5313            assert!(workspace.right_dock().read(cx).is_open());
5314            assert!(panel.is_zoomed(cx));
5315            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5316        });
5317
5318        // Transfer focus to the center closes the dock
5319        workspace.update(cx, |workspace, cx| {
5320            workspace.toggle_panel_focus::<TestPanel>(cx);
5321        });
5322
5323        workspace.update(cx, |workspace, cx| {
5324            assert!(!workspace.right_dock().read(cx).is_open());
5325            assert!(panel.is_zoomed(cx));
5326            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5327        });
5328
5329        // Transferring focus back to the panel keeps it zoomed
5330        workspace.update(cx, |workspace, cx| {
5331            workspace.toggle_panel_focus::<TestPanel>(cx);
5332        });
5333
5334        workspace.update(cx, |workspace, cx| {
5335            assert!(workspace.right_dock().read(cx).is_open());
5336            assert!(panel.is_zoomed(cx));
5337            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5338        });
5339
5340        // Close the dock while it is zoomed
5341        workspace.update(cx, |workspace, cx| {
5342            workspace.toggle_dock(DockPosition::Right, cx)
5343        });
5344
5345        workspace.update(cx, |workspace, cx| {
5346            assert!(!workspace.right_dock().read(cx).is_open());
5347            assert!(panel.is_zoomed(cx));
5348            assert!(workspace.zoomed.is_none());
5349            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5350        });
5351
5352        // Opening the dock, when it's zoomed, retains focus
5353        workspace.update(cx, |workspace, cx| {
5354            workspace.toggle_dock(DockPosition::Right, cx)
5355        });
5356
5357        workspace.update(cx, |workspace, cx| {
5358            assert!(workspace.right_dock().read(cx).is_open());
5359            assert!(panel.is_zoomed(cx));
5360            assert!(workspace.zoomed.is_some());
5361            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5362        });
5363
5364        // Unzoom and close the panel, zoom the active pane.
5365        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5366        workspace.update(cx, |workspace, cx| {
5367            workspace.toggle_dock(DockPosition::Right, cx)
5368        });
5369        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5370
5371        // Opening a dock unzooms the pane.
5372        workspace.update(cx, |workspace, cx| {
5373            workspace.toggle_dock(DockPosition::Right, cx)
5374        });
5375        workspace.update(cx, |workspace, cx| {
5376            let pane = pane.read(cx);
5377            assert!(!pane.is_zoomed());
5378            assert!(!pane.focus_handle(cx).is_focused(cx));
5379            assert!(workspace.right_dock().read(cx).is_open());
5380            assert!(workspace.zoomed.is_none());
5381        });
5382    }
5383
5384    struct TestModal(FocusHandle);
5385
5386    impl TestModal {
5387        fn new(cx: &mut ViewContext<Self>) -> Self {
5388            Self(cx.focus_handle())
5389        }
5390    }
5391
5392    impl EventEmitter<DismissEvent> for TestModal {}
5393
5394    impl FocusableView for TestModal {
5395        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5396            self.0.clone()
5397        }
5398    }
5399
5400    impl ModalView for TestModal {}
5401
5402    impl Render for TestModal {
5403        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5404            div().track_focus(&self.0)
5405        }
5406    }
5407
5408    #[gpui::test]
5409    async fn test_panels(cx: &mut gpui::TestAppContext) {
5410        init_test(cx);
5411        let fs = FakeFs::new(cx.executor());
5412
5413        let project = Project::test(fs, [], cx).await;
5414        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5415
5416        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5417            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5418            workspace.add_panel(panel_1.clone(), cx);
5419            workspace
5420                .left_dock()
5421                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5422            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5423            workspace.add_panel(panel_2.clone(), cx);
5424            workspace
5425                .right_dock()
5426                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5427
5428            let left_dock = workspace.left_dock();
5429            assert_eq!(
5430                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5431                panel_1.panel_id()
5432            );
5433            assert_eq!(
5434                left_dock.read(cx).active_panel_size(cx).unwrap(),
5435                panel_1.size(cx)
5436            );
5437
5438            left_dock.update(cx, |left_dock, cx| {
5439                left_dock.resize_active_panel(Some(px(1337.)), cx)
5440            });
5441            assert_eq!(
5442                workspace
5443                    .right_dock()
5444                    .read(cx)
5445                    .visible_panel()
5446                    .unwrap()
5447                    .panel_id(),
5448                panel_2.panel_id(),
5449            );
5450
5451            (panel_1, panel_2)
5452        });
5453
5454        // Move panel_1 to the right
5455        panel_1.update(cx, |panel_1, cx| {
5456            panel_1.set_position(DockPosition::Right, cx)
5457        });
5458
5459        workspace.update(cx, |workspace, cx| {
5460            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5461            // Since it was the only panel on the left, the left dock should now be closed.
5462            assert!(!workspace.left_dock().read(cx).is_open());
5463            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5464            let right_dock = workspace.right_dock();
5465            assert_eq!(
5466                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5467                panel_1.panel_id()
5468            );
5469            assert_eq!(
5470                right_dock.read(cx).active_panel_size(cx).unwrap(),
5471                px(1337.)
5472            );
5473
5474            // Now we move panel_2 to the left
5475            panel_2.set_position(DockPosition::Left, cx);
5476        });
5477
5478        workspace.update(cx, |workspace, cx| {
5479            // Since panel_2 was not visible on the right, we don't open the left dock.
5480            assert!(!workspace.left_dock().read(cx).is_open());
5481            // And the right dock is unaffected in it's displaying of panel_1
5482            assert!(workspace.right_dock().read(cx).is_open());
5483            assert_eq!(
5484                workspace
5485                    .right_dock()
5486                    .read(cx)
5487                    .visible_panel()
5488                    .unwrap()
5489                    .panel_id(),
5490                panel_1.panel_id(),
5491            );
5492        });
5493
5494        // Move panel_1 back to the left
5495        panel_1.update(cx, |panel_1, cx| {
5496            panel_1.set_position(DockPosition::Left, cx)
5497        });
5498
5499        workspace.update(cx, |workspace, cx| {
5500            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5501            let left_dock = workspace.left_dock();
5502            assert!(left_dock.read(cx).is_open());
5503            assert_eq!(
5504                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5505                panel_1.panel_id()
5506            );
5507            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5508            // And the right dock should be closed as it no longer has any panels.
5509            assert!(!workspace.right_dock().read(cx).is_open());
5510
5511            // Now we move panel_1 to the bottom
5512            panel_1.set_position(DockPosition::Bottom, cx);
5513        });
5514
5515        workspace.update(cx, |workspace, cx| {
5516            // Since panel_1 was visible on the left, we close the left dock.
5517            assert!(!workspace.left_dock().read(cx).is_open());
5518            // The bottom dock is sized based on the panel's default size,
5519            // since the panel orientation changed from vertical to horizontal.
5520            let bottom_dock = workspace.bottom_dock();
5521            assert_eq!(
5522                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5523                panel_1.size(cx),
5524            );
5525            // Close bottom dock and move panel_1 back to the left.
5526            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5527            panel_1.set_position(DockPosition::Left, cx);
5528        });
5529
5530        // Emit activated event on panel 1
5531        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5532
5533        // Now the left dock is open and panel_1 is active and focused.
5534        workspace.update(cx, |workspace, cx| {
5535            let left_dock = workspace.left_dock();
5536            assert!(left_dock.read(cx).is_open());
5537            assert_eq!(
5538                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5539                panel_1.panel_id(),
5540            );
5541            assert!(panel_1.focus_handle(cx).is_focused(cx));
5542        });
5543
5544        // Emit closed event on panel 2, which is not active
5545        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5546
5547        // Wo don't close the left dock, because panel_2 wasn't the active panel
5548        workspace.update(cx, |workspace, cx| {
5549            let left_dock = workspace.left_dock();
5550            assert!(left_dock.read(cx).is_open());
5551            assert_eq!(
5552                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5553                panel_1.panel_id(),
5554            );
5555        });
5556
5557        // Emitting a ZoomIn event shows the panel as zoomed.
5558        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5559        workspace.update(cx, |workspace, _| {
5560            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5561            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5562        });
5563
5564        // Move panel to another dock while it is zoomed
5565        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5566        workspace.update(cx, |workspace, _| {
5567            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5568
5569            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5570        });
5571
5572        // This is a helper for getting a:
5573        // - valid focus on an element,
5574        // - that isn't a part of the panes and panels system of the Workspace,
5575        // - and doesn't trigger the 'on_focus_lost' API.
5576        let focus_other_view = {
5577            let workspace = workspace.clone();
5578            move |cx: &mut VisualTestContext| {
5579                workspace.update(cx, |workspace, cx| {
5580                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5581                        workspace.toggle_modal(cx, TestModal::new);
5582                        workspace.toggle_modal(cx, TestModal::new);
5583                    } else {
5584                        workspace.toggle_modal(cx, TestModal::new);
5585                    }
5586                })
5587            }
5588        };
5589
5590        // If focus is transferred to another view that's not a panel or another pane, we still show
5591        // the panel as zoomed.
5592        focus_other_view(cx);
5593        workspace.update(cx, |workspace, _| {
5594            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5595            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5596        });
5597
5598        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5599        workspace.update(cx, |_, cx| cx.focus_self());
5600        workspace.update(cx, |workspace, _| {
5601            assert_eq!(workspace.zoomed, None);
5602            assert_eq!(workspace.zoomed_position, None);
5603        });
5604
5605        // If focus is transferred again to another view that's not a panel or a pane, we won't
5606        // show the panel as zoomed because it wasn't zoomed before.
5607        focus_other_view(cx);
5608        workspace.update(cx, |workspace, _| {
5609            assert_eq!(workspace.zoomed, None);
5610            assert_eq!(workspace.zoomed_position, None);
5611        });
5612
5613        // When the panel is activated, it is zoomed again.
5614        cx.dispatch_action(ToggleRightDock);
5615        workspace.update(cx, |workspace, _| {
5616            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5617            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5618        });
5619
5620        // Emitting a ZoomOut event unzooms the panel.
5621        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5622        workspace.update(cx, |workspace, _| {
5623            assert_eq!(workspace.zoomed, None);
5624            assert_eq!(workspace.zoomed_position, None);
5625        });
5626
5627        // Emit closed event on panel 1, which is active
5628        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5629
5630        // Now the left dock is closed, because panel_1 was the active panel
5631        workspace.update(cx, |workspace, cx| {
5632            let right_dock = workspace.right_dock();
5633            assert!(!right_dock.read(cx).is_open());
5634        });
5635    }
5636
5637    pub fn init_test(cx: &mut TestAppContext) {
5638        cx.update(|cx| {
5639            let settings_store = SettingsStore::test(cx);
5640            cx.set_global(settings_store);
5641            theme::init(theme::LoadThemes::JustBase, cx);
5642            language::init(cx);
5643            crate::init_settings(cx);
5644            Project::init_settings(cx);
5645        });
5646    }
5647}