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, channel::ZedVersion, 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            let version = cx.global::<ZedVersion>().0;
3201            if version.contains("0.91") {
3202                panic!("Please remove the dock key binding change notification");
3203            }
3204            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3205        })
3206        .unwrap_or(false)
3207    {
3208        return;
3209    }
3210
3211    if db::kvp::KEY_VALUE_STORE
3212        .read_kvp(NEW_DOCK_HINT_KEY)
3213        .ok()
3214        .flatten()
3215        .is_some()
3216    {
3217        if !workspace
3218            .read_with(cx, |workspace, cx| {
3219                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3220            })
3221            .unwrap_or(false)
3222        {
3223            cx.update(|cx| {
3224                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3225                    let entry = tracker
3226                        .entry(TypeId::of::<MessageNotification>())
3227                        .or_default();
3228                    if !entry.contains(&MESSAGE_ID) {
3229                        entry.push(MESSAGE_ID);
3230                    }
3231                });
3232            });
3233        }
3234
3235        return;
3236    }
3237
3238    cx.spawn(|_| async move {
3239        db::kvp::KEY_VALUE_STORE
3240            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3241            .await
3242            .ok();
3243    })
3244    .detach();
3245
3246    workspace
3247        .update(cx, |workspace, cx| {
3248            workspace.show_notification_once(2, cx, |cx| {
3249                cx.add_view(|_| {
3250                    MessageNotification::new_element(|text, _| {
3251                        Text::new(
3252                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3253                            text,
3254                        )
3255                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
3256                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3257                                .theme
3258                                .editor
3259                                .document_highlight_read_background;
3260
3261                            scene.push_quad(gpui::Quad {
3262                                bounds,
3263                                background: Some(code_span_background_color),
3264                                border: Default::default(),
3265                                corner_radius: 2.0,
3266                            })
3267                        })
3268                        .into_any()
3269                    })
3270                    .with_click_message("Read more about the new panel system")
3271                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3272                })
3273            })
3274        })
3275        .ok();
3276}
3277
3278fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3279    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3280
3281    workspace
3282        .update(cx, |workspace, cx| {
3283            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3284                workspace.show_notification_once(0, cx, |cx| {
3285                    cx.add_view(|_| {
3286                        MessageNotification::new("Failed to load any database file.")
3287                            .with_click_message("Click to let us know about this error")
3288                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3289                    })
3290                });
3291            } else {
3292                let backup_path = (*db::BACKUP_DB_PATH).read();
3293                if let Some(backup_path) = backup_path.clone() {
3294                    workspace.show_notification_once(1, cx, move |cx| {
3295                        cx.add_view(move |_| {
3296                            MessageNotification::new(format!(
3297                                "Database file was corrupted. Old database backed up to {}",
3298                                backup_path.display()
3299                            ))
3300                            .with_click_message("Click to show old database in finder")
3301                            .on_click(move |cx| {
3302                                cx.platform().open_url(&backup_path.to_string_lossy())
3303                            })
3304                        })
3305                    });
3306                }
3307            }
3308        })
3309        .log_err();
3310}
3311
3312impl Entity for Workspace {
3313    type Event = Event;
3314}
3315
3316impl View for Workspace {
3317    fn ui_name() -> &'static str {
3318        "Workspace"
3319    }
3320
3321    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3322        let theme = theme::current(cx).clone();
3323        Stack::new()
3324            .with_child(
3325                Flex::column()
3326                    .with_child(self.render_titlebar(&theme, cx))
3327                    .with_child(
3328                        Stack::new()
3329                            .with_child({
3330                                let project = self.project.clone();
3331                                Flex::row()
3332                                    .with_children(self.render_dock(DockPosition::Left, cx))
3333                                    .with_child(
3334                                        Flex::column()
3335                                            .with_child(
3336                                                FlexItem::new(
3337                                                    self.center.render(
3338                                                        &project,
3339                                                        &theme,
3340                                                        &self.follower_states_by_leader,
3341                                                        self.active_call(),
3342                                                        self.active_pane(),
3343                                                        self.zoomed
3344                                                            .as_ref()
3345                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3346                                                            .as_ref(),
3347                                                        &self.app_state,
3348                                                        cx,
3349                                                    ),
3350                                                )
3351                                                .flex(1., true),
3352                                            )
3353                                            .with_children(
3354                                                self.render_dock(DockPosition::Bottom, cx),
3355                                            )
3356                                            .flex(1., true),
3357                                    )
3358                                    .with_children(self.render_dock(DockPosition::Right, cx))
3359                            })
3360                            .with_child(Overlay::new(
3361                                Stack::new()
3362                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3363                                        enum ZoomBackground {}
3364                                        let zoomed = zoomed.upgrade(cx)?;
3365
3366                                        let mut foreground_style;
3367                                        match self.zoomed_position {
3368                                            Some(DockPosition::Left) => {
3369                                                foreground_style =
3370                                                    theme.workspace.zoomed_panel_foreground;
3371                                                foreground_style.margin.left = 0.;
3372                                                foreground_style.margin.top = 0.;
3373                                                foreground_style.margin.bottom = 0.;
3374                                            }
3375                                            Some(DockPosition::Right) => {
3376                                                foreground_style =
3377                                                    theme.workspace.zoomed_panel_foreground;
3378                                                foreground_style.margin.right = 0.;
3379                                                foreground_style.margin.top = 0.;
3380                                                foreground_style.margin.bottom = 0.;
3381                                            }
3382                                            Some(DockPosition::Bottom) => {
3383                                                foreground_style =
3384                                                    theme.workspace.zoomed_panel_foreground;
3385                                                foreground_style.margin.left = 0.;
3386                                                foreground_style.margin.right = 0.;
3387                                                foreground_style.margin.bottom = 0.;
3388                                            }
3389                                            None => {
3390                                                foreground_style =
3391                                                    theme.workspace.zoomed_pane_foreground;
3392                                            }
3393                                        }
3394
3395                                        Some(
3396                                            ChildView::new(&zoomed, cx)
3397                                                .contained()
3398                                                .with_style(foreground_style)
3399                                                .aligned()
3400                                                .contained()
3401                                                .with_style(theme.workspace.zoomed_background)
3402                                                .mouse::<ZoomBackground>(0)
3403                                                .capture_all()
3404                                                .on_down(
3405                                                    MouseButton::Left,
3406                                                    |_, this: &mut Self, cx| {
3407                                                        this.zoom_out(cx);
3408                                                    },
3409                                                ),
3410                                        )
3411                                    }))
3412                                    .with_children(self.modal.as_ref().map(|modal| {
3413                                        ChildView::new(modal, cx)
3414                                            .contained()
3415                                            .with_style(theme.workspace.modal)
3416                                            .aligned()
3417                                            .top()
3418                                    }))
3419                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3420                            ))
3421                            .flex(1.0, true),
3422                    )
3423                    .with_child(ChildView::new(&self.status_bar, cx))
3424                    .contained()
3425                    .with_background_color(theme.workspace.background),
3426            )
3427            .with_children(DragAndDrop::render(cx))
3428            .with_children(self.render_disconnected_overlay(cx))
3429            .into_any_named("workspace")
3430    }
3431
3432    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3433        if cx.is_self_focused() {
3434            cx.focus(&self.active_pane);
3435        }
3436    }
3437}
3438
3439impl ViewId {
3440    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3441        Ok(Self {
3442            creator: message
3443                .creator
3444                .ok_or_else(|| anyhow!("creator is missing"))?,
3445            id: message.id,
3446        })
3447    }
3448
3449    pub(crate) fn to_proto(&self) -> proto::ViewId {
3450        proto::ViewId {
3451            creator: Some(self.creator),
3452            id: self.id,
3453        }
3454    }
3455}
3456
3457pub trait WorkspaceHandle {
3458    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3459}
3460
3461impl WorkspaceHandle for ViewHandle<Workspace> {
3462    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3463        self.read(cx)
3464            .worktrees(cx)
3465            .flat_map(|worktree| {
3466                let worktree_id = worktree.read(cx).id();
3467                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3468                    worktree_id,
3469                    path: f.path.clone(),
3470                })
3471            })
3472            .collect::<Vec<_>>()
3473    }
3474}
3475
3476impl std::fmt::Debug for OpenPaths {
3477    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3478        f.debug_struct("OpenPaths")
3479            .field("paths", &self.paths)
3480            .finish()
3481    }
3482}
3483
3484pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3485
3486pub fn activate_workspace_for_project(
3487    cx: &mut AsyncAppContext,
3488    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3489) -> Option<WeakViewHandle<Workspace>> {
3490    for window_id in cx.window_ids() {
3491        let handle = cx
3492            .update_window(window_id, |cx| {
3493                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3494                    let project = workspace_handle.read(cx).project.clone();
3495                    if project.update(cx, &predicate) {
3496                        cx.activate_window();
3497                        return Some(workspace_handle.clone());
3498                    }
3499                }
3500                None
3501            })
3502            .flatten();
3503
3504        if let Some(handle) = handle {
3505            return Some(handle.downgrade());
3506        }
3507    }
3508    None
3509}
3510
3511pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3512    DB.last_workspace().await.log_err().flatten()
3513}
3514
3515#[allow(clippy::type_complexity)]
3516pub fn open_paths(
3517    abs_paths: &[PathBuf],
3518    app_state: &Arc<AppState>,
3519    requesting_window_id: Option<usize>,
3520    cx: &mut AppContext,
3521) -> Task<
3522    Result<(
3523        WeakViewHandle<Workspace>,
3524        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3525    )>,
3526> {
3527    let app_state = app_state.clone();
3528    let abs_paths = abs_paths.to_vec();
3529    cx.spawn(|mut cx| async move {
3530        // Open paths in existing workspace if possible
3531        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3532            project.contains_paths(&abs_paths, cx)
3533        });
3534
3535        if let Some(existing) = existing {
3536            Ok((
3537                existing.clone(),
3538                existing
3539                    .update(&mut cx, |workspace, cx| {
3540                        workspace.open_paths(abs_paths, true, cx)
3541                    })?
3542                    .await,
3543            ))
3544        } else {
3545            Ok(cx
3546                .update(|cx| {
3547                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3548                })
3549                .await)
3550        }
3551    })
3552}
3553
3554pub fn open_new(
3555    app_state: &Arc<AppState>,
3556    cx: &mut AppContext,
3557    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3558) -> Task<()> {
3559    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3560    cx.spawn(|mut cx| async move {
3561        let (workspace, opened_paths) = task.await;
3562
3563        workspace
3564            .update(&mut cx, |workspace, cx| {
3565                if opened_paths.is_empty() {
3566                    init(workspace, cx)
3567                }
3568            })
3569            .log_err();
3570    })
3571}
3572
3573pub fn create_and_open_local_file(
3574    path: &'static Path,
3575    cx: &mut ViewContext<Workspace>,
3576    default_content: impl 'static + Send + FnOnce() -> Rope,
3577) -> Task<Result<Box<dyn ItemHandle>>> {
3578    cx.spawn(|workspace, mut cx| async move {
3579        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3580        if !fs.is_file(path).await {
3581            fs.create_file(path, Default::default()).await?;
3582            fs.save(path, &default_content(), Default::default())
3583                .await?;
3584        }
3585
3586        let mut items = workspace
3587            .update(&mut cx, |workspace, cx| {
3588                workspace.with_local_workspace(cx, |workspace, cx| {
3589                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3590                })
3591            })?
3592            .await?
3593            .await;
3594
3595        let item = items.pop().flatten();
3596        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3597    })
3598}
3599
3600pub fn join_remote_project(
3601    project_id: u64,
3602    follow_user_id: u64,
3603    app_state: Arc<AppState>,
3604    cx: &mut AppContext,
3605) -> Task<Result<()>> {
3606    cx.spawn(|mut cx| async move {
3607        let existing_workspace = cx
3608            .window_ids()
3609            .into_iter()
3610            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3611            .find(|workspace| {
3612                cx.read_window(workspace.window_id(), |cx| {
3613                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3614                })
3615                .unwrap_or(false)
3616            });
3617
3618        let workspace = if let Some(existing_workspace) = existing_workspace {
3619            existing_workspace.downgrade()
3620        } else {
3621            let active_call = cx.read(ActiveCall::global);
3622            let room = active_call
3623                .read_with(&cx, |call, _| call.room().cloned())
3624                .ok_or_else(|| anyhow!("not in a call"))?;
3625            let project = room
3626                .update(&mut cx, |room, cx| {
3627                    room.join_project(
3628                        project_id,
3629                        app_state.languages.clone(),
3630                        app_state.fs.clone(),
3631                        cx,
3632                    )
3633                })
3634                .await?;
3635
3636            let (_, workspace) = cx.add_window(
3637                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3638                |cx| Workspace::new(0, project, app_state.clone(), cx),
3639            );
3640            (app_state.initialize_workspace)(
3641                workspace.downgrade(),
3642                false,
3643                app_state.clone(),
3644                cx.clone(),
3645            )
3646            .await
3647            .log_err();
3648
3649            workspace.downgrade()
3650        };
3651
3652        cx.activate_window(workspace.window_id());
3653        cx.platform().activate(true);
3654
3655        workspace.update(&mut cx, |workspace, cx| {
3656            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3657                let follow_peer_id = room
3658                    .read(cx)
3659                    .remote_participants()
3660                    .iter()
3661                    .find(|(_, participant)| participant.user.id == follow_user_id)
3662                    .map(|(_, p)| p.peer_id)
3663                    .or_else(|| {
3664                        // If we couldn't follow the given user, follow the host instead.
3665                        let collaborator = workspace
3666                            .project()
3667                            .read(cx)
3668                            .collaborators()
3669                            .values()
3670                            .find(|collaborator| collaborator.replica_id == 0)?;
3671                        Some(collaborator.peer_id)
3672                    });
3673
3674                if let Some(follow_peer_id) = follow_peer_id {
3675                    if !workspace.is_being_followed(follow_peer_id) {
3676                        workspace
3677                            .toggle_follow(follow_peer_id, cx)
3678                            .map(|follow| follow.detach_and_log_err(cx));
3679                    }
3680                }
3681            }
3682        })?;
3683
3684        anyhow::Ok(())
3685    })
3686}
3687
3688pub fn restart(_: &Restart, cx: &mut AppContext) {
3689    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3690    cx.spawn(|mut cx| async move {
3691        let mut workspaces = cx
3692            .window_ids()
3693            .into_iter()
3694            .filter_map(|window_id| {
3695                Some(
3696                    cx.root_view(window_id)?
3697                        .clone()
3698                        .downcast::<Workspace>()?
3699                        .downgrade(),
3700                )
3701            })
3702            .collect::<Vec<_>>();
3703
3704        // If multiple windows have unsaved changes, and need a save prompt,
3705        // prompt in the active window before switching to a different window.
3706        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3707
3708        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3709            let answer = cx.prompt(
3710                workspace.window_id(),
3711                PromptLevel::Info,
3712                "Are you sure you want to restart?",
3713                &["Restart", "Cancel"],
3714            );
3715
3716            if let Some(mut answer) = answer {
3717                let answer = answer.next().await;
3718                if answer != Some(0) {
3719                    return Ok(());
3720                }
3721            }
3722        }
3723
3724        // If the user cancels any save prompt, then keep the app open.
3725        for workspace in workspaces {
3726            if !workspace
3727                .update(&mut cx, |workspace, cx| {
3728                    workspace.prepare_to_close(true, cx)
3729                })?
3730                .await?
3731            {
3732                return Ok(());
3733            }
3734        }
3735        cx.platform().restart();
3736        anyhow::Ok(())
3737    })
3738    .detach_and_log_err(cx);
3739}
3740
3741fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3742    let mut parts = value.split(',');
3743    let width: usize = parts.next()?.parse().ok()?;
3744    let height: usize = parts.next()?.parse().ok()?;
3745    Some(vec2f(width as f32, height as f32))
3746}
3747
3748#[cfg(test)]
3749mod tests {
3750    use super::*;
3751    use crate::{
3752        dock::test::{TestPanel, TestPanelEvent},
3753        item::test::{TestItem, TestItemEvent, TestProjectItem},
3754    };
3755    use fs::FakeFs;
3756    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3757    use project::{Project, ProjectEntryId};
3758    use serde_json::json;
3759    use settings::SettingsStore;
3760    use std::{cell::RefCell, rc::Rc};
3761
3762    #[gpui::test]
3763    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3764        init_test(cx);
3765
3766        let fs = FakeFs::new(cx.background());
3767        let project = Project::test(fs, [], cx).await;
3768        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3769
3770        // Adding an item with no ambiguity renders the tab without detail.
3771        let item1 = cx.add_view(window_id, |_| {
3772            let mut item = TestItem::new();
3773            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3774            item
3775        });
3776        workspace.update(cx, |workspace, cx| {
3777            workspace.add_item(Box::new(item1.clone()), cx);
3778        });
3779        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3780
3781        // Adding an item that creates ambiguity increases the level of detail on
3782        // both tabs.
3783        let item2 = cx.add_view(window_id, |_| {
3784            let mut item = TestItem::new();
3785            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3786            item
3787        });
3788        workspace.update(cx, |workspace, cx| {
3789            workspace.add_item(Box::new(item2.clone()), cx);
3790        });
3791        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3792        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3793
3794        // Adding an item that creates ambiguity increases the level of detail only
3795        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3796        // we stop at the highest detail available.
3797        let item3 = cx.add_view(window_id, |_| {
3798            let mut item = TestItem::new();
3799            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3800            item
3801        });
3802        workspace.update(cx, |workspace, cx| {
3803            workspace.add_item(Box::new(item3.clone()), cx);
3804        });
3805        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3806        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3807        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3808    }
3809
3810    #[gpui::test]
3811    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3812        init_test(cx);
3813
3814        let fs = FakeFs::new(cx.background());
3815        fs.insert_tree(
3816            "/root1",
3817            json!({
3818                "one.txt": "",
3819                "two.txt": "",
3820            }),
3821        )
3822        .await;
3823        fs.insert_tree(
3824            "/root2",
3825            json!({
3826                "three.txt": "",
3827            }),
3828        )
3829        .await;
3830
3831        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3832        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3833        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3834        let worktree_id = project.read_with(cx, |project, cx| {
3835            project.worktrees(cx).next().unwrap().read(cx).id()
3836        });
3837
3838        let item1 = cx.add_view(window_id, |cx| {
3839            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3840        });
3841        let item2 = cx.add_view(window_id, |cx| {
3842            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3843        });
3844
3845        // Add an item to an empty pane
3846        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3847        project.read_with(cx, |project, cx| {
3848            assert_eq!(
3849                project.active_entry(),
3850                project
3851                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3852                    .map(|e| e.id)
3853            );
3854        });
3855        assert_eq!(
3856            cx.current_window_title(window_id).as_deref(),
3857            Some("one.txt β€” root1")
3858        );
3859
3860        // Add a second item to a non-empty pane
3861        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3862        assert_eq!(
3863            cx.current_window_title(window_id).as_deref(),
3864            Some("two.txt β€” root1")
3865        );
3866        project.read_with(cx, |project, cx| {
3867            assert_eq!(
3868                project.active_entry(),
3869                project
3870                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3871                    .map(|e| e.id)
3872            );
3873        });
3874
3875        // Close the active item
3876        pane.update(cx, |pane, cx| {
3877            pane.close_active_item(&Default::default(), cx).unwrap()
3878        })
3879        .await
3880        .unwrap();
3881        assert_eq!(
3882            cx.current_window_title(window_id).as_deref(),
3883            Some("one.txt β€” root1")
3884        );
3885        project.read_with(cx, |project, cx| {
3886            assert_eq!(
3887                project.active_entry(),
3888                project
3889                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3890                    .map(|e| e.id)
3891            );
3892        });
3893
3894        // Add a project folder
3895        project
3896            .update(cx, |project, cx| {
3897                project.find_or_create_local_worktree("/root2", true, cx)
3898            })
3899            .await
3900            .unwrap();
3901        assert_eq!(
3902            cx.current_window_title(window_id).as_deref(),
3903            Some("one.txt β€” root1, root2")
3904        );
3905
3906        // Remove a project folder
3907        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3908        assert_eq!(
3909            cx.current_window_title(window_id).as_deref(),
3910            Some("one.txt β€” root2")
3911        );
3912    }
3913
3914    #[gpui::test]
3915    async fn test_close_window(cx: &mut TestAppContext) {
3916        init_test(cx);
3917
3918        let fs = FakeFs::new(cx.background());
3919        fs.insert_tree("/root", json!({ "one": "" })).await;
3920
3921        let project = Project::test(fs, ["root".as_ref()], cx).await;
3922        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3923
3924        // When there are no dirty items, there's nothing to do.
3925        let item1 = cx.add_view(window_id, |_| TestItem::new());
3926        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3927        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3928        assert!(task.await.unwrap());
3929
3930        // When there are dirty untitled items, prompt to save each one. If the user
3931        // cancels any prompt, then abort.
3932        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3933        let item3 = cx.add_view(window_id, |cx| {
3934            TestItem::new()
3935                .with_dirty(true)
3936                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3937        });
3938        workspace.update(cx, |w, cx| {
3939            w.add_item(Box::new(item2.clone()), cx);
3940            w.add_item(Box::new(item3.clone()), cx);
3941        });
3942        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3943        cx.foreground().run_until_parked();
3944        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3945        cx.foreground().run_until_parked();
3946        assert!(!cx.has_pending_prompt(window_id));
3947        assert!(!task.await.unwrap());
3948    }
3949
3950    #[gpui::test]
3951    async fn test_close_pane_items(cx: &mut TestAppContext) {
3952        init_test(cx);
3953
3954        let fs = FakeFs::new(cx.background());
3955
3956        let project = Project::test(fs, None, cx).await;
3957        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3958
3959        let item1 = cx.add_view(window_id, |cx| {
3960            TestItem::new()
3961                .with_dirty(true)
3962                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3963        });
3964        let item2 = cx.add_view(window_id, |cx| {
3965            TestItem::new()
3966                .with_dirty(true)
3967                .with_conflict(true)
3968                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3969        });
3970        let item3 = cx.add_view(window_id, |cx| {
3971            TestItem::new()
3972                .with_dirty(true)
3973                .with_conflict(true)
3974                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3975        });
3976        let item4 = cx.add_view(window_id, |cx| {
3977            TestItem::new()
3978                .with_dirty(true)
3979                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3980        });
3981        let pane = workspace.update(cx, |workspace, cx| {
3982            workspace.add_item(Box::new(item1.clone()), cx);
3983            workspace.add_item(Box::new(item2.clone()), cx);
3984            workspace.add_item(Box::new(item3.clone()), cx);
3985            workspace.add_item(Box::new(item4.clone()), cx);
3986            workspace.active_pane().clone()
3987        });
3988
3989        let close_items = pane.update(cx, |pane, cx| {
3990            pane.activate_item(1, true, true, cx);
3991            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3992            let item1_id = item1.id();
3993            let item3_id = item3.id();
3994            let item4_id = item4.id();
3995            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3996        });
3997        cx.foreground().run_until_parked();
3998
3999        // There's a prompt to save item 1.
4000        pane.read_with(cx, |pane, _| {
4001            assert_eq!(pane.items_len(), 4);
4002            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4003        });
4004        assert!(cx.has_pending_prompt(window_id));
4005
4006        // Confirm saving item 1.
4007        cx.simulate_prompt_answer(window_id, 0);
4008        cx.foreground().run_until_parked();
4009
4010        // Item 1 is saved. There's a prompt to save item 3.
4011        pane.read_with(cx, |pane, cx| {
4012            assert_eq!(item1.read(cx).save_count, 1);
4013            assert_eq!(item1.read(cx).save_as_count, 0);
4014            assert_eq!(item1.read(cx).reload_count, 0);
4015            assert_eq!(pane.items_len(), 3);
4016            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4017        });
4018        assert!(cx.has_pending_prompt(window_id));
4019
4020        // Cancel saving item 3.
4021        cx.simulate_prompt_answer(window_id, 1);
4022        cx.foreground().run_until_parked();
4023
4024        // Item 3 is reloaded. There's a prompt to save item 4.
4025        pane.read_with(cx, |pane, cx| {
4026            assert_eq!(item3.read(cx).save_count, 0);
4027            assert_eq!(item3.read(cx).save_as_count, 0);
4028            assert_eq!(item3.read(cx).reload_count, 1);
4029            assert_eq!(pane.items_len(), 2);
4030            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4031        });
4032        assert!(cx.has_pending_prompt(window_id));
4033
4034        // Confirm saving item 4.
4035        cx.simulate_prompt_answer(window_id, 0);
4036        cx.foreground().run_until_parked();
4037
4038        // There's a prompt for a path for item 4.
4039        cx.simulate_new_path_selection(|_| Some(Default::default()));
4040        close_items.await.unwrap();
4041
4042        // The requested items are closed.
4043        pane.read_with(cx, |pane, cx| {
4044            assert_eq!(item4.read(cx).save_count, 0);
4045            assert_eq!(item4.read(cx).save_as_count, 1);
4046            assert_eq!(item4.read(cx).reload_count, 0);
4047            assert_eq!(pane.items_len(), 1);
4048            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4049        });
4050    }
4051
4052    #[gpui::test]
4053    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4054        init_test(cx);
4055
4056        let fs = FakeFs::new(cx.background());
4057
4058        let project = Project::test(fs, [], cx).await;
4059        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4060
4061        // Create several workspace items with single project entries, and two
4062        // workspace items with multiple project entries.
4063        let single_entry_items = (0..=4)
4064            .map(|project_entry_id| {
4065                cx.add_view(window_id, |cx| {
4066                    TestItem::new()
4067                        .with_dirty(true)
4068                        .with_project_items(&[TestProjectItem::new(
4069                            project_entry_id,
4070                            &format!("{project_entry_id}.txt"),
4071                            cx,
4072                        )])
4073                })
4074            })
4075            .collect::<Vec<_>>();
4076        let item_2_3 = cx.add_view(window_id, |cx| {
4077            TestItem::new()
4078                .with_dirty(true)
4079                .with_singleton(false)
4080                .with_project_items(&[
4081                    single_entry_items[2].read(cx).project_items[0].clone(),
4082                    single_entry_items[3].read(cx).project_items[0].clone(),
4083                ])
4084        });
4085        let item_3_4 = cx.add_view(window_id, |cx| {
4086            TestItem::new()
4087                .with_dirty(true)
4088                .with_singleton(false)
4089                .with_project_items(&[
4090                    single_entry_items[3].read(cx).project_items[0].clone(),
4091                    single_entry_items[4].read(cx).project_items[0].clone(),
4092                ])
4093        });
4094
4095        // Create two panes that contain the following project entries:
4096        //   left pane:
4097        //     multi-entry items:   (2, 3)
4098        //     single-entry items:  0, 1, 2, 3, 4
4099        //   right pane:
4100        //     single-entry items:  1
4101        //     multi-entry items:   (3, 4)
4102        let left_pane = workspace.update(cx, |workspace, cx| {
4103            let left_pane = workspace.active_pane().clone();
4104            workspace.add_item(Box::new(item_2_3.clone()), cx);
4105            for item in single_entry_items {
4106                workspace.add_item(Box::new(item), cx);
4107            }
4108            left_pane.update(cx, |pane, cx| {
4109                pane.activate_item(2, true, true, cx);
4110            });
4111
4112            workspace
4113                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
4114                .unwrap();
4115
4116            left_pane
4117        });
4118
4119        //Need to cause an effect flush in order to respect new focus
4120        workspace.update(cx, |workspace, cx| {
4121            workspace.add_item(Box::new(item_3_4.clone()), cx);
4122            cx.focus(&left_pane);
4123        });
4124
4125        // When closing all of the items in the left pane, we should be prompted twice:
4126        // once for project entry 0, and once for project entry 2. After those two
4127        // prompts, the task should complete.
4128
4129        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4130        cx.foreground().run_until_parked();
4131        left_pane.read_with(cx, |pane, cx| {
4132            assert_eq!(
4133                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4134                &[ProjectEntryId::from_proto(0)]
4135            );
4136        });
4137        cx.simulate_prompt_answer(window_id, 0);
4138
4139        cx.foreground().run_until_parked();
4140        left_pane.read_with(cx, |pane, cx| {
4141            assert_eq!(
4142                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4143                &[ProjectEntryId::from_proto(2)]
4144            );
4145        });
4146        cx.simulate_prompt_answer(window_id, 0);
4147
4148        cx.foreground().run_until_parked();
4149        close.await.unwrap();
4150        left_pane.read_with(cx, |pane, _| {
4151            assert_eq!(pane.items_len(), 0);
4152        });
4153    }
4154
4155    #[gpui::test]
4156    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4157        init_test(cx);
4158
4159        let fs = FakeFs::new(cx.background());
4160
4161        let project = Project::test(fs, [], cx).await;
4162        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4163        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4164
4165        let item = cx.add_view(window_id, |cx| {
4166            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4167        });
4168        let item_id = item.id();
4169        workspace.update(cx, |workspace, cx| {
4170            workspace.add_item(Box::new(item.clone()), cx);
4171        });
4172
4173        // Autosave on window change.
4174        item.update(cx, |item, cx| {
4175            cx.update_global(|settings: &mut SettingsStore, cx| {
4176                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4177                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4178                })
4179            });
4180            item.is_dirty = true;
4181        });
4182
4183        // Deactivating the window saves the file.
4184        cx.simulate_window_activation(None);
4185        deterministic.run_until_parked();
4186        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4187
4188        // Autosave on focus change.
4189        item.update(cx, |item, cx| {
4190            cx.focus_self();
4191            cx.update_global(|settings: &mut SettingsStore, cx| {
4192                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4193                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4194                })
4195            });
4196            item.is_dirty = true;
4197        });
4198
4199        // Blurring the item saves the file.
4200        item.update(cx, |_, cx| cx.blur());
4201        deterministic.run_until_parked();
4202        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4203
4204        // Deactivating the window still saves the file.
4205        cx.simulate_window_activation(Some(window_id));
4206        item.update(cx, |item, cx| {
4207            cx.focus_self();
4208            item.is_dirty = true;
4209        });
4210        cx.simulate_window_activation(None);
4211
4212        deterministic.run_until_parked();
4213        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4214
4215        // Autosave after delay.
4216        item.update(cx, |item, cx| {
4217            cx.update_global(|settings: &mut SettingsStore, cx| {
4218                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4219                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4220                })
4221            });
4222            item.is_dirty = true;
4223            cx.emit(TestItemEvent::Edit);
4224        });
4225
4226        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4227        deterministic.advance_clock(Duration::from_millis(250));
4228        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4229
4230        // After delay expires, the file is saved.
4231        deterministic.advance_clock(Duration::from_millis(250));
4232        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4233
4234        // Autosave on focus change, ensuring closing the tab counts as such.
4235        item.update(cx, |item, cx| {
4236            cx.update_global(|settings: &mut SettingsStore, cx| {
4237                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4238                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4239                })
4240            });
4241            item.is_dirty = true;
4242        });
4243
4244        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4245            .await
4246            .unwrap();
4247        assert!(!cx.has_pending_prompt(window_id));
4248        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4249
4250        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4251        workspace.update(cx, |workspace, cx| {
4252            workspace.add_item(Box::new(item.clone()), cx);
4253        });
4254        item.update(cx, |item, cx| {
4255            item.project_items[0].update(cx, |item, _| {
4256                item.entry_id = None;
4257            });
4258            item.is_dirty = true;
4259            cx.blur();
4260        });
4261        deterministic.run_until_parked();
4262        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4263
4264        // Ensure autosave is prevented for deleted files also when closing the buffer.
4265        let _close_items =
4266            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4267        deterministic.run_until_parked();
4268        assert!(cx.has_pending_prompt(window_id));
4269        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4270    }
4271
4272    #[gpui::test]
4273    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4274        init_test(cx);
4275
4276        let fs = FakeFs::new(cx.background());
4277
4278        let project = Project::test(fs, [], cx).await;
4279        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4280
4281        let item = cx.add_view(window_id, |cx| {
4282            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4283        });
4284        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4285        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4286        let toolbar_notify_count = Rc::new(RefCell::new(0));
4287
4288        workspace.update(cx, |workspace, cx| {
4289            workspace.add_item(Box::new(item.clone()), cx);
4290            let toolbar_notification_count = toolbar_notify_count.clone();
4291            cx.observe(&toolbar, move |_, _, _| {
4292                *toolbar_notification_count.borrow_mut() += 1
4293            })
4294            .detach();
4295        });
4296
4297        pane.read_with(cx, |pane, _| {
4298            assert!(!pane.can_navigate_backward());
4299            assert!(!pane.can_navigate_forward());
4300        });
4301
4302        item.update(cx, |item, cx| {
4303            item.set_state("one".to_string(), cx);
4304        });
4305
4306        // Toolbar must be notified to re-render the navigation buttons
4307        assert_eq!(*toolbar_notify_count.borrow(), 1);
4308
4309        pane.read_with(cx, |pane, _| {
4310            assert!(pane.can_navigate_backward());
4311            assert!(!pane.can_navigate_forward());
4312        });
4313
4314        workspace
4315            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4316            .await
4317            .unwrap();
4318
4319        assert_eq!(*toolbar_notify_count.borrow(), 3);
4320        pane.read_with(cx, |pane, _| {
4321            assert!(!pane.can_navigate_backward());
4322            assert!(pane.can_navigate_forward());
4323        });
4324    }
4325
4326    #[gpui::test]
4327    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4328        init_test(cx);
4329        let fs = FakeFs::new(cx.background());
4330
4331        let project = Project::test(fs, [], cx).await;
4332        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4333
4334        let panel = workspace.update(cx, |workspace, cx| {
4335            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4336            workspace.add_panel(panel.clone(), cx);
4337
4338            workspace
4339                .right_dock()
4340                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4341
4342            panel
4343        });
4344
4345        // Transfer focus from center to panel
4346        workspace.update(cx, |workspace, cx| {
4347            workspace.toggle_panel_focus::<TestPanel>(cx);
4348        });
4349
4350        workspace.read_with(cx, |workspace, cx| {
4351            assert!(workspace.right_dock().read(cx).is_open());
4352            assert!(!panel.is_zoomed(cx));
4353            assert!(panel.has_focus(cx));
4354        });
4355
4356        // Transfer focus from panel to center
4357        workspace.update(cx, |workspace, cx| {
4358            workspace.toggle_panel_focus::<TestPanel>(cx);
4359        });
4360
4361        workspace.read_with(cx, |workspace, cx| {
4362            assert!(workspace.right_dock().read(cx).is_open());
4363            assert!(!panel.is_zoomed(cx));
4364            assert!(!panel.has_focus(cx));
4365        });
4366
4367        // Close the dock
4368        workspace.update(cx, |workspace, cx| {
4369            workspace.toggle_dock(DockPosition::Right, cx);
4370        });
4371
4372        workspace.read_with(cx, |workspace, cx| {
4373            assert!(!workspace.right_dock().read(cx).is_open());
4374            assert!(!panel.is_zoomed(cx));
4375            assert!(!panel.has_focus(cx));
4376        });
4377
4378        // Open the dock
4379        workspace.update(cx, |workspace, cx| {
4380            workspace.toggle_dock(DockPosition::Right, cx);
4381        });
4382
4383        workspace.read_with(cx, |workspace, cx| {
4384            assert!(workspace.right_dock().read(cx).is_open());
4385            assert!(!panel.is_zoomed(cx));
4386            assert!(!panel.has_focus(cx));
4387        });
4388
4389        // Focus and zoom panel
4390        panel.update(cx, |panel, cx| {
4391            cx.focus_self();
4392            panel.set_zoomed(true, cx)
4393        });
4394
4395        workspace.read_with(cx, |workspace, cx| {
4396            assert!(workspace.right_dock().read(cx).is_open());
4397            assert!(panel.is_zoomed(cx));
4398            assert!(panel.has_focus(cx));
4399        });
4400
4401        // Transfer focus to the center closes the dock
4402        workspace.update(cx, |workspace, cx| {
4403            workspace.toggle_panel_focus::<TestPanel>(cx);
4404        });
4405
4406        workspace.read_with(cx, |workspace, cx| {
4407            assert!(!workspace.right_dock().read(cx).is_open());
4408            assert!(panel.is_zoomed(cx));
4409            assert!(!panel.has_focus(cx));
4410        });
4411
4412        // Transfering focus back to the panel keeps it zoomed
4413        workspace.update(cx, |workspace, cx| {
4414            workspace.toggle_panel_focus::<TestPanel>(cx);
4415        });
4416
4417        workspace.read_with(cx, |workspace, cx| {
4418            assert!(workspace.right_dock().read(cx).is_open());
4419            assert!(panel.is_zoomed(cx));
4420            assert!(panel.has_focus(cx));
4421        });
4422
4423        // Close the dock while it is zoomed
4424        workspace.update(cx, |workspace, cx| {
4425            workspace.toggle_dock(DockPosition::Right, cx)
4426        });
4427
4428        workspace.read_with(cx, |workspace, cx| {
4429            assert!(!workspace.right_dock().read(cx).is_open());
4430            assert!(panel.is_zoomed(cx));
4431            assert!(workspace.zoomed.is_none());
4432            assert!(!panel.has_focus(cx));
4433        });
4434
4435        // Opening the dock, when it's zoomed, retains focus
4436        workspace.update(cx, |workspace, cx| {
4437            workspace.toggle_dock(DockPosition::Right, cx)
4438        });
4439
4440        workspace.read_with(cx, |workspace, cx| {
4441            assert!(workspace.right_dock().read(cx).is_open());
4442            assert!(panel.is_zoomed(cx));
4443            assert!(workspace.zoomed.is_some());
4444            assert!(panel.has_focus(cx));
4445        });
4446    }
4447
4448    #[gpui::test]
4449    async fn test_panels(cx: &mut gpui::TestAppContext) {
4450        init_test(cx);
4451        let fs = FakeFs::new(cx.background());
4452
4453        let project = Project::test(fs, [], cx).await;
4454        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4455
4456        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4457            // Add panel_1 on the left, panel_2 on the right.
4458            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4459            workspace.add_panel(panel_1.clone(), cx);
4460            workspace
4461                .left_dock()
4462                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4463            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4464            workspace.add_panel(panel_2.clone(), cx);
4465            workspace
4466                .right_dock()
4467                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4468
4469            let left_dock = workspace.left_dock();
4470            assert_eq!(
4471                left_dock.read(cx).visible_panel().unwrap().id(),
4472                panel_1.id()
4473            );
4474            assert_eq!(
4475                left_dock.read(cx).active_panel_size(cx).unwrap(),
4476                panel_1.size(cx)
4477            );
4478
4479            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4480            assert_eq!(
4481                workspace
4482                    .right_dock()
4483                    .read(cx)
4484                    .visible_panel()
4485                    .unwrap()
4486                    .id(),
4487                panel_2.id()
4488            );
4489
4490            (panel_1, panel_2)
4491        });
4492
4493        // Move panel_1 to the right
4494        panel_1.update(cx, |panel_1, cx| {
4495            panel_1.set_position(DockPosition::Right, cx)
4496        });
4497
4498        workspace.update(cx, |workspace, cx| {
4499            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4500            // Since it was the only panel on the left, the left dock should now be closed.
4501            assert!(!workspace.left_dock().read(cx).is_open());
4502            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4503            let right_dock = workspace.right_dock();
4504            assert_eq!(
4505                right_dock.read(cx).visible_panel().unwrap().id(),
4506                panel_1.id()
4507            );
4508            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4509
4510            // Now we move panel_2Β to the left
4511            panel_2.set_position(DockPosition::Left, cx);
4512        });
4513
4514        workspace.update(cx, |workspace, cx| {
4515            // Since panel_2 was not visible on the right, we don't open the left dock.
4516            assert!(!workspace.left_dock().read(cx).is_open());
4517            // And the right dock is unaffected in it's displaying of panel_1
4518            assert!(workspace.right_dock().read(cx).is_open());
4519            assert_eq!(
4520                workspace
4521                    .right_dock()
4522                    .read(cx)
4523                    .visible_panel()
4524                    .unwrap()
4525                    .id(),
4526                panel_1.id()
4527            );
4528        });
4529
4530        // Move panel_1 back to the left
4531        panel_1.update(cx, |panel_1, cx| {
4532            panel_1.set_position(DockPosition::Left, cx)
4533        });
4534
4535        workspace.update(cx, |workspace, cx| {
4536            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4537            let left_dock = workspace.left_dock();
4538            assert!(left_dock.read(cx).is_open());
4539            assert_eq!(
4540                left_dock.read(cx).visible_panel().unwrap().id(),
4541                panel_1.id()
4542            );
4543            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4544            // And right the dock should be closed as it no longer has any panels.
4545            assert!(!workspace.right_dock().read(cx).is_open());
4546
4547            // Now we move panel_1 to the bottom
4548            panel_1.set_position(DockPosition::Bottom, cx);
4549        });
4550
4551        workspace.update(cx, |workspace, cx| {
4552            // Since panel_1 was visible on the left, we close the left dock.
4553            assert!(!workspace.left_dock().read(cx).is_open());
4554            // The bottom dock is sized based on the panel's default size,
4555            // since the panel orientation changed from vertical to horizontal.
4556            let bottom_dock = workspace.bottom_dock();
4557            assert_eq!(
4558                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4559                panel_1.size(cx),
4560            );
4561            // Close bottom dock and move panel_1 back to the left.
4562            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4563            panel_1.set_position(DockPosition::Left, cx);
4564        });
4565
4566        // Emit activated event on panel 1
4567        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4568
4569        // Now the left dock is open and panel_1 is active and focused.
4570        workspace.read_with(cx, |workspace, cx| {
4571            let left_dock = workspace.left_dock();
4572            assert!(left_dock.read(cx).is_open());
4573            assert_eq!(
4574                left_dock.read(cx).visible_panel().unwrap().id(),
4575                panel_1.id()
4576            );
4577            assert!(panel_1.is_focused(cx));
4578        });
4579
4580        // Emit closed event on panel 2, which is not active
4581        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4582
4583        // Wo don't close the left dock, because panel_2 wasn't the active panel
4584        workspace.read_with(cx, |workspace, cx| {
4585            let left_dock = workspace.left_dock();
4586            assert!(left_dock.read(cx).is_open());
4587            assert_eq!(
4588                left_dock.read(cx).visible_panel().unwrap().id(),
4589                panel_1.id()
4590            );
4591        });
4592
4593        // Emitting a ZoomIn event shows the panel as zoomed.
4594        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4595        workspace.read_with(cx, |workspace, _| {
4596            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4597            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4598        });
4599
4600        // Move panel to another dock while it is zoomed
4601        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4602        workspace.read_with(cx, |workspace, _| {
4603            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4604            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4605        });
4606
4607        // If focus is transferred to another view that's not a panel or another pane, we still show
4608        // the panel as zoomed.
4609        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4610        focus_receiver.update(cx, |_, cx| cx.focus_self());
4611        workspace.read_with(cx, |workspace, _| {
4612            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4613            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4614        });
4615
4616        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4617        workspace.update(cx, |_, cx| cx.focus_self());
4618        workspace.read_with(cx, |workspace, _| {
4619            assert_eq!(workspace.zoomed, None);
4620            assert_eq!(workspace.zoomed_position, None);
4621        });
4622
4623        // If focus is transferred again to another view that's not a panel or a pane, we won't
4624        // show the panel as zoomed because it wasn't zoomed before.
4625        focus_receiver.update(cx, |_, cx| cx.focus_self());
4626        workspace.read_with(cx, |workspace, _| {
4627            assert_eq!(workspace.zoomed, None);
4628            assert_eq!(workspace.zoomed_position, None);
4629        });
4630
4631        // When focus is transferred back to the panel, it is zoomed again.
4632        panel_1.update(cx, |_, cx| cx.focus_self());
4633        workspace.read_with(cx, |workspace, _| {
4634            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4635            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4636        });
4637
4638        // Emitting a ZoomOut event unzooms the panel.
4639        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4640        workspace.read_with(cx, |workspace, _| {
4641            assert_eq!(workspace.zoomed, None);
4642            assert_eq!(workspace.zoomed_position, None);
4643        });
4644
4645        // Emit closed event on panel 1, which is active
4646        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4647
4648        // Now the left dock is closed, because panel_1 was the active panel
4649        workspace.read_with(cx, |workspace, cx| {
4650            let right_dock = workspace.right_dock();
4651            assert!(!right_dock.read(cx).is_open());
4652        });
4653    }
4654
4655    pub fn init_test(cx: &mut TestAppContext) {
4656        cx.foreground().forbid_parking();
4657        cx.update(|cx| {
4658            cx.set_global(SettingsStore::test(cx));
4659            theme::init((), cx);
4660            language::init(cx);
4661            crate::init_settings(cx);
4662        });
4663    }
4664}