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