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 mut is_project_item = true;
2984        let mut update = proto::UpdateActiveView::default();
2985        if self.active_pane.read(cx).has_focus() {
2986            let item = self
2987                .active_item(cx)
2988                .and_then(|item| item.to_followable_item_handle(cx));
2989            if let Some(item) = item {
2990                is_project_item = item.is_project_item(cx);
2991                update = proto::UpdateActiveView {
2992                    id: item
2993                        .remote_id(&self.app_state.client, cx)
2994                        .map(|id| id.to_proto()),
2995                    leader_id: self.leader_for_pane(&self.active_pane),
2996                };
2997            }
2998        }
2999
3000        self.update_followers(
3001            is_project_item,
3002            proto::update_followers::Variant::UpdateActiveView(update),
3003            cx,
3004        );
3005    }
3006
3007    fn update_followers(
3008        &self,
3009        project_only: bool,
3010        update: proto::update_followers::Variant,
3011        cx: &AppContext,
3012    ) -> Option<()> {
3013        let project_id = if project_only {
3014            self.project.read(cx).remote_id()
3015        } else {
3016            None
3017        };
3018        self.app_state().workspace_store.read_with(cx, |store, cx| {
3019            store.update_followers(project_id, update, cx)
3020        })
3021    }
3022
3023    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3024        self.follower_states_by_leader
3025            .iter()
3026            .find_map(|(leader_id, state)| {
3027                if state.contains_key(pane) {
3028                    Some(*leader_id)
3029                } else {
3030                    None
3031                }
3032            })
3033    }
3034
3035    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3036        cx.notify();
3037
3038        let call = self.active_call()?;
3039        let room = call.read(cx).room()?.read(cx);
3040        let participant = room.remote_participant_for_peer_id(leader_id)?;
3041        let mut items_to_activate = Vec::new();
3042
3043        let leader_in_this_app;
3044        let leader_in_this_project;
3045        match participant.location {
3046            call::ParticipantLocation::SharedProject { project_id } => {
3047                leader_in_this_app = true;
3048                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3049            }
3050            call::ParticipantLocation::UnsharedProject => {
3051                leader_in_this_app = true;
3052                leader_in_this_project = false;
3053            }
3054            call::ParticipantLocation::External => {
3055                leader_in_this_app = false;
3056                leader_in_this_project = false;
3057            }
3058        };
3059
3060        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
3061            if leader_in_this_app {
3062                let item = state
3063                    .active_view_id
3064                    .and_then(|id| state.items_by_leader_view_id.get(&id));
3065                if let Some(item) = item {
3066                    if leader_in_this_project || !item.is_project_item(cx) {
3067                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3068                    }
3069                    continue;
3070                }
3071            }
3072            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3073                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3074            }
3075        }
3076
3077        for (pane, item) in items_to_activate {
3078            let pane_was_focused = pane.read(cx).has_focus();
3079            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3080                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3081            } else {
3082                pane.update(cx, |pane, cx| {
3083                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3084                });
3085            }
3086
3087            if pane_was_focused {
3088                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3089            }
3090        }
3091
3092        None
3093    }
3094
3095    fn shared_screen_for_peer(
3096        &self,
3097        peer_id: PeerId,
3098        pane: &ViewHandle<Pane>,
3099        cx: &mut ViewContext<Self>,
3100    ) -> Option<ViewHandle<SharedScreen>> {
3101        let call = self.active_call()?;
3102        let room = call.read(cx).room()?.read(cx);
3103        let participant = room.remote_participant_for_peer_id(peer_id)?;
3104        let track = participant.video_tracks.values().next()?.clone();
3105        let user = participant.user.clone();
3106
3107        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3108            if item.read(cx).peer_id == peer_id {
3109                return Some(item);
3110            }
3111        }
3112
3113        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3114    }
3115
3116    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3117        if active {
3118            self.update_active_view_for_followers(cx);
3119            cx.background()
3120                .spawn(persistence::DB.update_timestamp(self.database_id()))
3121                .detach();
3122        } else {
3123            for pane in &self.panes {
3124                pane.update(cx, |pane, cx| {
3125                    if let Some(item) = pane.active_item() {
3126                        item.workspace_deactivated(cx);
3127                    }
3128                    if matches!(
3129                        settings::get::<WorkspaceSettings>(cx).autosave,
3130                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3131                    ) {
3132                        for item in pane.items() {
3133                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3134                                .detach_and_log_err(cx);
3135                        }
3136                    }
3137                });
3138            }
3139        }
3140    }
3141
3142    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3143        self.active_call.as_ref().map(|(call, _)| call)
3144    }
3145
3146    fn on_active_call_event(
3147        &mut self,
3148        _: ModelHandle<ActiveCall>,
3149        event: &call::room::Event,
3150        cx: &mut ViewContext<Self>,
3151    ) {
3152        match event {
3153            call::room::Event::ParticipantLocationChanged { participant_id }
3154            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3155                self.leader_updated(*participant_id, cx);
3156            }
3157            _ => {}
3158        }
3159    }
3160
3161    pub fn database_id(&self) -> WorkspaceId {
3162        self.database_id
3163    }
3164
3165    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3166        let project = self.project().read(cx);
3167
3168        if project.is_local() {
3169            Some(
3170                project
3171                    .visible_worktrees(cx)
3172                    .map(|worktree| worktree.read(cx).abs_path())
3173                    .collect::<Vec<_>>()
3174                    .into(),
3175            )
3176        } else {
3177            None
3178        }
3179    }
3180
3181    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3182        match member {
3183            Member::Axis(PaneAxis { members, .. }) => {
3184                for child in members.iter() {
3185                    self.remove_panes(child.clone(), cx)
3186                }
3187            }
3188            Member::Pane(pane) => {
3189                self.force_remove_pane(&pane, cx);
3190            }
3191        }
3192    }
3193
3194    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3195        self.panes.retain(|p| p != pane);
3196        cx.focus(self.panes.last().unwrap());
3197        if self.last_active_center_pane == Some(pane.downgrade()) {
3198            self.last_active_center_pane = None;
3199        }
3200        cx.notify();
3201    }
3202
3203    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3204        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3205            cx.background().timer(Duration::from_millis(100)).await;
3206            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3207                .ok();
3208        }));
3209    }
3210
3211    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3212        fn serialize_pane_handle(
3213            pane_handle: &ViewHandle<Pane>,
3214            cx: &AppContext,
3215        ) -> SerializedPane {
3216            let (items, active) = {
3217                let pane = pane_handle.read(cx);
3218                let active_item_id = pane.active_item().map(|item| item.id());
3219                (
3220                    pane.items()
3221                        .filter_map(|item_handle| {
3222                            Some(SerializedItem {
3223                                kind: Arc::from(item_handle.serialized_item_kind()?),
3224                                item_id: item_handle.id(),
3225                                active: Some(item_handle.id()) == active_item_id,
3226                            })
3227                        })
3228                        .collect::<Vec<_>>(),
3229                    pane.has_focus(),
3230                )
3231            };
3232
3233            SerializedPane::new(items, active)
3234        }
3235
3236        fn build_serialized_pane_group(
3237            pane_group: &Member,
3238            cx: &AppContext,
3239        ) -> SerializedPaneGroup {
3240            match pane_group {
3241                Member::Axis(PaneAxis {
3242                    axis,
3243                    members,
3244                    flexes,
3245                    bounding_boxes: _,
3246                }) => SerializedPaneGroup::Group {
3247                    axis: *axis,
3248                    children: members
3249                        .iter()
3250                        .map(|member| build_serialized_pane_group(member, cx))
3251                        .collect::<Vec<_>>(),
3252                    flexes: Some(flexes.borrow().clone()),
3253                },
3254                Member::Pane(pane_handle) => {
3255                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3256                }
3257            }
3258        }
3259
3260        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3261            let left_dock = this.left_dock.read(cx);
3262            let left_visible = left_dock.is_open();
3263            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3264                Some(
3265                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3266                        .to_string(),
3267                )
3268            });
3269            let left_dock_zoom = left_dock
3270                .visible_panel()
3271                .map(|panel| panel.is_zoomed(cx))
3272                .unwrap_or(false);
3273
3274            let right_dock = this.right_dock.read(cx);
3275            let right_visible = right_dock.is_open();
3276            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3277                Some(
3278                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3279                        .to_string(),
3280                )
3281            });
3282            let right_dock_zoom = right_dock
3283                .visible_panel()
3284                .map(|panel| panel.is_zoomed(cx))
3285                .unwrap_or(false);
3286
3287            let bottom_dock = this.bottom_dock.read(cx);
3288            let bottom_visible = bottom_dock.is_open();
3289            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3290                Some(
3291                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3292                        .to_string(),
3293                )
3294            });
3295            let bottom_dock_zoom = bottom_dock
3296                .visible_panel()
3297                .map(|panel| panel.is_zoomed(cx))
3298                .unwrap_or(false);
3299
3300            DockStructure {
3301                left: DockData {
3302                    visible: left_visible,
3303                    active_panel: left_active_panel,
3304                    zoom: left_dock_zoom,
3305                },
3306                right: DockData {
3307                    visible: right_visible,
3308                    active_panel: right_active_panel,
3309                    zoom: right_dock_zoom,
3310                },
3311                bottom: DockData {
3312                    visible: bottom_visible,
3313                    active_panel: bottom_active_panel,
3314                    zoom: bottom_dock_zoom,
3315                },
3316            }
3317        }
3318
3319        if let Some(location) = self.location(cx) {
3320            // Load bearing special case:
3321            //  - with_local_workspace() relies on this to not have other stuff open
3322            //    when you open your log
3323            if !location.paths().is_empty() {
3324                let center_group = build_serialized_pane_group(&self.center.root, cx);
3325                let docks = build_serialized_docks(self, cx);
3326
3327                let serialized_workspace = SerializedWorkspace {
3328                    id: self.database_id,
3329                    location,
3330                    center_group,
3331                    bounds: Default::default(),
3332                    display: Default::default(),
3333                    docks,
3334                };
3335
3336                cx.background()
3337                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3338                    .detach();
3339            }
3340        }
3341    }
3342
3343    pub(crate) fn load_workspace(
3344        workspace: WeakViewHandle<Workspace>,
3345        serialized_workspace: SerializedWorkspace,
3346        paths_to_open: Vec<Option<ProjectPath>>,
3347        cx: &mut AppContext,
3348    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3349        cx.spawn(|mut cx| async move {
3350            let result = async_iife! {{
3351                let (project, old_center_pane) =
3352                workspace.read_with(&cx, |workspace, _| {
3353                    (
3354                        workspace.project().clone(),
3355                        workspace.last_active_center_pane.clone(),
3356                    )
3357                })?;
3358
3359                let mut center_items = None;
3360                let mut center_group = None;
3361                // Traverse the splits tree and add to things
3362                if let Some((group, active_pane, items)) = serialized_workspace
3363                        .center_group
3364                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3365                        .await {
3366                    center_items = Some(items);
3367                    center_group = Some((group, active_pane))
3368                }
3369
3370                let resulting_list = cx.read(|cx| {
3371                    let mut opened_items = center_items
3372                        .unwrap_or_default()
3373                        .into_iter()
3374                        .filter_map(|item| {
3375                            let item = item?;
3376                            let project_path = item.project_path(cx)?;
3377                            Some((project_path, item))
3378                        })
3379                        .collect::<HashMap<_, _>>();
3380
3381                    paths_to_open
3382                        .into_iter()
3383                        .map(|path_to_open| {
3384                            path_to_open.map(|path_to_open| {
3385                                Ok(opened_items.remove(&path_to_open))
3386                            })
3387                            .transpose()
3388                            .map(|item| item.flatten())
3389                            .transpose()
3390                        })
3391                        .collect::<Vec<_>>()
3392                });
3393
3394                // Remove old panes from workspace panes list
3395                workspace.update(&mut cx, |workspace, cx| {
3396                    if let Some((center_group, active_pane)) = center_group {
3397                        workspace.remove_panes(workspace.center.root.clone(), cx);
3398
3399                        // Swap workspace center group
3400                        workspace.center = PaneGroup::with_root(center_group);
3401
3402                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3403                        cx.focus_self();
3404
3405                        if let Some(active_pane) = active_pane {
3406                            cx.focus(&active_pane);
3407                        } else {
3408                            cx.focus(workspace.panes.last().unwrap());
3409                        }
3410                    } else {
3411                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3412                        if let Some(old_center_handle) = old_center_handle {
3413                            cx.focus(&old_center_handle)
3414                        } else {
3415                            cx.focus_self()
3416                        }
3417                    }
3418
3419                    let docks = serialized_workspace.docks;
3420                    workspace.left_dock.update(cx, |dock, cx| {
3421                        dock.set_open(docks.left.visible, cx);
3422                        if let Some(active_panel) = docks.left.active_panel {
3423                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3424                                dock.activate_panel(ix, cx);
3425                            }
3426                        }
3427                                dock.active_panel()
3428                                    .map(|panel| {
3429                                        panel.set_zoomed(docks.left.zoom, cx)
3430                                    });
3431                                if docks.left.visible && docks.left.zoom {
3432                                    cx.focus_self()
3433                                }
3434                    });
3435                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3436                    workspace.right_dock.update(cx, |dock, cx| {
3437                        dock.set_open(docks.right.visible, cx);
3438                        if let Some(active_panel) = docks.right.active_panel {
3439                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3440                                dock.activate_panel(ix, cx);
3441
3442                            }
3443                        }
3444                                dock.active_panel()
3445                                    .map(|panel| {
3446                                        panel.set_zoomed(docks.right.zoom, cx)
3447                                    });
3448
3449                                if docks.right.visible && docks.right.zoom {
3450                                    cx.focus_self()
3451                                }
3452                    });
3453                    workspace.bottom_dock.update(cx, |dock, cx| {
3454                        dock.set_open(docks.bottom.visible, cx);
3455                        if let Some(active_panel) = docks.bottom.active_panel {
3456                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3457                                dock.activate_panel(ix, cx);
3458                            }
3459                        }
3460
3461                        dock.active_panel()
3462                            .map(|panel| {
3463                                panel.set_zoomed(docks.bottom.zoom, cx)
3464                            });
3465
3466                        if docks.bottom.visible && docks.bottom.zoom {
3467                            cx.focus_self()
3468                        }
3469                    });
3470
3471
3472                    cx.notify();
3473                })?;
3474
3475                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3476                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3477
3478                Ok::<_, anyhow::Error>(resulting_list)
3479            }};
3480
3481            result.await.unwrap_or_default()
3482        })
3483    }
3484
3485    #[cfg(any(test, feature = "test-support"))]
3486    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3487        let client = project.read(cx).client();
3488        let user_store = project.read(cx).user_store();
3489
3490        let channel_store =
3491            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3492        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
3493        let app_state = Arc::new(AppState {
3494            languages: project.read(cx).languages().clone(),
3495            workspace_store,
3496            client,
3497            user_store,
3498            channel_store,
3499            fs: project.read(cx).fs().clone(),
3500            build_window_options: |_, _, _| Default::default(),
3501            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3502            background_actions: || &[],
3503        });
3504        Self::new(0, project, app_state, cx)
3505    }
3506
3507    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3508        let dock = match position {
3509            DockPosition::Left => &self.left_dock,
3510            DockPosition::Right => &self.right_dock,
3511            DockPosition::Bottom => &self.bottom_dock,
3512        };
3513        let active_panel = dock.read(cx).visible_panel()?;
3514        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3515            dock.read(cx).render_placeholder(cx)
3516        } else {
3517            ChildView::new(dock, cx).into_any()
3518        };
3519
3520        Some(
3521            element
3522                .constrained()
3523                .dynamically(move |constraint, _, cx| match position {
3524                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3525                        Vector2F::new(20., constraint.min.y()),
3526                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3527                    ),
3528                    DockPosition::Bottom => SizeConstraint::new(
3529                        Vector2F::new(constraint.min.x(), 20.),
3530                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3531                    ),
3532                })
3533                .into_any(),
3534        )
3535    }
3536}
3537
3538fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3539    ZED_WINDOW_POSITION
3540        .zip(*ZED_WINDOW_SIZE)
3541        .map(|(position, size)| {
3542            WindowBounds::Fixed(RectF::new(
3543                cx.platform().screens()[0].bounds().origin() + position,
3544                size,
3545            ))
3546        })
3547}
3548
3549async fn open_items(
3550    serialized_workspace: Option<SerializedWorkspace>,
3551    workspace: &WeakViewHandle<Workspace>,
3552    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3553    app_state: Arc<AppState>,
3554    mut cx: AsyncAppContext,
3555) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3556    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3557
3558    if let Some(serialized_workspace) = serialized_workspace {
3559        let workspace = workspace.clone();
3560        let restored_items = cx
3561            .update(|cx| {
3562                Workspace::load_workspace(
3563                    workspace,
3564                    serialized_workspace,
3565                    project_paths_to_open
3566                        .iter()
3567                        .map(|(_, project_path)| project_path)
3568                        .cloned()
3569                        .collect(),
3570                    cx,
3571                )
3572            })
3573            .await;
3574
3575        let restored_project_paths = cx.read(|cx| {
3576            restored_items
3577                .iter()
3578                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3579                .collect::<HashSet<_>>()
3580        });
3581
3582        opened_items = restored_items;
3583        project_paths_to_open
3584            .iter_mut()
3585            .for_each(|(_, project_path)| {
3586                if let Some(project_path_to_open) = project_path {
3587                    if restored_project_paths.contains(project_path_to_open) {
3588                        *project_path = None;
3589                    }
3590                }
3591            });
3592    } else {
3593        for _ in 0..project_paths_to_open.len() {
3594            opened_items.push(None);
3595        }
3596    }
3597    assert!(opened_items.len() == project_paths_to_open.len());
3598
3599    let tasks =
3600        project_paths_to_open
3601            .into_iter()
3602            .enumerate()
3603            .map(|(i, (abs_path, project_path))| {
3604                let workspace = workspace.clone();
3605                cx.spawn(|mut cx| {
3606                    let fs = app_state.fs.clone();
3607                    async move {
3608                        let file_project_path = project_path?;
3609                        if fs.is_file(&abs_path).await {
3610                            Some((
3611                                i,
3612                                workspace
3613                                    .update(&mut cx, |workspace, cx| {
3614                                        workspace.open_path(file_project_path, None, true, cx)
3615                                    })
3616                                    .log_err()?
3617                                    .await,
3618                            ))
3619                        } else {
3620                            None
3621                        }
3622                    }
3623                })
3624            });
3625
3626    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3627        .await
3628        .into_iter()
3629    {
3630        if let Some((i, path_open_result)) = maybe_opened_path {
3631            opened_items[i] = Some(path_open_result);
3632        }
3633    }
3634
3635    opened_items
3636}
3637
3638fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3639    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3640    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3641    const MESSAGE_ID: usize = 2;
3642
3643    if workspace
3644        .read_with(cx, |workspace, cx| {
3645            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3646        })
3647        .unwrap_or(false)
3648    {
3649        return;
3650    }
3651
3652    if db::kvp::KEY_VALUE_STORE
3653        .read_kvp(NEW_DOCK_HINT_KEY)
3654        .ok()
3655        .flatten()
3656        .is_some()
3657    {
3658        if !workspace
3659            .read_with(cx, |workspace, cx| {
3660                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3661            })
3662            .unwrap_or(false)
3663        {
3664            cx.update(|cx| {
3665                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3666                    let entry = tracker
3667                        .entry(TypeId::of::<MessageNotification>())
3668                        .or_default();
3669                    if !entry.contains(&MESSAGE_ID) {
3670                        entry.push(MESSAGE_ID);
3671                    }
3672                });
3673            });
3674        }
3675
3676        return;
3677    }
3678
3679    cx.spawn(|_| async move {
3680        db::kvp::KEY_VALUE_STORE
3681            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3682            .await
3683            .ok();
3684    })
3685    .detach();
3686
3687    workspace
3688        .update(cx, |workspace, cx| {
3689            workspace.show_notification_once(2, cx, |cx| {
3690                cx.add_view(|_| {
3691                    MessageNotification::new_element(|text, _| {
3692                        Text::new(
3693                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3694                            text,
3695                        )
3696                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3697                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3698                                .theme
3699                                .editor
3700                                .document_highlight_read_background;
3701
3702                            cx.scene().push_quad(gpui::Quad {
3703                                bounds,
3704                                background: Some(code_span_background_color),
3705                                border: Default::default(),
3706                                corner_radii: (2.0).into(),
3707                            })
3708                        })
3709                        .into_any()
3710                    })
3711                    .with_click_message("Read more about the new panel system")
3712                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3713                })
3714            })
3715        })
3716        .ok();
3717}
3718
3719fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3720    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3721
3722    workspace
3723        .update(cx, |workspace, cx| {
3724            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3725                workspace.show_notification_once(0, cx, |cx| {
3726                    cx.add_view(|_| {
3727                        MessageNotification::new("Failed to load the database file.")
3728                            .with_click_message("Click to let us know about this error")
3729                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3730                    })
3731                });
3732            }
3733        })
3734        .log_err();
3735}
3736
3737impl Entity for Workspace {
3738    type Event = Event;
3739
3740    fn release(&mut self, cx: &mut AppContext) {
3741        self.app_state.workspace_store.update(cx, |store, _| {
3742            store.workspaces.remove(&self.weak_self);
3743        })
3744    }
3745}
3746
3747impl View for Workspace {
3748    fn ui_name() -> &'static str {
3749        "Workspace"
3750    }
3751
3752    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3753        let theme = theme::current(cx).clone();
3754        Stack::new()
3755            .with_child(
3756                Flex::column()
3757                    .with_child(self.render_titlebar(&theme, cx))
3758                    .with_child(
3759                        Stack::new()
3760                            .with_child({
3761                                let project = self.project.clone();
3762                                Flex::row()
3763                                    .with_children(self.render_dock(DockPosition::Left, cx))
3764                                    .with_child(
3765                                        Flex::column()
3766                                            .with_child(
3767                                                FlexItem::new(
3768                                                    self.center.render(
3769                                                        &project,
3770                                                        &theme,
3771                                                        &self.follower_states_by_leader,
3772                                                        self.active_call(),
3773                                                        self.active_pane(),
3774                                                        self.zoomed
3775                                                            .as_ref()
3776                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3777                                                            .as_ref(),
3778                                                        &self.app_state,
3779                                                        cx,
3780                                                    ),
3781                                                )
3782                                                .flex(1., true),
3783                                            )
3784                                            .with_children(
3785                                                self.render_dock(DockPosition::Bottom, cx),
3786                                            )
3787                                            .flex(1., true),
3788                                    )
3789                                    .with_children(self.render_dock(DockPosition::Right, cx))
3790                            })
3791                            .with_child(Overlay::new(
3792                                Stack::new()
3793                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3794                                        enum ZoomBackground {}
3795                                        let zoomed = zoomed.upgrade(cx)?;
3796
3797                                        let mut foreground_style =
3798                                            theme.workspace.zoomed_pane_foreground;
3799                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3800                                            foreground_style =
3801                                                theme.workspace.zoomed_panel_foreground;
3802                                            let margin = foreground_style.margin.top;
3803                                            let border = foreground_style.border.top;
3804
3805                                            // Only include a margin and border on the opposite side.
3806                                            foreground_style.margin.top = 0.;
3807                                            foreground_style.margin.left = 0.;
3808                                            foreground_style.margin.bottom = 0.;
3809                                            foreground_style.margin.right = 0.;
3810                                            foreground_style.border.top = false;
3811                                            foreground_style.border.left = false;
3812                                            foreground_style.border.bottom = false;
3813                                            foreground_style.border.right = false;
3814                                            match zoomed_dock_position {
3815                                                DockPosition::Left => {
3816                                                    foreground_style.margin.right = margin;
3817                                                    foreground_style.border.right = border;
3818                                                }
3819                                                DockPosition::Right => {
3820                                                    foreground_style.margin.left = margin;
3821                                                    foreground_style.border.left = border;
3822                                                }
3823                                                DockPosition::Bottom => {
3824                                                    foreground_style.margin.top = margin;
3825                                                    foreground_style.border.top = border;
3826                                                }
3827                                            }
3828                                        }
3829
3830                                        Some(
3831                                            ChildView::new(&zoomed, cx)
3832                                                .contained()
3833                                                .with_style(foreground_style)
3834                                                .aligned()
3835                                                .contained()
3836                                                .with_style(theme.workspace.zoomed_background)
3837                                                .mouse::<ZoomBackground>(0)
3838                                                .capture_all()
3839                                                .on_down(
3840                                                    MouseButton::Left,
3841                                                    |_, this: &mut Self, cx| {
3842                                                        this.zoom_out(cx);
3843                                                    },
3844                                                ),
3845                                        )
3846                                    }))
3847                                    .with_children(self.modal.as_ref().map(|modal| {
3848                                        // Prevent clicks within the modal from falling
3849                                        // through to the rest of the workspace.
3850                                        enum ModalBackground {}
3851                                        MouseEventHandler::new::<ModalBackground, _>(
3852                                            0,
3853                                            cx,
3854                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3855                                        )
3856                                        .on_click(MouseButton::Left, |_, _, _| {})
3857                                        .contained()
3858                                        .with_style(theme.workspace.modal)
3859                                        .aligned()
3860                                        .top()
3861                                    }))
3862                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3863                            ))
3864                            .provide_resize_bounds::<WorkspaceBounds>()
3865                            .flex(1.0, true),
3866                    )
3867                    .with_child(ChildView::new(&self.status_bar, cx))
3868                    .contained()
3869                    .with_background_color(theme.workspace.background),
3870            )
3871            .with_children(DragAndDrop::render(cx))
3872            .with_children(self.render_disconnected_overlay(cx))
3873            .into_any_named("workspace")
3874    }
3875
3876    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3877        if cx.is_self_focused() {
3878            cx.focus(&self.active_pane);
3879        }
3880    }
3881
3882    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3883        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3884    }
3885}
3886
3887impl WorkspaceStore {
3888    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3889        Self {
3890            workspaces: Default::default(),
3891            followers: Default::default(),
3892            _subscriptions: vec![
3893                client.add_request_handler(cx.handle(), Self::handle_follow),
3894                client.add_message_handler(cx.handle(), Self::handle_unfollow),
3895                client.add_message_handler(cx.handle(), Self::handle_update_followers),
3896            ],
3897            client,
3898        }
3899    }
3900
3901    pub fn update_followers(
3902        &self,
3903        project_id: Option<u64>,
3904        update: proto::update_followers::Variant,
3905        cx: &AppContext,
3906    ) -> Option<()> {
3907        if !cx.has_global::<ModelHandle<ActiveCall>>() {
3908            return None;
3909        }
3910
3911        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3912        let follower_ids: Vec<_> = self
3913            .followers
3914            .iter()
3915            .filter_map(|follower| {
3916                if follower.project_id == project_id || project_id.is_none() {
3917                    Some(follower.peer_id.into())
3918                } else {
3919                    None
3920                }
3921            })
3922            .collect();
3923        if follower_ids.is_empty() {
3924            return None;
3925        }
3926        self.client
3927            .send(proto::UpdateFollowers {
3928                room_id,
3929                project_id,
3930                follower_ids,
3931                variant: Some(update),
3932            })
3933            .log_err()
3934    }
3935
3936    async fn handle_follow(
3937        this: ModelHandle<Self>,
3938        envelope: TypedEnvelope<proto::Follow>,
3939        _: Arc<Client>,
3940        mut cx: AsyncAppContext,
3941    ) -> Result<proto::FollowResponse> {
3942        this.update(&mut cx, |this, cx| {
3943            let follower = Follower {
3944                project_id: envelope.payload.project_id,
3945                peer_id: envelope.original_sender_id()?,
3946            };
3947            let active_project = ActiveCall::global(cx)
3948                .read(cx)
3949                .location()
3950                .map(|project| project.id());
3951
3952            let mut response = proto::FollowResponse::default();
3953            for workspace in &this.workspaces {
3954                let Some(workspace) = workspace.upgrade(cx) else {
3955                    continue;
3956                };
3957
3958                workspace.update(cx.as_mut(), |workspace, cx| {
3959                    let handler_response = workspace.handle_follow(follower.project_id, cx);
3960                    if response.views.is_empty() {
3961                        response.views = handler_response.views;
3962                    } else {
3963                        response.views.extend_from_slice(&handler_response.views);
3964                    }
3965
3966                    if let Some(active_view_id) = handler_response.active_view_id.clone() {
3967                        if response.active_view_id.is_none()
3968                            || Some(workspace.project.id()) == active_project
3969                        {
3970                            response.active_view_id = Some(active_view_id);
3971                        }
3972                    }
3973                });
3974            }
3975
3976            if let Err(ix) = this.followers.binary_search(&follower) {
3977                this.followers.insert(ix, follower);
3978            }
3979
3980            Ok(response)
3981        })
3982    }
3983
3984    async fn handle_unfollow(
3985        this: ModelHandle<Self>,
3986        envelope: TypedEnvelope<proto::Unfollow>,
3987        _: Arc<Client>,
3988        mut cx: AsyncAppContext,
3989    ) -> Result<()> {
3990        this.update(&mut cx, |this, _| {
3991            let follower = Follower {
3992                project_id: envelope.payload.project_id,
3993                peer_id: envelope.original_sender_id()?,
3994            };
3995            if let Ok(ix) = this.followers.binary_search(&follower) {
3996                this.followers.remove(ix);
3997            }
3998            Ok(())
3999        })
4000    }
4001
4002    async fn handle_update_followers(
4003        this: ModelHandle<Self>,
4004        envelope: TypedEnvelope<proto::UpdateFollowers>,
4005        _: Arc<Client>,
4006        mut cx: AsyncAppContext,
4007    ) -> Result<()> {
4008        let leader_id = envelope.original_sender_id()?;
4009        let update = envelope.payload;
4010        this.update(&mut cx, |this, cx| {
4011            for workspace in &this.workspaces {
4012                let Some(workspace) = workspace.upgrade(cx) else {
4013                    continue;
4014                };
4015                workspace.update(cx.as_mut(), |workspace, cx| {
4016                    let project_id = workspace.project.read(cx).remote_id();
4017                    if update.project_id != project_id && update.project_id.is_some() {
4018                        return;
4019                    }
4020                    workspace.handle_update_followers(leader_id, update.clone(), cx);
4021                });
4022            }
4023            Ok(())
4024        })
4025    }
4026}
4027
4028impl Entity for WorkspaceStore {
4029    type Event = ();
4030}
4031
4032impl ViewId {
4033    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4034        Ok(Self {
4035            creator: message
4036                .creator
4037                .ok_or_else(|| anyhow!("creator is missing"))?,
4038            id: message.id,
4039        })
4040    }
4041
4042    pub(crate) fn to_proto(&self) -> proto::ViewId {
4043        proto::ViewId {
4044            creator: Some(self.creator),
4045            id: self.id,
4046        }
4047    }
4048}
4049
4050pub trait WorkspaceHandle {
4051    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4052}
4053
4054impl WorkspaceHandle for ViewHandle<Workspace> {
4055    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4056        self.read(cx)
4057            .worktrees(cx)
4058            .flat_map(|worktree| {
4059                let worktree_id = worktree.read(cx).id();
4060                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4061                    worktree_id,
4062                    path: f.path.clone(),
4063                })
4064            })
4065            .collect::<Vec<_>>()
4066    }
4067}
4068
4069impl std::fmt::Debug for OpenPaths {
4070    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4071        f.debug_struct("OpenPaths")
4072            .field("paths", &self.paths)
4073            .finish()
4074    }
4075}
4076
4077pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
4078
4079pub fn activate_workspace_for_project(
4080    cx: &mut AsyncAppContext,
4081    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
4082) -> Option<WeakViewHandle<Workspace>> {
4083    for window in cx.windows() {
4084        let handle = window
4085            .update(cx, |cx| {
4086                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
4087                    let project = workspace_handle.read(cx).project.clone();
4088                    if project.update(cx, &predicate) {
4089                        cx.activate_window();
4090                        return Some(workspace_handle.clone());
4091                    }
4092                }
4093                None
4094            })
4095            .flatten();
4096
4097        if let Some(handle) = handle {
4098            return Some(handle.downgrade());
4099        }
4100    }
4101    None
4102}
4103
4104pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4105    DB.last_workspace().await.log_err().flatten()
4106}
4107
4108#[allow(clippy::type_complexity)]
4109pub fn open_paths(
4110    abs_paths: &[PathBuf],
4111    app_state: &Arc<AppState>,
4112    requesting_window: Option<WindowHandle<Workspace>>,
4113    cx: &mut AppContext,
4114) -> Task<
4115    Result<(
4116        WeakViewHandle<Workspace>,
4117        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4118    )>,
4119> {
4120    let app_state = app_state.clone();
4121    let abs_paths = abs_paths.to_vec();
4122    cx.spawn(|mut cx| async move {
4123        // Open paths in existing workspace if possible
4124        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4125            project.contains_paths(&abs_paths, cx)
4126        });
4127
4128        if let Some(existing) = existing {
4129            Ok((
4130                existing.clone(),
4131                existing
4132                    .update(&mut cx, |workspace, cx| {
4133                        workspace.open_paths(abs_paths, true, cx)
4134                    })?
4135                    .await,
4136            ))
4137        } else {
4138            Ok(cx
4139                .update(|cx| {
4140                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4141                })
4142                .await)
4143        }
4144    })
4145}
4146
4147pub fn open_new(
4148    app_state: &Arc<AppState>,
4149    cx: &mut AppContext,
4150    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4151) -> Task<()> {
4152    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4153    cx.spawn(|mut cx| async move {
4154        let (workspace, opened_paths) = task.await;
4155
4156        workspace
4157            .update(&mut cx, |workspace, cx| {
4158                if opened_paths.is_empty() {
4159                    init(workspace, cx)
4160                }
4161            })
4162            .log_err();
4163    })
4164}
4165
4166pub fn create_and_open_local_file(
4167    path: &'static Path,
4168    cx: &mut ViewContext<Workspace>,
4169    default_content: impl 'static + Send + FnOnce() -> Rope,
4170) -> Task<Result<Box<dyn ItemHandle>>> {
4171    cx.spawn(|workspace, mut cx| async move {
4172        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4173        if !fs.is_file(path).await {
4174            fs.create_file(path, Default::default()).await?;
4175            fs.save(path, &default_content(), Default::default())
4176                .await?;
4177        }
4178
4179        let mut items = workspace
4180            .update(&mut cx, |workspace, cx| {
4181                workspace.with_local_workspace(cx, |workspace, cx| {
4182                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4183                })
4184            })?
4185            .await?
4186            .await;
4187
4188        let item = items.pop().flatten();
4189        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4190    })
4191}
4192
4193pub fn join_remote_project(
4194    project_id: u64,
4195    follow_user_id: u64,
4196    app_state: Arc<AppState>,
4197    cx: &mut AppContext,
4198) -> Task<Result<()>> {
4199    cx.spawn(|mut cx| async move {
4200        let existing_workspace = cx
4201            .windows()
4202            .into_iter()
4203            .find_map(|window| {
4204                window.downcast::<Workspace>().and_then(|window| {
4205                    window.read_root_with(&cx, |workspace, cx| {
4206                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4207                            Some(cx.handle().downgrade())
4208                        } else {
4209                            None
4210                        }
4211                    })
4212                })
4213            })
4214            .flatten();
4215
4216        let workspace = if let Some(existing_workspace) = existing_workspace {
4217            existing_workspace
4218        } else {
4219            let active_call = cx.read(ActiveCall::global);
4220            let room = active_call
4221                .read_with(&cx, |call, _| call.room().cloned())
4222                .ok_or_else(|| anyhow!("not in a call"))?;
4223            let project = room
4224                .update(&mut cx, |room, cx| {
4225                    room.join_project(
4226                        project_id,
4227                        app_state.languages.clone(),
4228                        app_state.fs.clone(),
4229                        cx,
4230                    )
4231                })
4232                .await?;
4233
4234            let window_bounds_override = window_bounds_env_override(&cx);
4235            let window = cx.add_window(
4236                (app_state.build_window_options)(
4237                    window_bounds_override,
4238                    None,
4239                    cx.platform().as_ref(),
4240                ),
4241                |cx| Workspace::new(0, project, app_state.clone(), cx),
4242            );
4243            let workspace = window.root(&cx).unwrap();
4244            (app_state.initialize_workspace)(
4245                workspace.downgrade(),
4246                false,
4247                app_state.clone(),
4248                cx.clone(),
4249            )
4250            .await
4251            .log_err();
4252
4253            workspace.downgrade()
4254        };
4255
4256        workspace.window().activate(&mut cx);
4257        cx.platform().activate(true);
4258
4259        workspace.update(&mut cx, |workspace, cx| {
4260            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4261                let follow_peer_id = room
4262                    .read(cx)
4263                    .remote_participants()
4264                    .iter()
4265                    .find(|(_, participant)| participant.user.id == follow_user_id)
4266                    .map(|(_, p)| p.peer_id)
4267                    .or_else(|| {
4268                        // If we couldn't follow the given user, follow the host instead.
4269                        let collaborator = workspace
4270                            .project()
4271                            .read(cx)
4272                            .collaborators()
4273                            .values()
4274                            .find(|collaborator| collaborator.replica_id == 0)?;
4275                        Some(collaborator.peer_id)
4276                    });
4277
4278                if let Some(follow_peer_id) = follow_peer_id {
4279                    if !workspace.is_being_followed(follow_peer_id) {
4280                        workspace
4281                            .toggle_follow(follow_peer_id, cx)
4282                            .map(|follow| follow.detach_and_log_err(cx));
4283                    }
4284                }
4285            }
4286        })?;
4287
4288        anyhow::Ok(())
4289    })
4290}
4291
4292pub fn restart(_: &Restart, cx: &mut AppContext) {
4293    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4294    cx.spawn(|mut cx| async move {
4295        let mut workspace_windows = cx
4296            .windows()
4297            .into_iter()
4298            .filter_map(|window| window.downcast::<Workspace>())
4299            .collect::<Vec<_>>();
4300
4301        // If multiple windows have unsaved changes, and need a save prompt,
4302        // prompt in the active window before switching to a different window.
4303        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4304
4305        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4306            let answer = window.prompt(
4307                PromptLevel::Info,
4308                "Are you sure you want to restart?",
4309                &["Restart", "Cancel"],
4310                &mut cx,
4311            );
4312
4313            if let Some(mut answer) = answer {
4314                let answer = answer.next().await;
4315                if answer != Some(0) {
4316                    return Ok(());
4317                }
4318            }
4319        }
4320
4321        // If the user cancels any save prompt, then keep the app open.
4322        for window in workspace_windows {
4323            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4324                workspace.prepare_to_close(true, cx)
4325            }) {
4326                if !should_close.await? {
4327                    return Ok(());
4328                }
4329            }
4330        }
4331        cx.platform().restart();
4332        anyhow::Ok(())
4333    })
4334    .detach_and_log_err(cx);
4335}
4336
4337fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4338    let mut parts = value.split(',');
4339    let width: usize = parts.next()?.parse().ok()?;
4340    let height: usize = parts.next()?.parse().ok()?;
4341    Some(vec2f(width as f32, height as f32))
4342}
4343
4344#[cfg(test)]
4345mod tests {
4346    use super::*;
4347    use crate::{
4348        dock::test::{TestPanel, TestPanelEvent},
4349        item::test::{TestItem, TestItemEvent, TestProjectItem},
4350    };
4351    use fs::FakeFs;
4352    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4353    use project::{Project, ProjectEntryId};
4354    use serde_json::json;
4355    use settings::SettingsStore;
4356    use std::{cell::RefCell, rc::Rc};
4357
4358    #[gpui::test]
4359    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4360        init_test(cx);
4361
4362        let fs = FakeFs::new(cx.background());
4363        let project = Project::test(fs, [], cx).await;
4364        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4365        let workspace = window.root(cx);
4366
4367        // Adding an item with no ambiguity renders the tab without detail.
4368        let item1 = window.add_view(cx, |_| {
4369            let mut item = TestItem::new();
4370            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4371            item
4372        });
4373        workspace.update(cx, |workspace, cx| {
4374            workspace.add_item(Box::new(item1.clone()), cx);
4375        });
4376        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4377
4378        // Adding an item that creates ambiguity increases the level of detail on
4379        // both tabs.
4380        let item2 = window.add_view(cx, |_| {
4381            let mut item = TestItem::new();
4382            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4383            item
4384        });
4385        workspace.update(cx, |workspace, cx| {
4386            workspace.add_item(Box::new(item2.clone()), cx);
4387        });
4388        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4389        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4390
4391        // Adding an item that creates ambiguity increases the level of detail only
4392        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4393        // we stop at the highest detail available.
4394        let item3 = window.add_view(cx, |_| {
4395            let mut item = TestItem::new();
4396            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4397            item
4398        });
4399        workspace.update(cx, |workspace, cx| {
4400            workspace.add_item(Box::new(item3.clone()), cx);
4401        });
4402        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4403        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4404        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4405    }
4406
4407    #[gpui::test]
4408    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4409        init_test(cx);
4410
4411        let fs = FakeFs::new(cx.background());
4412        fs.insert_tree(
4413            "/root1",
4414            json!({
4415                "one.txt": "",
4416                "two.txt": "",
4417            }),
4418        )
4419        .await;
4420        fs.insert_tree(
4421            "/root2",
4422            json!({
4423                "three.txt": "",
4424            }),
4425        )
4426        .await;
4427
4428        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4429        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4430        let workspace = window.root(cx);
4431        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4432        let worktree_id = project.read_with(cx, |project, cx| {
4433            project.worktrees(cx).next().unwrap().read(cx).id()
4434        });
4435
4436        let item1 = window.add_view(cx, |cx| {
4437            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4438        });
4439        let item2 = window.add_view(cx, |cx| {
4440            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4441        });
4442
4443        // Add an item to an empty pane
4444        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4445        project.read_with(cx, |project, cx| {
4446            assert_eq!(
4447                project.active_entry(),
4448                project
4449                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4450                    .map(|e| e.id)
4451            );
4452        });
4453        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4454
4455        // Add a second item to a non-empty pane
4456        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4457        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4458        project.read_with(cx, |project, cx| {
4459            assert_eq!(
4460                project.active_entry(),
4461                project
4462                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4463                    .map(|e| e.id)
4464            );
4465        });
4466
4467        // Close the active item
4468        pane.update(cx, |pane, cx| {
4469            pane.close_active_item(&Default::default(), cx).unwrap()
4470        })
4471        .await
4472        .unwrap();
4473        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4474        project.read_with(cx, |project, cx| {
4475            assert_eq!(
4476                project.active_entry(),
4477                project
4478                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4479                    .map(|e| e.id)
4480            );
4481        });
4482
4483        // Add a project folder
4484        project
4485            .update(cx, |project, cx| {
4486                project.find_or_create_local_worktree("/root2", true, cx)
4487            })
4488            .await
4489            .unwrap();
4490        assert_eq!(
4491            window.current_title(cx).as_deref(),
4492            Some("one.txt β€” root1, root2")
4493        );
4494
4495        // Remove a project folder
4496        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4497        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4498    }
4499
4500    #[gpui::test]
4501    async fn test_close_window(cx: &mut TestAppContext) {
4502        init_test(cx);
4503
4504        let fs = FakeFs::new(cx.background());
4505        fs.insert_tree("/root", json!({ "one": "" })).await;
4506
4507        let project = Project::test(fs, ["root".as_ref()], cx).await;
4508        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4509        let workspace = window.root(cx);
4510
4511        // When there are no dirty items, there's nothing to do.
4512        let item1 = window.add_view(cx, |_| TestItem::new());
4513        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4514        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4515        assert!(task.await.unwrap());
4516
4517        // When there are dirty untitled items, prompt to save each one. If the user
4518        // cancels any prompt, then abort.
4519        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4520        let item3 = window.add_view(cx, |cx| {
4521            TestItem::new()
4522                .with_dirty(true)
4523                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4524        });
4525        workspace.update(cx, |w, cx| {
4526            w.add_item(Box::new(item2.clone()), cx);
4527            w.add_item(Box::new(item3.clone()), cx);
4528        });
4529        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4530        cx.foreground().run_until_parked();
4531        window.simulate_prompt_answer(2, cx); // cancel save all
4532        cx.foreground().run_until_parked();
4533        window.simulate_prompt_answer(2, cx); // cancel save all
4534        cx.foreground().run_until_parked();
4535        assert!(!window.has_pending_prompt(cx));
4536        assert!(!task.await.unwrap());
4537    }
4538
4539    #[gpui::test]
4540    async fn test_close_pane_items(cx: &mut TestAppContext) {
4541        init_test(cx);
4542
4543        let fs = FakeFs::new(cx.background());
4544
4545        let project = Project::test(fs, None, cx).await;
4546        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4547        let workspace = window.root(cx);
4548
4549        let item1 = window.add_view(cx, |cx| {
4550            TestItem::new()
4551                .with_dirty(true)
4552                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4553        });
4554        let item2 = window.add_view(cx, |cx| {
4555            TestItem::new()
4556                .with_dirty(true)
4557                .with_conflict(true)
4558                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4559        });
4560        let item3 = window.add_view(cx, |cx| {
4561            TestItem::new()
4562                .with_dirty(true)
4563                .with_conflict(true)
4564                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4565        });
4566        let item4 = window.add_view(cx, |cx| {
4567            TestItem::new()
4568                .with_dirty(true)
4569                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4570        });
4571        let pane = workspace.update(cx, |workspace, cx| {
4572            workspace.add_item(Box::new(item1.clone()), cx);
4573            workspace.add_item(Box::new(item2.clone()), cx);
4574            workspace.add_item(Box::new(item3.clone()), cx);
4575            workspace.add_item(Box::new(item4.clone()), cx);
4576            workspace.active_pane().clone()
4577        });
4578
4579        let close_items = pane.update(cx, |pane, cx| {
4580            pane.activate_item(1, true, true, cx);
4581            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4582            let item1_id = item1.id();
4583            let item3_id = item3.id();
4584            let item4_id = item4.id();
4585            pane.close_items(cx, SaveIntent::Close, move |id| {
4586                [item1_id, item3_id, item4_id].contains(&id)
4587            })
4588        });
4589        cx.foreground().run_until_parked();
4590
4591        assert!(window.has_pending_prompt(cx));
4592        // Ignore "Save all" prompt
4593        window.simulate_prompt_answer(2, cx);
4594        cx.foreground().run_until_parked();
4595        // There's a prompt to save item 1.
4596        pane.read_with(cx, |pane, _| {
4597            assert_eq!(pane.items_len(), 4);
4598            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4599        });
4600        // Confirm saving item 1.
4601        window.simulate_prompt_answer(0, cx);
4602        cx.foreground().run_until_parked();
4603
4604        // Item 1 is saved. There's a prompt to save item 3.
4605        pane.read_with(cx, |pane, cx| {
4606            assert_eq!(item1.read(cx).save_count, 1);
4607            assert_eq!(item1.read(cx).save_as_count, 0);
4608            assert_eq!(item1.read(cx).reload_count, 0);
4609            assert_eq!(pane.items_len(), 3);
4610            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4611        });
4612        assert!(window.has_pending_prompt(cx));
4613
4614        // Cancel saving item 3.
4615        window.simulate_prompt_answer(1, cx);
4616        cx.foreground().run_until_parked();
4617
4618        // Item 3 is reloaded. There's a prompt to save item 4.
4619        pane.read_with(cx, |pane, cx| {
4620            assert_eq!(item3.read(cx).save_count, 0);
4621            assert_eq!(item3.read(cx).save_as_count, 0);
4622            assert_eq!(item3.read(cx).reload_count, 1);
4623            assert_eq!(pane.items_len(), 2);
4624            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4625        });
4626        assert!(window.has_pending_prompt(cx));
4627
4628        // Confirm saving item 4.
4629        window.simulate_prompt_answer(0, cx);
4630        cx.foreground().run_until_parked();
4631
4632        // There's a prompt for a path for item 4.
4633        cx.simulate_new_path_selection(|_| Some(Default::default()));
4634        close_items.await.unwrap();
4635
4636        // The requested items are closed.
4637        pane.read_with(cx, |pane, cx| {
4638            assert_eq!(item4.read(cx).save_count, 0);
4639            assert_eq!(item4.read(cx).save_as_count, 1);
4640            assert_eq!(item4.read(cx).reload_count, 0);
4641            assert_eq!(pane.items_len(), 1);
4642            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4643        });
4644    }
4645
4646    #[gpui::test]
4647    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4648        init_test(cx);
4649
4650        let fs = FakeFs::new(cx.background());
4651
4652        let project = Project::test(fs, [], cx).await;
4653        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4654        let workspace = window.root(cx);
4655
4656        // Create several workspace items with single project entries, and two
4657        // workspace items with multiple project entries.
4658        let single_entry_items = (0..=4)
4659            .map(|project_entry_id| {
4660                window.add_view(cx, |cx| {
4661                    TestItem::new()
4662                        .with_dirty(true)
4663                        .with_project_items(&[TestProjectItem::new(
4664                            project_entry_id,
4665                            &format!("{project_entry_id}.txt"),
4666                            cx,
4667                        )])
4668                })
4669            })
4670            .collect::<Vec<_>>();
4671        let item_2_3 = window.add_view(cx, |cx| {
4672            TestItem::new()
4673                .with_dirty(true)
4674                .with_singleton(false)
4675                .with_project_items(&[
4676                    single_entry_items[2].read(cx).project_items[0].clone(),
4677                    single_entry_items[3].read(cx).project_items[0].clone(),
4678                ])
4679        });
4680        let item_3_4 = window.add_view(cx, |cx| {
4681            TestItem::new()
4682                .with_dirty(true)
4683                .with_singleton(false)
4684                .with_project_items(&[
4685                    single_entry_items[3].read(cx).project_items[0].clone(),
4686                    single_entry_items[4].read(cx).project_items[0].clone(),
4687                ])
4688        });
4689
4690        // Create two panes that contain the following project entries:
4691        //   left pane:
4692        //     multi-entry items:   (2, 3)
4693        //     single-entry items:  0, 1, 2, 3, 4
4694        //   right pane:
4695        //     single-entry items:  1
4696        //     multi-entry items:   (3, 4)
4697        let left_pane = workspace.update(cx, |workspace, cx| {
4698            let left_pane = workspace.active_pane().clone();
4699            workspace.add_item(Box::new(item_2_3.clone()), cx);
4700            for item in single_entry_items {
4701                workspace.add_item(Box::new(item), cx);
4702            }
4703            left_pane.update(cx, |pane, cx| {
4704                pane.activate_item(2, true, true, cx);
4705            });
4706
4707            workspace
4708                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4709                .unwrap();
4710
4711            left_pane
4712        });
4713
4714        //Need to cause an effect flush in order to respect new focus
4715        workspace.update(cx, |workspace, cx| {
4716            workspace.add_item(Box::new(item_3_4.clone()), cx);
4717            cx.focus(&left_pane);
4718        });
4719
4720        // When closing all of the items in the left pane, we should be prompted twice:
4721        // once for project entry 0, and once for project entry 2. After those two
4722        // prompts, the task should complete.
4723
4724        let close = left_pane.update(cx, |pane, cx| {
4725            pane.close_items(cx, SaveIntent::Close, move |_| true)
4726        });
4727        cx.foreground().run_until_parked();
4728        // Discard "Save all" prompt
4729        window.simulate_prompt_answer(2, cx);
4730
4731        cx.foreground().run_until_parked();
4732        left_pane.read_with(cx, |pane, cx| {
4733            assert_eq!(
4734                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4735                &[ProjectEntryId::from_proto(0)]
4736            );
4737        });
4738        window.simulate_prompt_answer(0, cx);
4739
4740        cx.foreground().run_until_parked();
4741        left_pane.read_with(cx, |pane, cx| {
4742            assert_eq!(
4743                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4744                &[ProjectEntryId::from_proto(2)]
4745            );
4746        });
4747        window.simulate_prompt_answer(0, cx);
4748
4749        cx.foreground().run_until_parked();
4750        close.await.unwrap();
4751        left_pane.read_with(cx, |pane, _| {
4752            assert_eq!(pane.items_len(), 0);
4753        });
4754    }
4755
4756    #[gpui::test]
4757    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4758        init_test(cx);
4759
4760        let fs = FakeFs::new(cx.background());
4761
4762        let project = Project::test(fs, [], cx).await;
4763        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4764        let workspace = window.root(cx);
4765        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4766
4767        let item = window.add_view(cx, |cx| {
4768            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4769        });
4770        let item_id = item.id();
4771        workspace.update(cx, |workspace, cx| {
4772            workspace.add_item(Box::new(item.clone()), cx);
4773        });
4774
4775        // Autosave on window change.
4776        item.update(cx, |item, cx| {
4777            cx.update_global(|settings: &mut SettingsStore, cx| {
4778                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4779                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4780                })
4781            });
4782            item.is_dirty = true;
4783        });
4784
4785        // Deactivating the window saves the file.
4786        window.simulate_deactivation(cx);
4787        deterministic.run_until_parked();
4788        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4789
4790        // Autosave on focus change.
4791        item.update(cx, |item, cx| {
4792            cx.focus_self();
4793            cx.update_global(|settings: &mut SettingsStore, cx| {
4794                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4795                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4796                })
4797            });
4798            item.is_dirty = true;
4799        });
4800
4801        // Blurring the item saves the file.
4802        item.update(cx, |_, cx| cx.blur());
4803        deterministic.run_until_parked();
4804        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4805
4806        // Deactivating the window still saves the file.
4807        window.simulate_activation(cx);
4808        item.update(cx, |item, cx| {
4809            cx.focus_self();
4810            item.is_dirty = true;
4811        });
4812        window.simulate_deactivation(cx);
4813
4814        deterministic.run_until_parked();
4815        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4816
4817        // Autosave after delay.
4818        item.update(cx, |item, cx| {
4819            cx.update_global(|settings: &mut SettingsStore, cx| {
4820                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4821                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4822                })
4823            });
4824            item.is_dirty = true;
4825            cx.emit(TestItemEvent::Edit);
4826        });
4827
4828        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4829        deterministic.advance_clock(Duration::from_millis(250));
4830        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4831
4832        // After delay expires, the file is saved.
4833        deterministic.advance_clock(Duration::from_millis(250));
4834        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4835
4836        // Autosave on focus change, ensuring closing the tab counts as such.
4837        item.update(cx, |item, cx| {
4838            cx.update_global(|settings: &mut SettingsStore, cx| {
4839                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4840                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4841                })
4842            });
4843            item.is_dirty = true;
4844        });
4845
4846        pane.update(cx, |pane, cx| {
4847            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4848        })
4849        .await
4850        .unwrap();
4851        assert!(!window.has_pending_prompt(cx));
4852        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4853
4854        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4855        workspace.update(cx, |workspace, cx| {
4856            workspace.add_item(Box::new(item.clone()), cx);
4857        });
4858        item.update(cx, |item, cx| {
4859            item.project_items[0].update(cx, |item, _| {
4860                item.entry_id = None;
4861            });
4862            item.is_dirty = true;
4863            cx.blur();
4864        });
4865        deterministic.run_until_parked();
4866        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4867
4868        // Ensure autosave is prevented for deleted files also when closing the buffer.
4869        let _close_items = pane.update(cx, |pane, cx| {
4870            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4871        });
4872        deterministic.run_until_parked();
4873        assert!(window.has_pending_prompt(cx));
4874        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4875    }
4876
4877    #[gpui::test]
4878    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4879        init_test(cx);
4880
4881        let fs = FakeFs::new(cx.background());
4882
4883        let project = Project::test(fs, [], cx).await;
4884        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4885        let workspace = window.root(cx);
4886
4887        let item = window.add_view(cx, |cx| {
4888            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4889        });
4890        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4891        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4892        let toolbar_notify_count = Rc::new(RefCell::new(0));
4893
4894        workspace.update(cx, |workspace, cx| {
4895            workspace.add_item(Box::new(item.clone()), cx);
4896            let toolbar_notification_count = toolbar_notify_count.clone();
4897            cx.observe(&toolbar, move |_, _, _| {
4898                *toolbar_notification_count.borrow_mut() += 1
4899            })
4900            .detach();
4901        });
4902
4903        pane.read_with(cx, |pane, _| {
4904            assert!(!pane.can_navigate_backward());
4905            assert!(!pane.can_navigate_forward());
4906        });
4907
4908        item.update(cx, |item, cx| {
4909            item.set_state("one".to_string(), cx);
4910        });
4911
4912        // Toolbar must be notified to re-render the navigation buttons
4913        assert_eq!(*toolbar_notify_count.borrow(), 1);
4914
4915        pane.read_with(cx, |pane, _| {
4916            assert!(pane.can_navigate_backward());
4917            assert!(!pane.can_navigate_forward());
4918        });
4919
4920        workspace
4921            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4922            .await
4923            .unwrap();
4924
4925        assert_eq!(*toolbar_notify_count.borrow(), 3);
4926        pane.read_with(cx, |pane, _| {
4927            assert!(!pane.can_navigate_backward());
4928            assert!(pane.can_navigate_forward());
4929        });
4930    }
4931
4932    #[gpui::test]
4933    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4934        init_test(cx);
4935        let fs = FakeFs::new(cx.background());
4936
4937        let project = Project::test(fs, [], cx).await;
4938        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4939        let workspace = window.root(cx);
4940
4941        let panel = workspace.update(cx, |workspace, cx| {
4942            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4943            workspace.add_panel(panel.clone(), cx);
4944
4945            workspace
4946                .right_dock()
4947                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4948
4949            panel
4950        });
4951
4952        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4953        pane.update(cx, |pane, cx| {
4954            let item = cx.add_view(|_| TestItem::new());
4955            pane.add_item(Box::new(item), true, true, None, cx);
4956        });
4957
4958        // Transfer focus from center to panel
4959        workspace.update(cx, |workspace, cx| {
4960            workspace.toggle_panel_focus::<TestPanel>(cx);
4961        });
4962
4963        workspace.read_with(cx, |workspace, cx| {
4964            assert!(workspace.right_dock().read(cx).is_open());
4965            assert!(!panel.is_zoomed(cx));
4966            assert!(panel.has_focus(cx));
4967        });
4968
4969        // Transfer focus from panel to center
4970        workspace.update(cx, |workspace, cx| {
4971            workspace.toggle_panel_focus::<TestPanel>(cx);
4972        });
4973
4974        workspace.read_with(cx, |workspace, cx| {
4975            assert!(workspace.right_dock().read(cx).is_open());
4976            assert!(!panel.is_zoomed(cx));
4977            assert!(!panel.has_focus(cx));
4978        });
4979
4980        // Close the dock
4981        workspace.update(cx, |workspace, cx| {
4982            workspace.toggle_dock(DockPosition::Right, cx);
4983        });
4984
4985        workspace.read_with(cx, |workspace, cx| {
4986            assert!(!workspace.right_dock().read(cx).is_open());
4987            assert!(!panel.is_zoomed(cx));
4988            assert!(!panel.has_focus(cx));
4989        });
4990
4991        // Open the dock
4992        workspace.update(cx, |workspace, cx| {
4993            workspace.toggle_dock(DockPosition::Right, cx);
4994        });
4995
4996        workspace.read_with(cx, |workspace, cx| {
4997            assert!(workspace.right_dock().read(cx).is_open());
4998            assert!(!panel.is_zoomed(cx));
4999            assert!(panel.has_focus(cx));
5000        });
5001
5002        // Focus and zoom panel
5003        panel.update(cx, |panel, cx| {
5004            cx.focus_self();
5005            panel.set_zoomed(true, cx)
5006        });
5007
5008        workspace.read_with(cx, |workspace, cx| {
5009            assert!(workspace.right_dock().read(cx).is_open());
5010            assert!(panel.is_zoomed(cx));
5011            assert!(panel.has_focus(cx));
5012        });
5013
5014        // Transfer focus to the center closes the dock
5015        workspace.update(cx, |workspace, cx| {
5016            workspace.toggle_panel_focus::<TestPanel>(cx);
5017        });
5018
5019        workspace.read_with(cx, |workspace, cx| {
5020            assert!(!workspace.right_dock().read(cx).is_open());
5021            assert!(panel.is_zoomed(cx));
5022            assert!(!panel.has_focus(cx));
5023        });
5024
5025        // Transferring focus back to the panel keeps it zoomed
5026        workspace.update(cx, |workspace, cx| {
5027            workspace.toggle_panel_focus::<TestPanel>(cx);
5028        });
5029
5030        workspace.read_with(cx, |workspace, cx| {
5031            assert!(workspace.right_dock().read(cx).is_open());
5032            assert!(panel.is_zoomed(cx));
5033            assert!(panel.has_focus(cx));
5034        });
5035
5036        // Close the dock while it is zoomed
5037        workspace.update(cx, |workspace, cx| {
5038            workspace.toggle_dock(DockPosition::Right, cx)
5039        });
5040
5041        workspace.read_with(cx, |workspace, cx| {
5042            assert!(!workspace.right_dock().read(cx).is_open());
5043            assert!(panel.is_zoomed(cx));
5044            assert!(workspace.zoomed.is_none());
5045            assert!(!panel.has_focus(cx));
5046        });
5047
5048        // Opening the dock, when it's zoomed, retains focus
5049        workspace.update(cx, |workspace, cx| {
5050            workspace.toggle_dock(DockPosition::Right, cx)
5051        });
5052
5053        workspace.read_with(cx, |workspace, cx| {
5054            assert!(workspace.right_dock().read(cx).is_open());
5055            assert!(panel.is_zoomed(cx));
5056            assert!(workspace.zoomed.is_some());
5057            assert!(panel.has_focus(cx));
5058        });
5059
5060        // Unzoom and close the panel, zoom the active pane.
5061        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5062        workspace.update(cx, |workspace, cx| {
5063            workspace.toggle_dock(DockPosition::Right, cx)
5064        });
5065        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5066
5067        // Opening a dock unzooms the pane.
5068        workspace.update(cx, |workspace, cx| {
5069            workspace.toggle_dock(DockPosition::Right, cx)
5070        });
5071        workspace.read_with(cx, |workspace, cx| {
5072            let pane = pane.read(cx);
5073            assert!(!pane.is_zoomed());
5074            assert!(!pane.has_focus());
5075            assert!(workspace.right_dock().read(cx).is_open());
5076            assert!(workspace.zoomed.is_none());
5077        });
5078    }
5079
5080    #[gpui::test]
5081    async fn test_panels(cx: &mut gpui::TestAppContext) {
5082        init_test(cx);
5083        let fs = FakeFs::new(cx.background());
5084
5085        let project = Project::test(fs, [], cx).await;
5086        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5087        let workspace = window.root(cx);
5088
5089        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5090            // Add panel_1 on the left, panel_2 on the right.
5091            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5092            workspace.add_panel(panel_1.clone(), cx);
5093            workspace
5094                .left_dock()
5095                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5096            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5097            workspace.add_panel(panel_2.clone(), cx);
5098            workspace
5099                .right_dock()
5100                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5101
5102            let left_dock = workspace.left_dock();
5103            assert_eq!(
5104                left_dock.read(cx).visible_panel().unwrap().id(),
5105                panel_1.id()
5106            );
5107            assert_eq!(
5108                left_dock.read(cx).active_panel_size(cx).unwrap(),
5109                panel_1.size(cx)
5110            );
5111
5112            left_dock.update(cx, |left_dock, cx| {
5113                left_dock.resize_active_panel(Some(1337.), cx)
5114            });
5115            assert_eq!(
5116                workspace
5117                    .right_dock()
5118                    .read(cx)
5119                    .visible_panel()
5120                    .unwrap()
5121                    .id(),
5122                panel_2.id()
5123            );
5124
5125            (panel_1, panel_2)
5126        });
5127
5128        // Move panel_1 to the right
5129        panel_1.update(cx, |panel_1, cx| {
5130            panel_1.set_position(DockPosition::Right, cx)
5131        });
5132
5133        workspace.update(cx, |workspace, cx| {
5134            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5135            // Since it was the only panel on the left, the left dock should now be closed.
5136            assert!(!workspace.left_dock().read(cx).is_open());
5137            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5138            let right_dock = workspace.right_dock();
5139            assert_eq!(
5140                right_dock.read(cx).visible_panel().unwrap().id(),
5141                panel_1.id()
5142            );
5143            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5144
5145            // Now we move panel_2Β to the left
5146            panel_2.set_position(DockPosition::Left, cx);
5147        });
5148
5149        workspace.update(cx, |workspace, cx| {
5150            // Since panel_2 was not visible on the right, we don't open the left dock.
5151            assert!(!workspace.left_dock().read(cx).is_open());
5152            // And the right dock is unaffected in it's displaying of panel_1
5153            assert!(workspace.right_dock().read(cx).is_open());
5154            assert_eq!(
5155                workspace
5156                    .right_dock()
5157                    .read(cx)
5158                    .visible_panel()
5159                    .unwrap()
5160                    .id(),
5161                panel_1.id()
5162            );
5163        });
5164
5165        // Move panel_1 back to the left
5166        panel_1.update(cx, |panel_1, cx| {
5167            panel_1.set_position(DockPosition::Left, cx)
5168        });
5169
5170        workspace.update(cx, |workspace, cx| {
5171            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5172            let left_dock = workspace.left_dock();
5173            assert!(left_dock.read(cx).is_open());
5174            assert_eq!(
5175                left_dock.read(cx).visible_panel().unwrap().id(),
5176                panel_1.id()
5177            );
5178            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5179            // And right the dock should be closed as it no longer has any panels.
5180            assert!(!workspace.right_dock().read(cx).is_open());
5181
5182            // Now we move panel_1 to the bottom
5183            panel_1.set_position(DockPosition::Bottom, cx);
5184        });
5185
5186        workspace.update(cx, |workspace, cx| {
5187            // Since panel_1 was visible on the left, we close the left dock.
5188            assert!(!workspace.left_dock().read(cx).is_open());
5189            // The bottom dock is sized based on the panel's default size,
5190            // since the panel orientation changed from vertical to horizontal.
5191            let bottom_dock = workspace.bottom_dock();
5192            assert_eq!(
5193                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5194                panel_1.size(cx),
5195            );
5196            // Close bottom dock and move panel_1 back to the left.
5197            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5198            panel_1.set_position(DockPosition::Left, cx);
5199        });
5200
5201        // Emit activated event on panel 1
5202        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5203
5204        // Now the left dock is open and panel_1 is active and focused.
5205        workspace.read_with(cx, |workspace, cx| {
5206            let left_dock = workspace.left_dock();
5207            assert!(left_dock.read(cx).is_open());
5208            assert_eq!(
5209                left_dock.read(cx).visible_panel().unwrap().id(),
5210                panel_1.id()
5211            );
5212            assert!(panel_1.is_focused(cx));
5213        });
5214
5215        // Emit closed event on panel 2, which is not active
5216        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5217
5218        // Wo don't close the left dock, because panel_2 wasn't the active panel
5219        workspace.read_with(cx, |workspace, cx| {
5220            let left_dock = workspace.left_dock();
5221            assert!(left_dock.read(cx).is_open());
5222            assert_eq!(
5223                left_dock.read(cx).visible_panel().unwrap().id(),
5224                panel_1.id()
5225            );
5226        });
5227
5228        // Emitting a ZoomIn event shows the panel as zoomed.
5229        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5230        workspace.read_with(cx, |workspace, _| {
5231            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5232            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5233        });
5234
5235        // Move panel to another dock while it is zoomed
5236        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5237        workspace.read_with(cx, |workspace, _| {
5238            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5239            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5240        });
5241
5242        // If focus is transferred to another view that's not a panel or another pane, we still show
5243        // the panel as zoomed.
5244        let focus_receiver = window.add_view(cx, |_| EmptyView);
5245        focus_receiver.update(cx, |_, cx| cx.focus_self());
5246        workspace.read_with(cx, |workspace, _| {
5247            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5248            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5249        });
5250
5251        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5252        workspace.update(cx, |_, cx| cx.focus_self());
5253        workspace.read_with(cx, |workspace, _| {
5254            assert_eq!(workspace.zoomed, None);
5255            assert_eq!(workspace.zoomed_position, None);
5256        });
5257
5258        // If focus is transferred again to another view that's not a panel or a pane, we won't
5259        // show the panel as zoomed because it wasn't zoomed before.
5260        focus_receiver.update(cx, |_, cx| cx.focus_self());
5261        workspace.read_with(cx, |workspace, _| {
5262            assert_eq!(workspace.zoomed, None);
5263            assert_eq!(workspace.zoomed_position, None);
5264        });
5265
5266        // When focus is transferred back to the panel, it is zoomed again.
5267        panel_1.update(cx, |_, cx| cx.focus_self());
5268        workspace.read_with(cx, |workspace, _| {
5269            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5270            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5271        });
5272
5273        // Emitting a ZoomOut event unzooms the panel.
5274        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5275        workspace.read_with(cx, |workspace, _| {
5276            assert_eq!(workspace.zoomed, None);
5277            assert_eq!(workspace.zoomed_position, None);
5278        });
5279
5280        // Emit closed event on panel 1, which is active
5281        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5282
5283        // Now the left dock is closed, because panel_1 was the active panel
5284        workspace.read_with(cx, |workspace, cx| {
5285            let right_dock = workspace.right_dock();
5286            assert!(!right_dock.read(cx).is_open());
5287        });
5288    }
5289
5290    pub fn init_test(cx: &mut TestAppContext) {
5291        cx.foreground().forbid_parking();
5292        cx.update(|cx| {
5293            cx.set_global(SettingsStore::test(cx));
5294            theme::init((), cx);
5295            language::init(cx);
5296            crate::init_settings(cx);
5297            Project::init_settings(cx);
5298        });
5299    }
5300}