workspace.rs

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