workspace.rs

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