workspace.rs

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