workspace.rs

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