workspace.rs

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