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