workspace.rs

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