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