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