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