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