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