workspace.rs

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