workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::{call_settings::CallSettings, ActiveCall};
  16use client::{
  17    proto::{self, ErrorCode, PeerId},
  18    Client, ErrorExt, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use derive_more::{Deref, DerefMut};
  22use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  23use futures::{
  24    channel::{mpsc, oneshot},
  25    future::try_join_all,
  26    Future, FutureExt, StreamExt,
  27};
  28use gpui::{
  29    actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
  30    AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
  31    DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
  32    FocusableView, Global, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
  33    ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
  34    Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext, VisualContext,
  35    WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
  36};
  37use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  38use itertools::Itertools;
  39use language::{LanguageRegistry, Rope};
  40use lazy_static::lazy_static;
  41pub use modal_layer::*;
  42use node_runtime::NodeRuntime;
  43use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  44pub use pane::*;
  45pub use pane_group::*;
  46use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  47pub use persistence::{
  48    model::{ItemId, WorkspaceLocation},
  49    WorkspaceDb, DB as WORKSPACE_DB,
  50};
  51use postage::stream::Stream;
  52use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  53use runnable::SpawnInTerminal;
  54use serde::Deserialize;
  55use settings::Settings;
  56use shared_screen::SharedScreen;
  57use status_bar::StatusBar;
  58pub use status_bar::StatusItemView;
  59use std::{
  60    any::TypeId,
  61    borrow::Cow,
  62    cmp, env,
  63    path::{Path, PathBuf},
  64    sync::Weak,
  65    sync::{atomic::AtomicUsize, Arc},
  66    time::Duration,
  67};
  68use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  69pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  70pub use ui;
  71use ui::Label;
  72use util::ResultExt;
  73use uuid::Uuid;
  74pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  75
  76use crate::persistence::{
  77    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  78    SerializedAxis,
  79};
  80
  81lazy_static! {
  82    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  83        .ok()
  84        .as_deref()
  85        .and_then(parse_pixel_size_env_var);
  86    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  87        .ok()
  88        .as_deref()
  89        .and_then(parse_pixel_position_env_var);
  90}
  91
  92#[derive(Clone, PartialEq)]
  93pub struct RemoveWorktreeFromProject(pub WorktreeId);
  94
  95actions!(
  96    workspace,
  97    [
  98        Open,
  99        NewFile,
 100        NewWindow,
 101        CloseWindow,
 102        CloseInactiveTabsAndPanes,
 103        AddFolderToProject,
 104        Unfollow,
 105        SaveAs,
 106        ReloadActiveItem,
 107        ActivatePreviousPane,
 108        ActivateNextPane,
 109        FollowNextCollaborator,
 110        NewTerminal,
 111        NewCenterTerminal,
 112        NewSearch,
 113        Feedback,
 114        Restart,
 115        Welcome,
 116        ToggleZoom,
 117        ToggleLeftDock,
 118        ToggleRightDock,
 119        ToggleBottomDock,
 120        CloseAllDocks,
 121        ToggleGraphicsProfiler,
 122    ]
 123);
 124
 125#[derive(Clone, PartialEq)]
 126pub struct OpenPaths {
 127    pub paths: Vec<PathBuf>,
 128}
 129
 130#[derive(Clone, Deserialize, PartialEq)]
 131pub struct ActivatePane(pub usize);
 132
 133#[derive(Clone, Deserialize, PartialEq)]
 134pub struct ActivatePaneInDirection(pub SplitDirection);
 135
 136#[derive(Clone, Deserialize, PartialEq)]
 137pub struct SwapPaneInDirection(pub SplitDirection);
 138
 139#[derive(Clone, Deserialize, PartialEq)]
 140pub struct NewFileInDirection(pub SplitDirection);
 141
 142#[derive(Clone, PartialEq, Debug, Deserialize)]
 143#[serde(rename_all = "camelCase")]
 144pub struct SaveAll {
 145    pub save_intent: Option<SaveIntent>,
 146}
 147
 148#[derive(Clone, PartialEq, Debug, Deserialize)]
 149#[serde(rename_all = "camelCase")]
 150pub struct Save {
 151    pub save_intent: Option<SaveIntent>,
 152}
 153
 154#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 155#[serde(rename_all = "camelCase")]
 156pub struct CloseAllItemsAndPanes {
 157    pub save_intent: Option<SaveIntent>,
 158}
 159
 160impl_actions!(
 161    workspace,
 162    [
 163        ActivatePane,
 164        ActivatePaneInDirection,
 165        CloseAllItemsAndPanes,
 166        NewFileInDirection,
 167        OpenTerminal,
 168        Save,
 169        SaveAll,
 170        SwapPaneInDirection,
 171    ]
 172);
 173
 174#[derive(Deserialize)]
 175pub struct Toast {
 176    id: usize,
 177    msg: Cow<'static, str>,
 178    #[serde(skip)]
 179    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 180}
 181
 182impl Toast {
 183    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 184        Toast {
 185            id,
 186            msg: msg.into(),
 187            on_click: None,
 188        }
 189    }
 190
 191    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 192    where
 193        M: Into<Cow<'static, str>>,
 194        F: Fn(&mut WindowContext) + 'static,
 195    {
 196        self.on_click = Some((message.into(), Arc::new(on_click)));
 197        self
 198    }
 199}
 200
 201impl PartialEq for Toast {
 202    fn eq(&self, other: &Self) -> bool {
 203        self.id == other.id
 204            && self.msg == other.msg
 205            && self.on_click.is_some() == other.on_click.is_some()
 206    }
 207}
 208
 209impl Clone for Toast {
 210    fn clone(&self) -> Self {
 211        Toast {
 212            id: self.id,
 213            msg: self.msg.to_owned(),
 214            on_click: self.on_click.clone(),
 215        }
 216    }
 217}
 218
 219#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 220pub struct OpenTerminal {
 221    pub working_directory: PathBuf,
 222}
 223
 224pub type WorkspaceId = i64;
 225
 226pub fn init_settings(cx: &mut AppContext) {
 227    WorkspaceSettings::register(cx);
 228    ItemSettings::register(cx);
 229}
 230
 231pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 232    init_settings(cx);
 233    notifications::init(cx);
 234
 235    cx.on_action(Workspace::close_global);
 236    cx.on_action(restart);
 237
 238    cx.on_action({
 239        let app_state = Arc::downgrade(&app_state);
 240        move |_: &Open, cx: &mut AppContext| {
 241            let paths = cx.prompt_for_paths(PathPromptOptions {
 242                files: true,
 243                directories: true,
 244                multiple: true,
 245            });
 246
 247            if let Some(app_state) = app_state.upgrade() {
 248                cx.spawn(move |cx| async move {
 249                    if let Some(paths) = paths.await.log_err().flatten() {
 250                        cx.update(|cx| {
 251                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 252                        })
 253                        .ok();
 254                    }
 255                })
 256                .detach();
 257            }
 258        }
 259    });
 260}
 261
 262#[derive(Clone, Default, Deref, DerefMut)]
 263struct ProjectItemBuilders(
 264    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>,
 265);
 266
 267impl Global for ProjectItemBuilders {}
 268
 269pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 270    let builders = cx.default_global::<ProjectItemBuilders>();
 271    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 272        let item = model.downcast::<I::Item>().unwrap();
 273        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 274    });
 275}
 276
 277type FollowableItemBuilder = fn(
 278    View<Pane>,
 279    View<Workspace>,
 280    ViewId,
 281    &mut Option<proto::view::Variant>,
 282    &mut WindowContext,
 283) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 284
 285#[derive(Default, Deref, DerefMut)]
 286struct FollowableItemBuilders(
 287    HashMap<
 288        TypeId,
 289        (
 290            FollowableItemBuilder,
 291            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 292        ),
 293    >,
 294);
 295
 296impl Global for FollowableItemBuilders {}
 297
 298pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 299    let builders = cx.default_global::<FollowableItemBuilders>();
 300    builders.insert(
 301        TypeId::of::<I>(),
 302        (
 303            |pane, workspace, id, state, cx| {
 304                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 305                    cx.foreground_executor()
 306                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 307                })
 308            },
 309            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 310        ),
 311    );
 312}
 313
 314#[derive(Default, Deref, DerefMut)]
 315struct ItemDeserializers(
 316    HashMap<
 317        Arc<str>,
 318        fn(
 319            Model<Project>,
 320            WeakView<Workspace>,
 321            WorkspaceId,
 322            ItemId,
 323            &mut ViewContext<Pane>,
 324        ) -> Task<Result<Box<dyn ItemHandle>>>,
 325    >,
 326);
 327
 328impl Global for ItemDeserializers {}
 329
 330pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 331    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 332        let deserializers = cx.default_global::<ItemDeserializers>();
 333        deserializers.insert(
 334            Arc::from(serialized_item_kind),
 335            |project, workspace, workspace_id, item_id, cx| {
 336                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 337                cx.foreground_executor()
 338                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 339            },
 340        );
 341    }
 342}
 343
 344pub struct AppState {
 345    pub languages: Arc<LanguageRegistry>,
 346    pub client: Arc<Client>,
 347    pub user_store: Model<UserStore>,
 348    pub workspace_store: Model<WorkspaceStore>,
 349    pub fs: Arc<dyn fs::Fs>,
 350    pub build_window_options:
 351        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 352    pub node_runtime: Arc<dyn NodeRuntime>,
 353}
 354
 355struct GlobalAppState(Weak<AppState>);
 356
 357impl Global for GlobalAppState {}
 358
 359pub struct WorkspaceStore {
 360    workspaces: HashSet<WindowHandle<Workspace>>,
 361    client: Arc<Client>,
 362    _subscriptions: Vec<client::Subscription>,
 363}
 364
 365#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 366struct Follower {
 367    project_id: Option<u64>,
 368    peer_id: PeerId,
 369}
 370
 371impl AppState {
 372    pub fn global(cx: &AppContext) -> Weak<Self> {
 373        cx.global::<GlobalAppState>().0.clone()
 374    }
 375    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 376        cx.try_global::<GlobalAppState>()
 377            .map(|state| state.0.clone())
 378    }
 379    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 380        cx.set_global(GlobalAppState(state));
 381    }
 382
 383    #[cfg(any(test, feature = "test-support"))]
 384    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 385        use node_runtime::FakeNodeRuntime;
 386        use settings::SettingsStore;
 387
 388        if !cx.has_global::<SettingsStore>() {
 389            let settings_store = SettingsStore::test(cx);
 390            cx.set_global(settings_store);
 391        }
 392
 393        let fs = fs::FakeFs::new(cx.background_executor().clone());
 394        let languages = Arc::new(LanguageRegistry::test());
 395        let http_client = util::http::FakeHttpClient::with_404_response();
 396        let client = Client::new(http_client.clone(), cx);
 397        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 398        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 399
 400        theme::init(theme::LoadThemes::JustBase, cx);
 401        client::init(&client, cx);
 402        crate::init_settings(cx);
 403
 404        Arc::new(Self {
 405            client,
 406            fs,
 407            languages,
 408            user_store,
 409            workspace_store,
 410            node_runtime: FakeNodeRuntime::new(),
 411            build_window_options: |_, _, _| Default::default(),
 412        })
 413    }
 414}
 415
 416struct DelayedDebouncedEditAction {
 417    task: Option<Task<()>>,
 418    cancel_channel: Option<oneshot::Sender<()>>,
 419}
 420
 421impl DelayedDebouncedEditAction {
 422    fn new() -> DelayedDebouncedEditAction {
 423        DelayedDebouncedEditAction {
 424            task: None,
 425            cancel_channel: None,
 426        }
 427    }
 428
 429    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 430    where
 431        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 432    {
 433        if let Some(channel) = self.cancel_channel.take() {
 434            _ = channel.send(());
 435        }
 436
 437        let (sender, mut receiver) = oneshot::channel::<()>();
 438        self.cancel_channel = Some(sender);
 439
 440        let previous_task = self.task.take();
 441        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 442            let mut timer = cx.background_executor().timer(delay).fuse();
 443            if let Some(previous_task) = previous_task {
 444                previous_task.await;
 445            }
 446
 447            futures::select_biased! {
 448                _ = receiver => return,
 449                    _ = timer => {}
 450            }
 451
 452            if let Some(result) = workspace
 453                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 454                .log_err()
 455            {
 456                result.await.log_err();
 457            }
 458        }));
 459    }
 460}
 461
 462pub enum Event {
 463    PaneAdded(View<Pane>),
 464    ContactRequestedJoin(u64),
 465    WorkspaceCreated(WeakView<Workspace>),
 466    SpawnRunnable(SpawnInTerminal),
 467}
 468
 469pub enum OpenVisible {
 470    All,
 471    None,
 472    OnlyFiles,
 473    OnlyDirectories,
 474}
 475
 476pub struct Workspace {
 477    weak_self: WeakView<Self>,
 478    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 479    zoomed: Option<AnyWeakView>,
 480    zoomed_position: Option<DockPosition>,
 481    center: PaneGroup,
 482    left_dock: View<Dock>,
 483    bottom_dock: View<Dock>,
 484    right_dock: View<Dock>,
 485    panes: Vec<View<Pane>>,
 486    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 487    active_pane: View<Pane>,
 488    last_active_center_pane: Option<WeakView<Pane>>,
 489    last_active_view_id: Option<proto::ViewId>,
 490    status_bar: View<StatusBar>,
 491    modal_layer: View<ModalLayer>,
 492    titlebar_item: Option<AnyView>,
 493    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 494    project: Model<Project>,
 495    follower_states: HashMap<View<Pane>, FollowerState>,
 496    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 497    window_edited: bool,
 498    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 499    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 500    database_id: WorkspaceId,
 501    app_state: Arc<AppState>,
 502    _subscriptions: Vec<Subscription>,
 503    _apply_leader_updates: Task<Result<()>>,
 504    _observe_current_user: Task<Result<()>>,
 505    _schedule_serialize: Option<Task<()>>,
 506    pane_history_timestamp: Arc<AtomicUsize>,
 507    bounds: Bounds<Pixels>,
 508}
 509
 510impl EventEmitter<Event> for Workspace {}
 511
 512#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 513pub struct ViewId {
 514    pub creator: PeerId,
 515    pub id: u64,
 516}
 517
 518#[derive(Default)]
 519struct FollowerState {
 520    leader_id: PeerId,
 521    active_view_id: Option<ViewId>,
 522    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 523}
 524
 525impl Workspace {
 526    pub fn new(
 527        workspace_id: WorkspaceId,
 528        project: Model<Project>,
 529        app_state: Arc<AppState>,
 530        cx: &mut ViewContext<Self>,
 531    ) -> Self {
 532        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 533        cx.subscribe(&project, move |this, _, event, cx| {
 534            match event {
 535                project::Event::RemoteIdChanged(_) => {
 536                    this.update_window_title(cx);
 537                }
 538
 539                project::Event::CollaboratorLeft(peer_id) => {
 540                    this.collaborator_left(*peer_id, cx);
 541                }
 542
 543                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 544                    this.update_window_title(cx);
 545                    this.serialize_workspace(cx);
 546                }
 547
 548                project::Event::DisconnectedFromHost => {
 549                    this.update_window_edited(cx);
 550                    let panes_to_unfollow: Vec<View<Pane>> =
 551                        this.follower_states.keys().map(|k| k.clone()).collect();
 552                    for pane in panes_to_unfollow {
 553                        this.unfollow(&pane, cx);
 554                    }
 555                    cx.disable_focus();
 556                }
 557
 558                project::Event::Closed => {
 559                    cx.remove_window();
 560                }
 561
 562                project::Event::DeletedEntry(entry_id) => {
 563                    for pane in this.panes.iter() {
 564                        pane.update(cx, |pane, cx| {
 565                            pane.handle_deleted_project_item(*entry_id, cx)
 566                        });
 567                    }
 568                }
 569
 570                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 571                    cx.new_view(|_| MessageNotification::new(message.clone()))
 572                }),
 573
 574                project::Event::LanguageServerPrompt(request) => {
 575                    let request = request.clone();
 576
 577                    cx.spawn(|_, mut cx| async move {
 578                        let messages = request
 579                            .actions
 580                            .iter()
 581                            .map(|action| action.title.as_str())
 582                            .collect::<Vec<_>>();
 583                        let index = cx
 584                            .update(|cx| {
 585                                cx.prompt(request.level, "", Some(&request.message), &messages)
 586                            })?
 587                            .await?;
 588                        request.respond(index).await;
 589
 590                        Result::<(), anyhow::Error>::Ok(())
 591                    })
 592                    .detach()
 593                }
 594
 595                _ => {}
 596            }
 597            cx.notify()
 598        })
 599        .detach();
 600
 601        cx.on_focus_lost(|this, cx| {
 602            let focus_handle = this.focus_handle(cx);
 603            cx.focus(&focus_handle);
 604        })
 605        .detach();
 606
 607        let weak_handle = cx.view().downgrade();
 608        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 609
 610        let center_pane = cx.new_view(|cx| {
 611            Pane::new(
 612                weak_handle.clone(),
 613                project.clone(),
 614                pane_history_timestamp.clone(),
 615                None,
 616                cx,
 617            )
 618        });
 619        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 620
 621        cx.focus_view(&center_pane);
 622        cx.emit(Event::PaneAdded(center_pane.clone()));
 623
 624        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 625        app_state.workspace_store.update(cx, |store, _| {
 626            store.workspaces.insert(window_handle);
 627        });
 628
 629        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 630        let mut connection_status = app_state.client.status();
 631        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 632            current_user.next().await;
 633            connection_status.next().await;
 634            let mut stream =
 635                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 636
 637            while stream.recv().await.is_some() {
 638                this.update(&mut cx, |_, cx| cx.notify())?;
 639            }
 640            anyhow::Ok(())
 641        });
 642
 643        // All leader updates are enqueued and then processed in a single task, so
 644        // that each asynchronous operation can be run in order.
 645        let (leader_updates_tx, mut leader_updates_rx) =
 646            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 647        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 648            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 649                Self::process_leader_update(&this, leader_id, update, &mut cx)
 650                    .await
 651                    .log_err();
 652            }
 653
 654            Ok(())
 655        });
 656
 657        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 658
 659        let left_dock = Dock::new(DockPosition::Left, cx);
 660        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 661        let right_dock = Dock::new(DockPosition::Right, cx);
 662        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 663        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 664        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 665        let status_bar = cx.new_view(|cx| {
 666            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 667            status_bar.add_left_item(left_dock_buttons, cx);
 668            status_bar.add_right_item(right_dock_buttons, cx);
 669            status_bar.add_right_item(bottom_dock_buttons, cx);
 670            status_bar
 671        });
 672
 673        let modal_layer = cx.new_view(|_| ModalLayer::new());
 674
 675        let mut active_call = None;
 676        if let Some(call) = ActiveCall::try_global(cx) {
 677            let call = call.clone();
 678            let mut subscriptions = Vec::new();
 679            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 680            active_call = Some((call, subscriptions));
 681        }
 682
 683        let subscriptions = vec![
 684            cx.observe_window_activation(Self::on_window_activation_changed),
 685            cx.observe_window_bounds(move |_, cx| {
 686                if let Some(display) = cx.display() {
 687                    // Transform fixed bounds to be stored in terms of the containing display
 688                    let mut bounds = cx.window_bounds();
 689                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 690                        let display_bounds = display.bounds();
 691                        window_bounds.origin.x -= display_bounds.origin.x;
 692                        window_bounds.origin.y -= display_bounds.origin.y;
 693                    }
 694
 695                    if let Some(display_uuid) = display.uuid().log_err() {
 696                        cx.background_executor()
 697                            .spawn(DB.set_window_bounds(
 698                                workspace_id,
 699                                SerializedWindowsBounds(bounds),
 700                                display_uuid,
 701                            ))
 702                            .detach_and_log_err(cx);
 703                    }
 704                }
 705                cx.notify();
 706            }),
 707            cx.observe_window_appearance(|_, cx| {
 708                let window_appearance = cx.appearance();
 709
 710                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 711
 712                ThemeSettings::reload_current_theme(cx);
 713            }),
 714            cx.observe(&left_dock, |this, _, cx| {
 715                this.serialize_workspace(cx);
 716                cx.notify();
 717            }),
 718            cx.observe(&bottom_dock, |this, _, cx| {
 719                this.serialize_workspace(cx);
 720                cx.notify();
 721            }),
 722            cx.observe(&right_dock, |this, _, cx| {
 723                this.serialize_workspace(cx);
 724                cx.notify();
 725            }),
 726            cx.on_release(|this, window, cx| {
 727                this.app_state.workspace_store.update(cx, |store, _| {
 728                    let window = window.downcast::<Self>().unwrap();
 729                    store.workspaces.remove(&window);
 730                })
 731            }),
 732        ];
 733
 734        cx.defer(|this, cx| {
 735            this.update_window_title(cx);
 736        });
 737        Workspace {
 738            weak_self: weak_handle.clone(),
 739            zoomed: None,
 740            zoomed_position: None,
 741            center: PaneGroup::new(center_pane.clone()),
 742            panes: vec![center_pane.clone()],
 743            panes_by_item: Default::default(),
 744            active_pane: center_pane.clone(),
 745            last_active_center_pane: Some(center_pane.downgrade()),
 746            last_active_view_id: None,
 747            status_bar,
 748            modal_layer,
 749            titlebar_item: None,
 750            notifications: Default::default(),
 751            left_dock,
 752            bottom_dock,
 753            right_dock,
 754            project: project.clone(),
 755            follower_states: Default::default(),
 756            last_leaders_by_pane: Default::default(),
 757            window_edited: false,
 758            active_call,
 759            database_id: workspace_id,
 760            app_state,
 761            _observe_current_user,
 762            _apply_leader_updates,
 763            _schedule_serialize: None,
 764            leader_updates_tx,
 765            _subscriptions: subscriptions,
 766            pane_history_timestamp,
 767            workspace_actions: Default::default(),
 768            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 769            bounds: Default::default(),
 770        }
 771    }
 772
 773    fn new_local(
 774        abs_paths: Vec<PathBuf>,
 775        app_state: Arc<AppState>,
 776        requesting_window: Option<WindowHandle<Workspace>>,
 777        cx: &mut AppContext,
 778    ) -> Task<
 779        anyhow::Result<(
 780            WindowHandle<Workspace>,
 781            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 782        )>,
 783    > {
 784        let project_handle = Project::local(
 785            app_state.client.clone(),
 786            app_state.node_runtime.clone(),
 787            app_state.user_store.clone(),
 788            app_state.languages.clone(),
 789            app_state.fs.clone(),
 790            cx,
 791        );
 792
 793        cx.spawn(|mut cx| async move {
 794            let serialized_workspace: Option<SerializedWorkspace> =
 795                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 796
 797            let paths_to_open = Arc::new(abs_paths);
 798
 799            // Get project paths for all of the abs_paths
 800            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 801            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 802                Vec::with_capacity(paths_to_open.len());
 803            for path in paths_to_open.iter().cloned() {
 804                if let Some((worktree, project_entry)) = cx
 805                    .update(|cx| {
 806                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 807                    })?
 808                    .await
 809                    .log_err()
 810                {
 811                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 812                    project_paths.push((path, Some(project_entry)));
 813                } else {
 814                    project_paths.push((path, None));
 815                }
 816            }
 817
 818            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 819                serialized_workspace.id
 820            } else {
 821                DB.next_id().await.unwrap_or(0)
 822            };
 823
 824            let window = if let Some(window) = requesting_window {
 825                cx.update_window(window.into(), |_, cx| {
 826                    cx.replace_root_view(|cx| {
 827                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 828                    });
 829                })?;
 830                window
 831            } else {
 832                let window_bounds_override = window_bounds_env_override(&cx);
 833                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 834                    (Some(bounds), None)
 835                } else {
 836                    serialized_workspace
 837                        .as_ref()
 838                        .and_then(|serialized_workspace| {
 839                            let serialized_display = serialized_workspace.display?;
 840                            let mut bounds = serialized_workspace.bounds?;
 841
 842                            // Stored bounds are relative to the containing display.
 843                            // So convert back to global coordinates if that screen still exists
 844                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 845                                let screen = cx
 846                                    .update(|cx| {
 847                                        cx.displays().into_iter().find(|display| {
 848                                            display.uuid().ok() == Some(serialized_display)
 849                                        })
 850                                    })
 851                                    .ok()??;
 852                                let screen_bounds = screen.bounds();
 853                                window_bounds.origin.x += screen_bounds.origin.x;
 854                                window_bounds.origin.y += screen_bounds.origin.y;
 855                                bounds = WindowBounds::Fixed(window_bounds);
 856                            }
 857
 858                            Some((bounds, serialized_display))
 859                        })
 860                        .unzip()
 861                };
 862
 863                // Use the serialized workspace to construct the new window
 864                let options =
 865                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 866
 867                cx.open_window(options, {
 868                    let app_state = app_state.clone();
 869                    let workspace_id = workspace_id.clone();
 870                    let project_handle = project_handle.clone();
 871                    move |cx| {
 872                        cx.new_view(|cx| {
 873                            Workspace::new(workspace_id, project_handle, app_state, cx)
 874                        })
 875                    }
 876                })?
 877            };
 878
 879            window
 880                .update(&mut cx, |_, cx| cx.activate_window())
 881                .log_err();
 882
 883            notify_if_database_failed(window, &mut cx);
 884            let opened_items = window
 885                .update(&mut cx, |_workspace, cx| {
 886                    open_items(serialized_workspace, project_paths, app_state, cx)
 887                })?
 888                .await
 889                .unwrap_or_default();
 890
 891            Ok((window, opened_items))
 892        })
 893    }
 894
 895    pub fn weak_handle(&self) -> WeakView<Self> {
 896        self.weak_self.clone()
 897    }
 898
 899    pub fn left_dock(&self) -> &View<Dock> {
 900        &self.left_dock
 901    }
 902
 903    pub fn bottom_dock(&self) -> &View<Dock> {
 904        &self.bottom_dock
 905    }
 906
 907    pub fn right_dock(&self) -> &View<Dock> {
 908        &self.right_dock
 909    }
 910
 911    pub fn is_edited(&self) -> bool {
 912        self.window_edited
 913    }
 914
 915    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
 916        let dock = match panel.position(cx) {
 917            DockPosition::Left => &self.left_dock,
 918            DockPosition::Bottom => &self.bottom_dock,
 919            DockPosition::Right => &self.right_dock,
 920        };
 921
 922        dock.update(cx, |dock, cx| {
 923            dock.add_panel(panel, self.weak_self.clone(), cx)
 924        });
 925    }
 926
 927    pub fn status_bar(&self) -> &View<StatusBar> {
 928        &self.status_bar
 929    }
 930
 931    pub fn app_state(&self) -> &Arc<AppState> {
 932        &self.app_state
 933    }
 934
 935    pub fn user_store(&self) -> &Model<UserStore> {
 936        &self.app_state.user_store
 937    }
 938
 939    pub fn project(&self) -> &Model<Project> {
 940        &self.project
 941    }
 942
 943    pub fn recent_navigation_history(
 944        &self,
 945        limit: Option<usize>,
 946        cx: &AppContext,
 947    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 948        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 949        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 950        for pane in &self.panes {
 951            let pane = pane.read(cx);
 952            pane.nav_history()
 953                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 954                    if let Some(fs_path) = &fs_path {
 955                        abs_paths_opened
 956                            .entry(fs_path.clone())
 957                            .or_default()
 958                            .insert(project_path.clone());
 959                    }
 960                    let timestamp = entry.timestamp;
 961                    match history.entry(project_path) {
 962                        hash_map::Entry::Occupied(mut entry) => {
 963                            let (_, old_timestamp) = entry.get();
 964                            if &timestamp > old_timestamp {
 965                                entry.insert((fs_path, timestamp));
 966                            }
 967                        }
 968                        hash_map::Entry::Vacant(entry) => {
 969                            entry.insert((fs_path, timestamp));
 970                        }
 971                    }
 972                });
 973        }
 974
 975        history
 976            .into_iter()
 977            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 978            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 979            .rev()
 980            .filter(|(history_path, abs_path)| {
 981                let latest_project_path_opened = abs_path
 982                    .as_ref()
 983                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 984                    .and_then(|project_paths| {
 985                        project_paths
 986                            .iter()
 987                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 988                    });
 989
 990                match latest_project_path_opened {
 991                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 992                    None => true,
 993                }
 994            })
 995            .take(limit.unwrap_or(usize::MAX))
 996            .collect()
 997    }
 998
 999    fn navigate_history(
1000        &mut self,
1001        pane: WeakView<Pane>,
1002        mode: NavigationMode,
1003        cx: &mut ViewContext<Workspace>,
1004    ) -> Task<Result<()>> {
1005        let to_load = if let Some(pane) = pane.upgrade() {
1006            pane.update(cx, |pane, cx| {
1007                pane.focus(cx);
1008                loop {
1009                    // Retrieve the weak item handle from the history.
1010                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1011
1012                    // If the item is still present in this pane, then activate it.
1013                    if let Some(index) = entry
1014                        .item
1015                        .upgrade()
1016                        .and_then(|v| pane.index_for_item(v.as_ref()))
1017                    {
1018                        let prev_active_item_index = pane.active_item_index();
1019                        pane.nav_history_mut().set_mode(mode);
1020                        pane.activate_item(index, true, true, cx);
1021                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1022
1023                        let mut navigated = prev_active_item_index != pane.active_item_index();
1024                        if let Some(data) = entry.data {
1025                            navigated |= pane.active_item()?.navigate(data, cx);
1026                        }
1027
1028                        if navigated {
1029                            break None;
1030                        }
1031                    }
1032                    // If the item is no longer present in this pane, then retrieve its
1033                    // project path in order to reopen it.
1034                    else {
1035                        break pane
1036                            .nav_history()
1037                            .path_for_item(entry.item.id())
1038                            .map(|(project_path, _)| (project_path, entry));
1039                    }
1040                }
1041            })
1042        } else {
1043            None
1044        };
1045
1046        if let Some((project_path, entry)) = to_load {
1047            // If the item was no longer present, then load it again from its previous path.
1048            let task = self.load_path(project_path, cx);
1049            cx.spawn(|workspace, mut cx| async move {
1050                let task = task.await;
1051                let mut navigated = false;
1052                if let Some((project_entry_id, build_item)) = task.log_err() {
1053                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1054                        pane.nav_history_mut().set_mode(mode);
1055                        pane.active_item().map(|p| p.item_id())
1056                    })?;
1057
1058                    pane.update(&mut cx, |pane, cx| {
1059                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1060                        navigated |= Some(item.item_id()) != prev_active_item_id;
1061                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1062                        if let Some(data) = entry.data {
1063                            navigated |= item.navigate(data, cx);
1064                        }
1065                    })?;
1066                }
1067
1068                if !navigated {
1069                    workspace
1070                        .update(&mut cx, |workspace, cx| {
1071                            Self::navigate_history(workspace, pane, mode, cx)
1072                        })?
1073                        .await?;
1074                }
1075
1076                Ok(())
1077            })
1078        } else {
1079            Task::ready(Ok(()))
1080        }
1081    }
1082
1083    pub fn go_back(
1084        &mut self,
1085        pane: WeakView<Pane>,
1086        cx: &mut ViewContext<Workspace>,
1087    ) -> Task<Result<()>> {
1088        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1089    }
1090
1091    pub fn go_forward(
1092        &mut self,
1093        pane: WeakView<Pane>,
1094        cx: &mut ViewContext<Workspace>,
1095    ) -> Task<Result<()>> {
1096        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1097    }
1098
1099    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1100        self.navigate_history(
1101            self.active_pane().downgrade(),
1102            NavigationMode::ReopeningClosedItem,
1103            cx,
1104        )
1105    }
1106
1107    pub fn client(&self) -> &Client {
1108        &self.app_state.client
1109    }
1110
1111    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1112        self.titlebar_item = Some(item);
1113        cx.notify();
1114    }
1115
1116    pub fn titlebar_item(&self) -> Option<AnyView> {
1117        self.titlebar_item.clone()
1118    }
1119
1120    /// Call the given callback with a workspace whose project is local.
1121    ///
1122    /// If the given workspace has a local project, then it will be passed
1123    /// to the callback. Otherwise, a new empty window will be created.
1124    pub fn with_local_workspace<T, F>(
1125        &mut self,
1126        cx: &mut ViewContext<Self>,
1127        callback: F,
1128    ) -> Task<Result<T>>
1129    where
1130        T: 'static,
1131        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1132    {
1133        if self.project.read(cx).is_local() {
1134            Task::Ready(Some(Ok(callback(self, cx))))
1135        } else {
1136            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1137            cx.spawn(|_vh, mut cx| async move {
1138                let (workspace, _) = task.await?;
1139                workspace.update(&mut cx, callback)
1140            })
1141        }
1142    }
1143
1144    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1145        self.project.read(cx).worktrees()
1146    }
1147
1148    pub fn visible_worktrees<'a>(
1149        &self,
1150        cx: &'a AppContext,
1151    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1152        self.project.read(cx).visible_worktrees(cx)
1153    }
1154
1155    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1156        let futures = self
1157            .worktrees(cx)
1158            .filter_map(|worktree| worktree.read(cx).as_local())
1159            .map(|worktree| worktree.scan_complete())
1160            .collect::<Vec<_>>();
1161        async move {
1162            for future in futures {
1163                future.await;
1164            }
1165        }
1166    }
1167
1168    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1169        cx.defer(|cx| {
1170            cx.windows().iter().find(|window| {
1171                window
1172                    .update(cx, |_, window| {
1173                        if window.is_window_active() {
1174                            //This can only get called when the window's project connection has been lost
1175                            //so we don't need to prompt the user for anything and instead just close the window
1176                            window.remove_window();
1177                            true
1178                        } else {
1179                            false
1180                        }
1181                    })
1182                    .unwrap_or(false)
1183            });
1184        });
1185    }
1186
1187    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1188        let window = cx.window_handle();
1189        let prepare = self.prepare_to_close(false, cx);
1190        cx.spawn(|_, mut cx| async move {
1191            if prepare.await? {
1192                window.update(&mut cx, |_, cx| {
1193                    cx.remove_window();
1194                })?;
1195            }
1196            anyhow::Ok(())
1197        })
1198        .detach_and_log_err(cx)
1199    }
1200
1201    pub fn prepare_to_close(
1202        &mut self,
1203        quitting: bool,
1204        cx: &mut ViewContext<Self>,
1205    ) -> Task<Result<bool>> {
1206        let active_call = self.active_call().cloned();
1207        let window = cx.window_handle();
1208
1209        cx.spawn(|this, mut cx| async move {
1210            let workspace_count = (*cx).update(|cx| {
1211                cx.windows()
1212                    .iter()
1213                    .filter(|window| window.downcast::<Workspace>().is_some())
1214                    .count()
1215            })?;
1216
1217            if let Some(active_call) = active_call {
1218                if !quitting
1219                    && workspace_count == 1
1220                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1221                {
1222                    let answer = window.update(&mut cx, |_, cx| {
1223                        cx.prompt(
1224                            PromptLevel::Warning,
1225                            "Do you want to leave the current call?",
1226                            None,
1227                            &["Close window and hang up", "Cancel"],
1228                        )
1229                    })?;
1230
1231                    if answer.await.log_err() == Some(1) {
1232                        return anyhow::Ok(false);
1233                    } else {
1234                        active_call
1235                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1236                            .await
1237                            .log_err();
1238                    }
1239                }
1240            }
1241
1242            Ok(this
1243                .update(&mut cx, |this, cx| {
1244                    this.save_all_internal(SaveIntent::Close, cx)
1245                })?
1246                .await?)
1247        })
1248    }
1249
1250    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1251        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1252            .detach_and_log_err(cx);
1253    }
1254
1255    fn save_all_internal(
1256        &mut self,
1257        mut save_intent: SaveIntent,
1258        cx: &mut ViewContext<Self>,
1259    ) -> Task<Result<bool>> {
1260        if self.project.read(cx).is_disconnected() {
1261            return Task::ready(Ok(true));
1262        }
1263        let dirty_items = self
1264            .panes
1265            .iter()
1266            .flat_map(|pane| {
1267                pane.read(cx).items().filter_map(|item| {
1268                    if item.is_dirty(cx) {
1269                        Some((pane.downgrade(), item.boxed_clone()))
1270                    } else {
1271                        None
1272                    }
1273                })
1274            })
1275            .collect::<Vec<_>>();
1276
1277        let project = self.project.clone();
1278        cx.spawn(|workspace, mut cx| async move {
1279            // Override save mode and display "Save all files" prompt
1280            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1281                let answer = workspace.update(&mut cx, |_, cx| {
1282                    let (prompt, detail) = Pane::file_names_for_prompt(
1283                        &mut dirty_items.iter().map(|(_, handle)| handle),
1284                        dirty_items.len(),
1285                        cx,
1286                    );
1287                    cx.prompt(
1288                        PromptLevel::Warning,
1289                        &prompt,
1290                        Some(&detail),
1291                        &["Save all", "Discard all", "Cancel"],
1292                    )
1293                })?;
1294                match answer.await.log_err() {
1295                    Some(0) => save_intent = SaveIntent::SaveAll,
1296                    Some(1) => save_intent = SaveIntent::Skip,
1297                    _ => {}
1298                }
1299            }
1300            for (pane, item) in dirty_items {
1301                let (singleton, project_entry_ids) =
1302                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1303                if singleton || !project_entry_ids.is_empty() {
1304                    if let Some(ix) =
1305                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1306                    {
1307                        if !Pane::save_item(
1308                            project.clone(),
1309                            &pane,
1310                            ix,
1311                            &*item,
1312                            save_intent,
1313                            &mut cx,
1314                        )
1315                        .await?
1316                        {
1317                            return Ok(false);
1318                        }
1319                    }
1320                }
1321            }
1322            Ok(true)
1323        })
1324    }
1325
1326    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1327        self.client()
1328            .telemetry()
1329            .report_app_event("open project".to_string());
1330        let paths = cx.prompt_for_paths(PathPromptOptions {
1331            files: true,
1332            directories: true,
1333            multiple: true,
1334        });
1335
1336        cx.spawn(|this, mut cx| async move {
1337            let Some(paths) = paths.await.log_err().flatten() else {
1338                return;
1339            };
1340
1341            if let Some(task) = this
1342                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1343                .log_err()
1344            {
1345                task.await.log_err();
1346            }
1347        })
1348        .detach()
1349    }
1350
1351    pub fn open_workspace_for_paths(
1352        &mut self,
1353        paths: Vec<PathBuf>,
1354        cx: &mut ViewContext<Self>,
1355    ) -> Task<Result<()>> {
1356        let window = cx.window_handle().downcast::<Self>();
1357        let is_remote = self.project.read(cx).is_remote();
1358        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1359        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1360        let window_to_replace = if is_remote || has_worktree || has_dirty_items {
1361            None
1362        } else {
1363            window
1364        };
1365        let app_state = self.app_state.clone();
1366
1367        cx.spawn(|_, mut cx| async move {
1368            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1369                .await?;
1370            Ok(())
1371        })
1372    }
1373
1374    #[allow(clippy::type_complexity)]
1375    pub fn open_paths(
1376        &mut self,
1377        mut abs_paths: Vec<PathBuf>,
1378        visible: OpenVisible,
1379        pane: Option<WeakView<Pane>>,
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(move |this, mut cx| async move {
1389            let mut tasks = Vec::with_capacity(abs_paths.len());
1390
1391            for abs_path in &abs_paths {
1392                let visible = match visible {
1393                    OpenVisible::All => Some(true),
1394                    OpenVisible::None => Some(false),
1395                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1396                        Some(Some(metadata)) => Some(!metadata.is_dir),
1397                        Some(None) => {
1398                            log::error!("No metadata for file {abs_path:?}");
1399                            None
1400                        }
1401                        None => None,
1402                    },
1403                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1404                        Some(Some(metadata)) => Some(metadata.is_dir),
1405                        Some(None) => {
1406                            log::error!("No metadata for file {abs_path:?}");
1407                            None
1408                        }
1409                        None => None,
1410                    },
1411                };
1412                let project_path = match visible {
1413                    Some(visible) => match this
1414                        .update(&mut cx, |this, cx| {
1415                            Workspace::project_path_for_path(
1416                                this.project.clone(),
1417                                abs_path,
1418                                visible,
1419                                cx,
1420                            )
1421                        })
1422                        .log_err()
1423                    {
1424                        Some(project_path) => project_path.await.log_err(),
1425                        None => None,
1426                    },
1427                    None => None,
1428                };
1429
1430                let this = this.clone();
1431                let abs_path = abs_path.clone();
1432                let fs = fs.clone();
1433                let pane = pane.clone();
1434                let task = cx.spawn(move |mut cx| async move {
1435                    let (worktree, project_path) = project_path?;
1436                    if fs.is_file(&abs_path).await {
1437                        Some(
1438                            this.update(&mut cx, |this, cx| {
1439                                this.open_path(project_path, pane, true, cx)
1440                            })
1441                            .log_err()?
1442                            .await,
1443                        )
1444                    } else {
1445                        this.update(&mut cx, |workspace, cx| {
1446                            let worktree = worktree.read(cx);
1447                            let worktree_abs_path = worktree.abs_path();
1448                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1449                                worktree.root_entry()
1450                            } else {
1451                                abs_path
1452                                    .strip_prefix(worktree_abs_path.as_ref())
1453                                    .ok()
1454                                    .and_then(|relative_path| {
1455                                        worktree.entry_for_path(relative_path)
1456                                    })
1457                            }
1458                            .map(|entry| entry.id);
1459                            if let Some(entry_id) = entry_id {
1460                                workspace.project.update(cx, |_, cx| {
1461                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1462                                })
1463                            }
1464                        })
1465                        .log_err()?;
1466                        None
1467                    }
1468                });
1469                tasks.push(task);
1470            }
1471
1472            futures::future::join_all(tasks).await
1473        })
1474    }
1475
1476    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1477        let paths = cx.prompt_for_paths(PathPromptOptions {
1478            files: false,
1479            directories: true,
1480            multiple: true,
1481        });
1482        cx.spawn(|this, mut cx| async move {
1483            if let Some(paths) = paths.await.log_err().flatten() {
1484                let results = this
1485                    .update(&mut cx, |this, cx| {
1486                        this.open_paths(paths, OpenVisible::All, None, cx)
1487                    })?
1488                    .await;
1489                for result in results.into_iter().flatten() {
1490                    result.log_err();
1491                }
1492            }
1493            anyhow::Ok(())
1494        })
1495        .detach_and_log_err(cx);
1496    }
1497
1498    fn project_path_for_path(
1499        project: Model<Project>,
1500        abs_path: &Path,
1501        visible: bool,
1502        cx: &mut AppContext,
1503    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1504        let entry = project.update(cx, |project, cx| {
1505            project.find_or_create_local_worktree(abs_path, visible, cx)
1506        });
1507        cx.spawn(|mut cx| async move {
1508            let (worktree, path) = entry.await?;
1509            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1510            Ok((
1511                worktree,
1512                ProjectPath {
1513                    worktree_id,
1514                    path: path.into(),
1515                },
1516            ))
1517        })
1518    }
1519
1520    pub fn items<'a>(
1521        &'a self,
1522        cx: &'a AppContext,
1523    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1524        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1525    }
1526
1527    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1528        self.items_of_type(cx).max_by_key(|item| item.item_id())
1529    }
1530
1531    pub fn items_of_type<'a, T: Item>(
1532        &'a self,
1533        cx: &'a AppContext,
1534    ) -> impl 'a + Iterator<Item = View<T>> {
1535        self.panes
1536            .iter()
1537            .flat_map(|pane| pane.read(cx).items_of_type())
1538    }
1539
1540    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1541        self.active_pane().read(cx).active_item()
1542    }
1543
1544    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1545        let item = self.active_item(cx)?;
1546        item.to_any().downcast::<I>().ok()
1547    }
1548
1549    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1550        self.active_item(cx).and_then(|item| item.project_path(cx))
1551    }
1552
1553    pub fn save_active_item(
1554        &mut self,
1555        save_intent: SaveIntent,
1556        cx: &mut WindowContext,
1557    ) -> Task<Result<()>> {
1558        let project = self.project.clone();
1559        let pane = self.active_pane();
1560        let item_ix = pane.read(cx).active_item_index();
1561        let item = pane.read(cx).active_item();
1562        let pane = pane.downgrade();
1563
1564        cx.spawn(|mut cx| async move {
1565            if let Some(item) = item {
1566                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1567                    .await
1568                    .map(|_| ())
1569            } else {
1570                Ok(())
1571            }
1572        })
1573    }
1574
1575    pub fn close_inactive_items_and_panes(
1576        &mut self,
1577        _: &CloseInactiveTabsAndPanes,
1578        cx: &mut ViewContext<Self>,
1579    ) {
1580        self.close_all_internal(true, SaveIntent::Close, cx)
1581            .map(|task| task.detach_and_log_err(cx));
1582    }
1583
1584    pub fn close_all_items_and_panes(
1585        &mut self,
1586        action: &CloseAllItemsAndPanes,
1587        cx: &mut ViewContext<Self>,
1588    ) {
1589        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1590            .map(|task| task.detach_and_log_err(cx));
1591    }
1592
1593    fn close_all_internal(
1594        &mut self,
1595        retain_active_pane: bool,
1596        save_intent: SaveIntent,
1597        cx: &mut ViewContext<Self>,
1598    ) -> Option<Task<Result<()>>> {
1599        let current_pane = self.active_pane();
1600
1601        let mut tasks = Vec::new();
1602
1603        if retain_active_pane {
1604            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1605                pane.close_inactive_items(&CloseInactiveItems, cx)
1606            }) {
1607                tasks.push(current_pane_close);
1608            };
1609        }
1610
1611        for pane in self.panes() {
1612            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1613                continue;
1614            }
1615
1616            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1617                pane.close_all_items(
1618                    &CloseAllItems {
1619                        save_intent: Some(save_intent),
1620                    },
1621                    cx,
1622                )
1623            }) {
1624                tasks.push(close_pane_items)
1625            }
1626        }
1627
1628        if tasks.is_empty() {
1629            None
1630        } else {
1631            Some(cx.spawn(|_, _| async move {
1632                for task in tasks {
1633                    task.await?
1634                }
1635                Ok(())
1636            }))
1637        }
1638    }
1639
1640    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1641        let dock = match dock_side {
1642            DockPosition::Left => &self.left_dock,
1643            DockPosition::Bottom => &self.bottom_dock,
1644            DockPosition::Right => &self.right_dock,
1645        };
1646        let mut focus_center = false;
1647        let mut reveal_dock = false;
1648        dock.update(cx, |dock, cx| {
1649            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1650            let was_visible = dock.is_open() && !other_is_zoomed;
1651            dock.set_open(!was_visible, cx);
1652
1653            if let Some(active_panel) = dock.active_panel() {
1654                if was_visible {
1655                    if active_panel.focus_handle(cx).contains_focused(cx) {
1656                        focus_center = true;
1657                    }
1658                } else {
1659                    let focus_handle = &active_panel.focus_handle(cx);
1660                    cx.focus(focus_handle);
1661                    reveal_dock = true;
1662                }
1663            }
1664        });
1665
1666        if reveal_dock {
1667            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1668        }
1669
1670        if focus_center {
1671            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1672        }
1673
1674        cx.notify();
1675        self.serialize_workspace(cx);
1676    }
1677
1678    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1679        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1680
1681        for dock in docks {
1682            dock.update(cx, |dock, cx| {
1683                dock.set_open(false, cx);
1684            });
1685        }
1686
1687        cx.focus_self();
1688        cx.notify();
1689        self.serialize_workspace(cx);
1690    }
1691
1692    /// Transfer focus to the panel of the given type.
1693    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1694        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1695        panel.to_any().downcast().ok()
1696    }
1697
1698    /// Focus the panel of the given type if it isn't already focused. If it is
1699    /// already focused, then transfer focus back to the workspace center.
1700    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1701        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1702            !panel.focus_handle(cx).contains_focused(cx)
1703        });
1704    }
1705
1706    /// Focus or unfocus the given panel type, depending on the given callback.
1707    fn focus_or_unfocus_panel<T: Panel>(
1708        &mut self,
1709        cx: &mut ViewContext<Self>,
1710        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1711    ) -> Option<Arc<dyn PanelHandle>> {
1712        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1713            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1714                let mut focus_center = false;
1715                let panel = dock.update(cx, |dock, cx| {
1716                    dock.activate_panel(panel_index, cx);
1717
1718                    let panel = dock.active_panel().cloned();
1719                    if let Some(panel) = panel.as_ref() {
1720                        if should_focus(&**panel, cx) {
1721                            dock.set_open(true, cx);
1722                            panel.focus_handle(cx).focus(cx);
1723                        } else {
1724                            focus_center = true;
1725                        }
1726                    }
1727                    panel
1728                });
1729
1730                if focus_center {
1731                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1732                }
1733
1734                self.serialize_workspace(cx);
1735                cx.notify();
1736                return panel;
1737            }
1738        }
1739        None
1740    }
1741
1742    /// Open the panel of the given type
1743    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1744        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1745            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1746                dock.update(cx, |dock, cx| {
1747                    dock.activate_panel(panel_index, cx);
1748                    dock.set_open(true, cx);
1749                });
1750            }
1751        }
1752    }
1753
1754    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1755        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1756            let dock = dock.read(cx);
1757            if let Some(panel) = dock.panel::<T>() {
1758                return Some(panel);
1759            }
1760        }
1761        None
1762    }
1763
1764    fn dismiss_zoomed_items_to_reveal(
1765        &mut self,
1766        dock_to_reveal: Option<DockPosition>,
1767        cx: &mut ViewContext<Self>,
1768    ) {
1769        // If a center pane is zoomed, unzoom it.
1770        for pane in &self.panes {
1771            if pane != &self.active_pane || dock_to_reveal.is_some() {
1772                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1773            }
1774        }
1775
1776        // If another dock is zoomed, hide it.
1777        let mut focus_center = false;
1778        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1779            dock.update(cx, |dock, cx| {
1780                if Some(dock.position()) != dock_to_reveal {
1781                    if let Some(panel) = dock.active_panel() {
1782                        if panel.is_zoomed(cx) {
1783                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
1784                            dock.set_open(false, cx);
1785                        }
1786                    }
1787                }
1788            });
1789        }
1790
1791        if focus_center {
1792            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1793        }
1794
1795        if self.zoomed_position != dock_to_reveal {
1796            self.zoomed = None;
1797            self.zoomed_position = None;
1798        }
1799
1800        cx.notify();
1801    }
1802
1803    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1804        let pane = cx.new_view(|cx| {
1805            Pane::new(
1806                self.weak_handle(),
1807                self.project.clone(),
1808                self.pane_history_timestamp.clone(),
1809                None,
1810                cx,
1811            )
1812        });
1813        cx.subscribe(&pane, Self::handle_pane_event).detach();
1814        self.panes.push(pane.clone());
1815        cx.focus_view(&pane);
1816        cx.emit(Event::PaneAdded(pane.clone()));
1817        pane
1818    }
1819
1820    pub fn add_item_to_center(
1821        &mut self,
1822        item: Box<dyn ItemHandle>,
1823        cx: &mut ViewContext<Self>,
1824    ) -> bool {
1825        if let Some(center_pane) = self.last_active_center_pane.clone() {
1826            if let Some(center_pane) = center_pane.upgrade() {
1827                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1828                true
1829            } else {
1830                false
1831            }
1832        } else {
1833            false
1834        }
1835    }
1836
1837    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
1838        if let Some(text) = item.telemetry_event_text(cx) {
1839            self.client()
1840                .telemetry()
1841                .report_app_event(format!("{}: open", text));
1842        }
1843
1844        self.active_pane
1845            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1846    }
1847
1848    pub fn split_item(
1849        &mut self,
1850        split_direction: SplitDirection,
1851        item: Box<dyn ItemHandle>,
1852        cx: &mut ViewContext<Self>,
1853    ) {
1854        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1855        new_pane.update(cx, move |new_pane, cx| {
1856            new_pane.add_item(item, true, true, None, cx)
1857        })
1858    }
1859
1860    pub fn open_abs_path(
1861        &mut self,
1862        abs_path: PathBuf,
1863        visible: bool,
1864        cx: &mut ViewContext<Self>,
1865    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1866        cx.spawn(|workspace, mut cx| async move {
1867            let open_paths_task_result = workspace
1868                .update(&mut cx, |workspace, cx| {
1869                    workspace.open_paths(
1870                        vec![abs_path.clone()],
1871                        if visible {
1872                            OpenVisible::All
1873                        } else {
1874                            OpenVisible::None
1875                        },
1876                        None,
1877                        cx,
1878                    )
1879                })
1880                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1881                .await;
1882            anyhow::ensure!(
1883                open_paths_task_result.len() == 1,
1884                "open abs path {abs_path:?} task returned incorrect number of results"
1885            );
1886            match open_paths_task_result
1887                .into_iter()
1888                .next()
1889                .expect("ensured single task result")
1890            {
1891                Some(open_result) => {
1892                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1893                }
1894                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1895            }
1896        })
1897    }
1898
1899    pub fn split_abs_path(
1900        &mut self,
1901        abs_path: PathBuf,
1902        visible: bool,
1903        cx: &mut ViewContext<Self>,
1904    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1905        let project_path_task =
1906            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1907        cx.spawn(|this, mut cx| async move {
1908            let (_, path) = project_path_task.await?;
1909            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1910                .await
1911        })
1912    }
1913
1914    pub fn open_path(
1915        &mut self,
1916        path: impl Into<ProjectPath>,
1917        pane: Option<WeakView<Pane>>,
1918        focus_item: bool,
1919        cx: &mut WindowContext,
1920    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1921        let pane = pane.unwrap_or_else(|| {
1922            self.last_active_center_pane.clone().unwrap_or_else(|| {
1923                self.panes
1924                    .first()
1925                    .expect("There must be an active pane")
1926                    .downgrade()
1927            })
1928        });
1929
1930        let task = self.load_path(path.into(), cx);
1931        cx.spawn(move |mut cx| async move {
1932            let (project_entry_id, build_item) = task.await?;
1933            pane.update(&mut cx, |pane, cx| {
1934                pane.open_item(project_entry_id, focus_item, cx, build_item)
1935            })
1936        })
1937    }
1938
1939    pub fn split_path(
1940        &mut self,
1941        path: impl Into<ProjectPath>,
1942        cx: &mut ViewContext<Self>,
1943    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1944        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1945            self.panes
1946                .first()
1947                .expect("There must be an active pane")
1948                .downgrade()
1949        });
1950
1951        if let Member::Pane(center_pane) = &self.center.root {
1952            if center_pane.read(cx).items_len() == 0 {
1953                return self.open_path(path, Some(pane), true, cx);
1954            }
1955        }
1956
1957        let task = self.load_path(path.into(), cx);
1958        cx.spawn(|this, mut cx| async move {
1959            let (project_entry_id, build_item) = task.await?;
1960            this.update(&mut cx, move |this, cx| -> Option<_> {
1961                let pane = pane.upgrade()?;
1962                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1963                new_pane.update(cx, |new_pane, cx| {
1964                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1965                })
1966            })
1967            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1968        })
1969    }
1970
1971    fn load_path(
1972        &mut self,
1973        path: ProjectPath,
1974        cx: &mut WindowContext,
1975    ) -> Task<
1976        Result<(
1977            Option<ProjectEntryId>,
1978            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1979        )>,
1980    > {
1981        let project = self.project().clone();
1982        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1983        cx.spawn(|mut cx| async move {
1984            let (project_entry_id, project_item) = project_item.await?;
1985            let build_item = cx.update(|cx| {
1986                cx.default_global::<ProjectItemBuilders>()
1987                    .get(&project_item.entity_type())
1988                    .ok_or_else(|| anyhow!("no item builder for project item"))
1989                    .cloned()
1990            })??;
1991            let build_item =
1992                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1993            Ok((project_entry_id, build_item))
1994        })
1995    }
1996
1997    pub fn open_project_item<T>(
1998        &mut self,
1999        project_item: Model<T::Item>,
2000        cx: &mut ViewContext<Self>,
2001    ) -> View<T>
2002    where
2003        T: ProjectItem,
2004    {
2005        use project::Item as _;
2006
2007        let entry_id = project_item.read(cx).entry_id(cx);
2008        if let Some(item) = entry_id
2009            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2010            .and_then(|item| item.downcast())
2011        {
2012            self.activate_item(&item, cx);
2013            return item;
2014        }
2015
2016        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2017        self.add_item(Box::new(item.clone()), cx);
2018        item
2019    }
2020
2021    pub fn split_project_item<T>(
2022        &mut self,
2023        project_item: Model<T::Item>,
2024        cx: &mut ViewContext<Self>,
2025    ) -> View<T>
2026    where
2027        T: ProjectItem,
2028    {
2029        use project::Item as _;
2030
2031        let entry_id = project_item.read(cx).entry_id(cx);
2032        if let Some(item) = entry_id
2033            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2034            .and_then(|item| item.downcast())
2035        {
2036            self.activate_item(&item, cx);
2037            return item;
2038        }
2039
2040        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2041        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2042        item
2043    }
2044
2045    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2046        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2047            self.active_pane.update(cx, |pane, cx| {
2048                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2049            });
2050        }
2051    }
2052
2053    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2054        let result = self.panes.iter().find_map(|pane| {
2055            pane.read(cx)
2056                .index_for_item(item)
2057                .map(|ix| (pane.clone(), ix))
2058        });
2059        if let Some((pane, ix)) = result {
2060            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2061            true
2062        } else {
2063            false
2064        }
2065    }
2066
2067    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2068        let panes = self.center.panes();
2069        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2070            cx.focus_view(&pane);
2071        } else {
2072            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2073        }
2074    }
2075
2076    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2077        let panes = self.center.panes();
2078        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2079            let next_ix = (ix + 1) % panes.len();
2080            let next_pane = panes[next_ix].clone();
2081            cx.focus_view(&next_pane);
2082        }
2083    }
2084
2085    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2086        let panes = self.center.panes();
2087        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2088            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2089            let prev_pane = panes[prev_ix].clone();
2090            cx.focus_view(&prev_pane);
2091        }
2092    }
2093
2094    pub fn activate_pane_in_direction(
2095        &mut self,
2096        direction: SplitDirection,
2097        cx: &mut WindowContext,
2098    ) {
2099        use ActivateInDirectionTarget as Target;
2100        enum Origin {
2101            LeftDock,
2102            RightDock,
2103            BottomDock,
2104            Center,
2105        }
2106
2107        let origin: Origin = [
2108            (&self.left_dock, Origin::LeftDock),
2109            (&self.right_dock, Origin::RightDock),
2110            (&self.bottom_dock, Origin::BottomDock),
2111        ]
2112        .into_iter()
2113        .find_map(|(dock, origin)| {
2114            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2115                Some(origin)
2116            } else {
2117                None
2118            }
2119        })
2120        .unwrap_or(Origin::Center);
2121
2122        let get_last_active_pane = || {
2123            self.last_active_center_pane.as_ref().and_then(|p| {
2124                let p = p.upgrade()?;
2125                (p.read(cx).items_len() != 0).then_some(p)
2126            })
2127        };
2128
2129        let try_dock =
2130            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2131
2132        let target = match (origin, direction) {
2133            // We're in the center, so we first try to go to a different pane,
2134            // otherwise try to go to a dock.
2135            (Origin::Center, direction) => {
2136                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2137                    Some(Target::Pane(pane))
2138                } else {
2139                    match direction {
2140                        SplitDirection::Up => None,
2141                        SplitDirection::Down => try_dock(&self.bottom_dock),
2142                        SplitDirection::Left => try_dock(&self.left_dock),
2143                        SplitDirection::Right => try_dock(&self.right_dock),
2144                    }
2145                }
2146            }
2147
2148            (Origin::LeftDock, SplitDirection::Right) => {
2149                if let Some(last_active_pane) = get_last_active_pane() {
2150                    Some(Target::Pane(last_active_pane))
2151                } else {
2152                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2153                }
2154            }
2155
2156            (Origin::LeftDock, SplitDirection::Down)
2157            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2158
2159            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2160            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2161            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2162
2163            (Origin::RightDock, SplitDirection::Left) => {
2164                if let Some(last_active_pane) = get_last_active_pane() {
2165                    Some(Target::Pane(last_active_pane))
2166                } else {
2167                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2168                }
2169            }
2170
2171            _ => None,
2172        };
2173
2174        match target {
2175            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2176            Some(ActivateInDirectionTarget::Dock(dock)) => {
2177                if let Some(panel) = dock.read(cx).active_panel() {
2178                    panel.focus_handle(cx).focus(cx);
2179                } else {
2180                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2181                }
2182            }
2183            None => {}
2184        }
2185    }
2186
2187    fn find_pane_in_direction(
2188        &mut self,
2189        direction: SplitDirection,
2190        cx: &WindowContext,
2191    ) -> Option<View<Pane>> {
2192        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2193            return None;
2194        };
2195        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2196        let center = match cursor {
2197            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2198            _ => bounding_box.center(),
2199        };
2200
2201        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2202
2203        let target = match direction {
2204            SplitDirection::Left => {
2205                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2206            }
2207            SplitDirection::Right => {
2208                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2209            }
2210            SplitDirection::Up => {
2211                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2212            }
2213            SplitDirection::Down => {
2214                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2215            }
2216        };
2217        self.center.pane_at_pixel_position(target).cloned()
2218    }
2219
2220    pub fn swap_pane_in_direction(
2221        &mut self,
2222        direction: SplitDirection,
2223        cx: &mut ViewContext<Self>,
2224    ) {
2225        if let Some(to) = self
2226            .find_pane_in_direction(direction, cx)
2227            .map(|pane| pane.clone())
2228        {
2229            self.center.swap(&self.active_pane.clone(), &to);
2230            cx.notify();
2231        }
2232    }
2233
2234    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2235        if self.active_pane != pane {
2236            self.active_pane = pane.clone();
2237            self.status_bar.update(cx, |status_bar, cx| {
2238                status_bar.set_active_pane(&self.active_pane, cx);
2239            });
2240            self.active_item_path_changed(cx);
2241            self.last_active_center_pane = Some(pane.downgrade());
2242        }
2243
2244        self.dismiss_zoomed_items_to_reveal(None, cx);
2245        if pane.read(cx).is_zoomed() {
2246            self.zoomed = Some(pane.downgrade().into());
2247        } else {
2248            self.zoomed = None;
2249        }
2250        self.zoomed_position = None;
2251        self.update_active_view_for_followers(cx);
2252
2253        cx.notify();
2254    }
2255
2256    fn handle_pane_event(
2257        &mut self,
2258        pane: View<Pane>,
2259        event: &pane::Event,
2260        cx: &mut ViewContext<Self>,
2261    ) {
2262        match event {
2263            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2264            pane::Event::Split(direction) => {
2265                self.split_and_clone(pane, *direction, cx);
2266            }
2267            pane::Event::Remove => self.remove_pane(pane, cx),
2268            pane::Event::ActivateItem { local } => {
2269                if *local {
2270                    self.unfollow(&pane, cx);
2271                }
2272                if &pane == self.active_pane() {
2273                    self.active_item_path_changed(cx);
2274                    self.update_active_view_for_followers(cx);
2275                }
2276            }
2277            pane::Event::ChangeItemTitle => {
2278                if pane == self.active_pane {
2279                    self.active_item_path_changed(cx);
2280                }
2281                self.update_window_edited(cx);
2282            }
2283            pane::Event::RemoveItem { item_id } => {
2284                self.update_window_edited(cx);
2285                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2286                    if entry.get().entity_id() == pane.entity_id() {
2287                        entry.remove();
2288                    }
2289                }
2290            }
2291            pane::Event::Focus => {
2292                self.handle_pane_focused(pane.clone(), cx);
2293            }
2294            pane::Event::ZoomIn => {
2295                if pane == self.active_pane {
2296                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2297                    if pane.read(cx).has_focus(cx) {
2298                        self.zoomed = Some(pane.downgrade().into());
2299                        self.zoomed_position = None;
2300                    }
2301                    cx.notify();
2302                }
2303            }
2304            pane::Event::ZoomOut => {
2305                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2306                if self.zoomed_position.is_none() {
2307                    self.zoomed = None;
2308                }
2309                cx.notify();
2310            }
2311        }
2312
2313        self.serialize_workspace(cx);
2314    }
2315
2316    pub fn split_pane(
2317        &mut self,
2318        pane_to_split: View<Pane>,
2319        split_direction: SplitDirection,
2320        cx: &mut ViewContext<Self>,
2321    ) -> View<Pane> {
2322        let new_pane = self.add_pane(cx);
2323        self.center
2324            .split(&pane_to_split, &new_pane, split_direction)
2325            .unwrap();
2326        cx.notify();
2327        new_pane
2328    }
2329
2330    pub fn split_and_clone(
2331        &mut self,
2332        pane: View<Pane>,
2333        direction: SplitDirection,
2334        cx: &mut ViewContext<Self>,
2335    ) -> Option<View<Pane>> {
2336        let item = pane.read(cx).active_item()?;
2337        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2338            let new_pane = self.add_pane(cx);
2339            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2340            self.center.split(&pane, &new_pane, direction).unwrap();
2341            Some(new_pane)
2342        } else {
2343            None
2344        };
2345        cx.notify();
2346        maybe_pane_handle
2347    }
2348
2349    pub fn split_pane_with_item(
2350        &mut self,
2351        pane_to_split: WeakView<Pane>,
2352        split_direction: SplitDirection,
2353        from: WeakView<Pane>,
2354        item_id_to_move: EntityId,
2355        cx: &mut ViewContext<Self>,
2356    ) {
2357        let Some(pane_to_split) = pane_to_split.upgrade() else {
2358            return;
2359        };
2360        let Some(from) = from.upgrade() else {
2361            return;
2362        };
2363
2364        let new_pane = self.add_pane(cx);
2365        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2366        self.center
2367            .split(&pane_to_split, &new_pane, split_direction)
2368            .unwrap();
2369        cx.notify();
2370    }
2371
2372    pub fn split_pane_with_project_entry(
2373        &mut self,
2374        pane_to_split: WeakView<Pane>,
2375        split_direction: SplitDirection,
2376        project_entry: ProjectEntryId,
2377        cx: &mut ViewContext<Self>,
2378    ) -> Option<Task<Result<()>>> {
2379        let pane_to_split = pane_to_split.upgrade()?;
2380        let new_pane = self.add_pane(cx);
2381        self.center
2382            .split(&pane_to_split, &new_pane, split_direction)
2383            .unwrap();
2384
2385        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2386        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2387        Some(cx.foreground_executor().spawn(async move {
2388            task.await?;
2389            Ok(())
2390        }))
2391    }
2392
2393    pub fn move_item(
2394        &mut self,
2395        source: View<Pane>,
2396        destination: View<Pane>,
2397        item_id_to_move: EntityId,
2398        destination_index: usize,
2399        cx: &mut ViewContext<Self>,
2400    ) {
2401        let Some((item_ix, item_handle)) = source
2402            .read(cx)
2403            .items()
2404            .enumerate()
2405            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2406        else {
2407            // Tab was closed during drag
2408            return;
2409        };
2410
2411        let item_handle = item_handle.clone();
2412
2413        if source != destination {
2414            // Close item from previous pane
2415            source.update(cx, |source, cx| {
2416                source.remove_item(item_ix, false, cx);
2417            });
2418        }
2419
2420        // This automatically removes duplicate items in the pane
2421        destination.update(cx, |destination, cx| {
2422            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2423            destination.focus(cx)
2424        });
2425    }
2426
2427    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2428        if self.center.remove(&pane).unwrap() {
2429            self.force_remove_pane(&pane, cx);
2430            self.unfollow(&pane, cx);
2431            self.last_leaders_by_pane.remove(&pane.downgrade());
2432            for removed_item in pane.read(cx).items() {
2433                self.panes_by_item.remove(&removed_item.item_id());
2434            }
2435
2436            cx.notify();
2437        } else {
2438            self.active_item_path_changed(cx);
2439        }
2440    }
2441
2442    pub fn panes(&self) -> &[View<Pane>] {
2443        &self.panes
2444    }
2445
2446    pub fn active_pane(&self) -> &View<Pane> {
2447        &self.active_pane
2448    }
2449
2450    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2451        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2452        weak_pane.upgrade()
2453    }
2454
2455    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2456        self.follower_states.retain(|_, state| {
2457            if state.leader_id == peer_id {
2458                for item in state.items_by_leader_view_id.values() {
2459                    item.set_leader_peer_id(None, cx);
2460                }
2461                false
2462            } else {
2463                true
2464            }
2465        });
2466        cx.notify();
2467    }
2468
2469    pub fn start_following(
2470        &mut self,
2471        leader_id: PeerId,
2472        cx: &mut ViewContext<Self>,
2473    ) -> Option<Task<Result<()>>> {
2474        let pane = self.active_pane().clone();
2475
2476        self.last_leaders_by_pane
2477            .insert(pane.downgrade(), leader_id);
2478        self.unfollow(&pane, cx);
2479        self.follower_states.insert(
2480            pane.clone(),
2481            FollowerState {
2482                leader_id,
2483                active_view_id: None,
2484                items_by_leader_view_id: Default::default(),
2485            },
2486        );
2487        cx.notify();
2488
2489        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2490        let project_id = self.project.read(cx).remote_id();
2491        let request = self.app_state.client.request(proto::Follow {
2492            room_id,
2493            project_id,
2494            leader_id: Some(leader_id),
2495        });
2496
2497        Some(cx.spawn(|this, mut cx| async move {
2498            let response = request.await?;
2499            this.update(&mut cx, |this, _| {
2500                let state = this
2501                    .follower_states
2502                    .get_mut(&pane)
2503                    .ok_or_else(|| anyhow!("following interrupted"))?;
2504                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2505                    Some(ViewId::from_proto(active_view_id)?)
2506                } else {
2507                    None
2508                };
2509                Ok::<_, anyhow::Error>(())
2510            })??;
2511            if let Some(view) = response.active_view {
2512                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2513                    .await?;
2514            }
2515            Self::add_views_from_leader(
2516                this.clone(),
2517                leader_id,
2518                vec![pane],
2519                response.views,
2520                &mut cx,
2521            )
2522            .await?;
2523            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2524            Ok(())
2525        }))
2526    }
2527
2528    pub fn follow_next_collaborator(
2529        &mut self,
2530        _: &FollowNextCollaborator,
2531        cx: &mut ViewContext<Self>,
2532    ) {
2533        let collaborators = self.project.read(cx).collaborators();
2534        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2535            let mut collaborators = collaborators.keys().copied();
2536            for peer_id in collaborators.by_ref() {
2537                if peer_id == leader_id {
2538                    break;
2539                }
2540            }
2541            collaborators.next()
2542        } else if let Some(last_leader_id) =
2543            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2544        {
2545            if collaborators.contains_key(last_leader_id) {
2546                Some(*last_leader_id)
2547            } else {
2548                None
2549            }
2550        } else {
2551            None
2552        };
2553
2554        let pane = self.active_pane.clone();
2555        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2556        else {
2557            return;
2558        };
2559        if Some(leader_id) == self.unfollow(&pane, cx) {
2560            return;
2561        }
2562        self.start_following(leader_id, cx)
2563            .map(|task| task.detach_and_log_err(cx));
2564    }
2565
2566    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2567        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2568            return;
2569        };
2570        let room = room.read(cx);
2571        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2572            return;
2573        };
2574
2575        let project = self.project.read(cx);
2576
2577        let other_project_id = match remote_participant.location {
2578            call::ParticipantLocation::External => None,
2579            call::ParticipantLocation::UnsharedProject => None,
2580            call::ParticipantLocation::SharedProject { project_id } => {
2581                if Some(project_id) == project.remote_id() {
2582                    None
2583                } else {
2584                    Some(project_id)
2585                }
2586            }
2587        };
2588
2589        // if they are active in another project, follow there.
2590        if let Some(project_id) = other_project_id {
2591            let app_state = self.app_state.clone();
2592            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2593                .detach_and_log_err(cx);
2594        }
2595
2596        // if you're already following, find the right pane and focus it.
2597        for (pane, state) in &self.follower_states {
2598            if leader_id == state.leader_id {
2599                cx.focus_view(pane);
2600                return;
2601            }
2602        }
2603
2604        // Otherwise, follow.
2605        self.start_following(leader_id, cx)
2606            .map(|task| task.detach_and_log_err(cx));
2607    }
2608
2609    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2610        let state = self.follower_states.remove(pane)?;
2611        let leader_id = state.leader_id;
2612        for (_, item) in state.items_by_leader_view_id {
2613            item.set_leader_peer_id(None, cx);
2614        }
2615
2616        if self
2617            .follower_states
2618            .values()
2619            .all(|state| state.leader_id != state.leader_id)
2620        {
2621            let project_id = self.project.read(cx).remote_id();
2622            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2623            self.app_state
2624                .client
2625                .send(proto::Unfollow {
2626                    room_id,
2627                    project_id,
2628                    leader_id: Some(leader_id),
2629                })
2630                .log_err();
2631        }
2632
2633        cx.notify();
2634        Some(leader_id)
2635    }
2636
2637    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2638        self.follower_states
2639            .values()
2640            .any(|state| state.leader_id == peer_id)
2641    }
2642
2643    fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
2644        let active_entry = self.active_project_path(cx);
2645        self.project
2646            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2647        self.update_window_title(cx);
2648    }
2649
2650    fn update_window_title(&mut self, cx: &mut WindowContext) {
2651        let project = self.project().read(cx);
2652        let mut title = String::new();
2653
2654        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2655            let filename = path
2656                .path
2657                .file_name()
2658                .map(|s| s.to_string_lossy())
2659                .or_else(|| {
2660                    Some(Cow::Borrowed(
2661                        project
2662                            .worktree_for_id(path.worktree_id, cx)?
2663                            .read(cx)
2664                            .root_name(),
2665                    ))
2666                });
2667
2668            if let Some(filename) = filename {
2669                title.push_str(filename.as_ref());
2670                title.push_str("");
2671            }
2672        }
2673
2674        for (i, name) in project.worktree_root_names(cx).enumerate() {
2675            if i > 0 {
2676                title.push_str(", ");
2677            }
2678            title.push_str(name);
2679        }
2680
2681        if title.is_empty() {
2682            title = "empty project".to_string();
2683        }
2684
2685        if project.is_remote() {
2686            title.push_str("");
2687        } else if project.is_shared() {
2688            title.push_str("");
2689        }
2690
2691        cx.set_window_title(&title);
2692    }
2693
2694    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2695        let is_edited = !self.project.read(cx).is_disconnected()
2696            && self
2697                .items(cx)
2698                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2699        if is_edited != self.window_edited {
2700            self.window_edited = is_edited;
2701            cx.set_window_edited(self.window_edited)
2702        }
2703    }
2704
2705    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2706        if self.notifications.is_empty() {
2707            None
2708        } else {
2709            Some(
2710                div()
2711                    .absolute()
2712                    .z_index(100)
2713                    .right_3()
2714                    .bottom_3()
2715                    .w_96()
2716                    .h_full()
2717                    .flex()
2718                    .flex_col()
2719                    .justify_end()
2720                    .gap_2()
2721                    .children(
2722                        self.notifications
2723                            .iter()
2724                            .map(|(_, _, notification)| notification.to_any()),
2725                    ),
2726            )
2727        }
2728    }
2729
2730    // RPC handlers
2731
2732    fn active_view_for_follower(&self, cx: &mut ViewContext<Self>) -> Option<proto::View> {
2733        let item = self.active_item(cx)?;
2734        let leader_id = self
2735            .pane_for(&*item)
2736            .and_then(|pane| self.leader_for_pane(&pane));
2737
2738        let item_handle = item.to_followable_item_handle(cx)?;
2739        let id = item_handle.remote_id(&self.app_state.client, cx)?;
2740        let variant = item_handle.to_state_proto(cx)?;
2741
2742        Some(proto::View {
2743            id: Some(id.to_proto()),
2744            leader_id,
2745            variant: Some(variant),
2746        })
2747    }
2748
2749    fn handle_follow(
2750        &mut self,
2751        follower_project_id: Option<u64>,
2752        cx: &mut ViewContext<Self>,
2753    ) -> proto::FollowResponse {
2754        let client = &self.app_state.client;
2755        let project_id = self.project.read(cx).remote_id();
2756
2757        let active_view = self.active_view_for_follower(cx);
2758        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
2759
2760        cx.notify();
2761
2762        proto::FollowResponse {
2763            active_view,
2764            // TODO: once v0.124.0 is retired we can stop sending these
2765            active_view_id,
2766            views: self
2767                .panes()
2768                .iter()
2769                .flat_map(|pane| {
2770                    let leader_id = self.leader_for_pane(pane);
2771                    pane.read(cx).items().filter_map({
2772                        let cx = &cx;
2773                        move |item| {
2774                            let item = item.to_followable_item_handle(cx)?;
2775
2776                            // If the item belongs to a particular project, then it should
2777                            // only be included if this project is shared, and the follower
2778                            // is in the project.
2779                            //
2780                            // Some items, like channel notes, do not belong to a particular
2781                            // project, so they should be included regardless of whether the
2782                            // current project is shared, or what project the follower is in.
2783                            if item.is_project_item(cx)
2784                                && (project_id.is_none() || project_id != follower_project_id)
2785                            {
2786                                return None;
2787                            }
2788
2789                            let id = item.remote_id(client, cx)?.to_proto();
2790                            let variant = item.to_state_proto(cx)?;
2791                            Some(proto::View {
2792                                id: Some(id),
2793                                leader_id,
2794                                variant: Some(variant),
2795                            })
2796                        }
2797                    })
2798                })
2799                .collect(),
2800        }
2801    }
2802
2803    fn handle_update_followers(
2804        &mut self,
2805        leader_id: PeerId,
2806        message: proto::UpdateFollowers,
2807        _cx: &mut ViewContext<Self>,
2808    ) {
2809        self.leader_updates_tx
2810            .unbounded_send((leader_id, message))
2811            .ok();
2812    }
2813
2814    async fn process_leader_update(
2815        this: &WeakView<Self>,
2816        leader_id: PeerId,
2817        update: proto::UpdateFollowers,
2818        cx: &mut AsyncWindowContext,
2819    ) -> Result<()> {
2820        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2821            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2822                let panes_missing_view = this.update(cx, |this, _| {
2823                    let mut panes = vec![];
2824                    for (pane, state) in &mut this.follower_states {
2825                        if state.leader_id != leader_id {
2826                            continue;
2827                        }
2828
2829                        state.active_view_id =
2830                            if let Some(active_view_id) = update_active_view.id.clone() {
2831                                Some(ViewId::from_proto(active_view_id)?)
2832                            } else {
2833                                None
2834                            };
2835
2836                        if state.active_view_id.is_some_and(|view_id| {
2837                            !state.items_by_leader_view_id.contains_key(&view_id)
2838                        }) {
2839                            panes.push(pane.clone())
2840                        }
2841                    }
2842                    anyhow::Ok(panes)
2843                })??;
2844
2845                if let Some(view) = update_active_view.view {
2846                    for pane in panes_missing_view {
2847                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
2848                            .await?
2849                    }
2850                }
2851            }
2852            proto::update_followers::Variant::UpdateView(update_view) => {
2853                let variant = update_view
2854                    .variant
2855                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2856                let id = update_view
2857                    .id
2858                    .ok_or_else(|| anyhow!("missing update view id"))?;
2859                let mut tasks = Vec::new();
2860                this.update(cx, |this, cx| {
2861                    let project = this.project.clone();
2862                    for (_, state) in &mut this.follower_states {
2863                        if state.leader_id == leader_id {
2864                            let view_id = ViewId::from_proto(id.clone())?;
2865                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2866                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2867                            }
2868                        }
2869                    }
2870                    anyhow::Ok(())
2871                })??;
2872                try_join_all(tasks).await.log_err();
2873            }
2874            proto::update_followers::Variant::CreateView(view) => {
2875                let panes = this.update(cx, |this, _| {
2876                    this.follower_states
2877                        .iter()
2878                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2879                        .cloned()
2880                        .collect()
2881                })?;
2882                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2883            }
2884        }
2885        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2886        Ok(())
2887    }
2888
2889    async fn add_view_from_leader(
2890        this: WeakView<Self>,
2891        leader_id: PeerId,
2892        pane: View<Pane>,
2893        view: &proto::View,
2894        cx: &mut AsyncWindowContext,
2895    ) -> Result<()> {
2896        let this = this.upgrade().context("workspace dropped")?;
2897
2898        let item_builders = cx.update(|cx| {
2899            cx.default_global::<FollowableItemBuilders>()
2900                .values()
2901                .map(|b| b.0)
2902                .collect::<Vec<_>>()
2903        })?;
2904
2905        let Some(id) = view.id.clone() else {
2906            return Err(anyhow!("no id for view")).into();
2907        };
2908        let id = ViewId::from_proto(id)?;
2909
2910        let mut variant = view.variant.clone();
2911        if variant.is_none() {
2912            Err(anyhow!("missing view variant"))?;
2913        }
2914
2915        let task = item_builders.iter().find_map(|build_item| {
2916            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
2917                .log_err()
2918                .flatten()
2919        });
2920        let Some(task) = task else {
2921            return Err(anyhow!(
2922                "failed to construct view from leader (maybe from a different version of zed?)"
2923            ));
2924        };
2925
2926        let item = task.await?;
2927
2928        this.update(cx, |this, cx| {
2929            let state = this.follower_states.get_mut(&pane)?;
2930            item.set_leader_peer_id(Some(leader_id), cx);
2931            state.items_by_leader_view_id.insert(id, item);
2932
2933            Some(())
2934        })?;
2935
2936        Ok(())
2937    }
2938
2939    async fn add_views_from_leader(
2940        this: WeakView<Self>,
2941        leader_id: PeerId,
2942        panes: Vec<View<Pane>>,
2943        views: Vec<proto::View>,
2944        cx: &mut AsyncWindowContext,
2945    ) -> Result<()> {
2946        let this = this.upgrade().context("workspace dropped")?;
2947
2948        let item_builders = cx.update(|cx| {
2949            cx.default_global::<FollowableItemBuilders>()
2950                .values()
2951                .map(|b| b.0)
2952                .collect::<Vec<_>>()
2953        })?;
2954
2955        let mut item_tasks_by_pane = HashMap::default();
2956        for pane in panes {
2957            let mut item_tasks = Vec::new();
2958            let mut leader_view_ids = Vec::new();
2959            for view in &views {
2960                let Some(id) = &view.id else { continue };
2961                let id = ViewId::from_proto(id.clone())?;
2962                let mut variant = view.variant.clone();
2963                if variant.is_none() {
2964                    Err(anyhow!("missing view variant"))?;
2965                }
2966                for build_item in &item_builders {
2967                    let task = cx.update(|cx| {
2968                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2969                    })?;
2970                    if let Some(task) = task {
2971                        item_tasks.push(task);
2972                        leader_view_ids.push(id);
2973                        break;
2974                    } else if variant.is_none() {
2975                        Err(anyhow!(
2976                            "failed to construct view from leader (maybe from a different version of zed?)"
2977                        ))?;
2978                    }
2979                }
2980            }
2981
2982            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2983        }
2984
2985        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2986            let items = futures::future::try_join_all(item_tasks).await?;
2987            this.update(cx, |this, cx| {
2988                let state = this.follower_states.get_mut(&pane)?;
2989                for (id, item) in leader_view_ids.into_iter().zip(items) {
2990                    item.set_leader_peer_id(Some(leader_id), cx);
2991                    state.items_by_leader_view_id.insert(id, item);
2992                }
2993
2994                Some(())
2995            })?;
2996        }
2997        Ok(())
2998    }
2999
3000    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3001        let mut is_project_item = true;
3002        let mut update = proto::UpdateActiveView::default();
3003        if cx.is_window_active() {
3004            if let Some(item) = self.active_item(cx) {
3005                if item.focus_handle(cx).contains_focused(cx) {
3006                    let leader_id = self
3007                        .pane_for(&*item)
3008                        .and_then(|pane| self.leader_for_pane(&pane));
3009
3010                    if let Some(item) = item.to_followable_item_handle(cx) {
3011                        let id = item
3012                            .remote_id(&self.app_state.client, cx)
3013                            .map(|id| id.to_proto());
3014
3015                        if let Some(id) = id.clone() {
3016                            if let Some(variant) = item.to_state_proto(cx) {
3017                                let view = Some(proto::View {
3018                                    id: Some(id.clone()),
3019                                    leader_id,
3020                                    variant: Some(variant),
3021                                });
3022
3023                                is_project_item = item.is_project_item(cx);
3024                                update = proto::UpdateActiveView {
3025                                    view,
3026                                    // TODO: once v0.124.0 is retired we can stop sending these
3027                                    id: Some(id),
3028                                    leader_id,
3029                                };
3030                            }
3031                        };
3032                    }
3033                }
3034            }
3035        }
3036
3037        if &update.id != &self.last_active_view_id {
3038            self.last_active_view_id = update.id.clone();
3039            self.update_followers(
3040                is_project_item,
3041                proto::update_followers::Variant::UpdateActiveView(update),
3042                cx,
3043            );
3044        }
3045    }
3046
3047    fn update_followers(
3048        &self,
3049        project_only: bool,
3050        update: proto::update_followers::Variant,
3051        cx: &mut WindowContext,
3052    ) -> Option<()> {
3053        // If this update only applies to for followers in the current project,
3054        // then skip it unless this project is shared. If it applies to all
3055        // followers, regardless of project, then set `project_id` to none,
3056        // indicating that it goes to all followers.
3057        let project_id = if project_only {
3058            Some(self.project.read(cx).remote_id()?)
3059        } else {
3060            None
3061        };
3062        self.app_state().workspace_store.update(cx, |store, cx| {
3063            store.update_followers(project_id, update, cx)
3064        })
3065    }
3066
3067    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3068        self.follower_states.get(pane).map(|state| state.leader_id)
3069    }
3070
3071    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3072        cx.notify();
3073
3074        let call = self.active_call()?;
3075        let room = call.read(cx).room()?.read(cx);
3076        let participant = room.remote_participant_for_peer_id(leader_id)?;
3077        let mut items_to_activate = Vec::new();
3078
3079        let leader_in_this_app;
3080        let leader_in_this_project;
3081        match participant.location {
3082            call::ParticipantLocation::SharedProject { project_id } => {
3083                leader_in_this_app = true;
3084                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3085            }
3086            call::ParticipantLocation::UnsharedProject => {
3087                leader_in_this_app = true;
3088                leader_in_this_project = false;
3089            }
3090            call::ParticipantLocation::External => {
3091                leader_in_this_app = false;
3092                leader_in_this_project = false;
3093            }
3094        };
3095
3096        for (pane, state) in &self.follower_states {
3097            if state.leader_id != leader_id {
3098                continue;
3099            }
3100            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3101                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3102                    if leader_in_this_project || !item.is_project_item(cx) {
3103                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3104                    }
3105                }
3106                continue;
3107            }
3108
3109            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3110                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3111            }
3112        }
3113
3114        for (pane, item) in items_to_activate {
3115            let pane_was_focused = pane.read(cx).has_focus(cx);
3116            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3117                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3118            } else {
3119                pane.update(cx, |pane, cx| {
3120                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3121                });
3122            }
3123
3124            if pane_was_focused {
3125                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3126            }
3127        }
3128
3129        None
3130    }
3131
3132    fn shared_screen_for_peer(
3133        &self,
3134        peer_id: PeerId,
3135        pane: &View<Pane>,
3136        cx: &mut WindowContext,
3137    ) -> Option<View<SharedScreen>> {
3138        let call = self.active_call()?;
3139        let room = call.read(cx).room()?.read(cx);
3140        let participant = room.remote_participant_for_peer_id(peer_id)?;
3141        let track = participant.video_tracks.values().next()?.clone();
3142        let user = participant.user.clone();
3143
3144        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3145            if item.read(cx).peer_id == peer_id {
3146                return Some(item);
3147            }
3148        }
3149
3150        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3151    }
3152
3153    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3154        if cx.is_window_active() {
3155            self.update_active_view_for_followers(cx);
3156            cx.background_executor()
3157                .spawn(persistence::DB.update_timestamp(self.database_id()))
3158                .detach();
3159        } else {
3160            for pane in &self.panes {
3161                pane.update(cx, |pane, cx| {
3162                    if let Some(item) = pane.active_item() {
3163                        item.workspace_deactivated(cx);
3164                    }
3165                    if matches!(
3166                        WorkspaceSettings::get_global(cx).autosave,
3167                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3168                    ) {
3169                        for item in pane.items() {
3170                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3171                                .detach_and_log_err(cx);
3172                        }
3173                    }
3174                });
3175            }
3176        }
3177    }
3178
3179    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3180        self.active_call.as_ref().map(|(call, _)| call)
3181    }
3182
3183    fn on_active_call_event(
3184        &mut self,
3185        _: Model<ActiveCall>,
3186        event: &call::room::Event,
3187        cx: &mut ViewContext<Self>,
3188    ) {
3189        match event {
3190            call::room::Event::ParticipantLocationChanged { participant_id }
3191            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3192                self.leader_updated(*participant_id, cx);
3193            }
3194            _ => {}
3195        }
3196    }
3197
3198    pub fn database_id(&self) -> WorkspaceId {
3199        self.database_id
3200    }
3201
3202    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3203        let project = self.project().read(cx);
3204
3205        if project.is_local() {
3206            Some(
3207                project
3208                    .visible_worktrees(cx)
3209                    .map(|worktree| worktree.read(cx).abs_path())
3210                    .collect::<Vec<_>>()
3211                    .into(),
3212            )
3213        } else {
3214            None
3215        }
3216    }
3217
3218    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3219        match member {
3220            Member::Axis(PaneAxis { members, .. }) => {
3221                for child in members.iter() {
3222                    self.remove_panes(child.clone(), cx)
3223                }
3224            }
3225            Member::Pane(pane) => {
3226                self.force_remove_pane(&pane, cx);
3227            }
3228        }
3229    }
3230
3231    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3232        self.panes.retain(|p| p != pane);
3233        self.panes
3234            .last()
3235            .unwrap()
3236            .update(cx, |pane, cx| pane.focus(cx));
3237        if self.last_active_center_pane == Some(pane.downgrade()) {
3238            self.last_active_center_pane = None;
3239        }
3240        cx.notify();
3241    }
3242
3243    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3244        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3245            cx.background_executor()
3246                .timer(Duration::from_millis(100))
3247                .await;
3248            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3249                .log_err();
3250        }));
3251    }
3252
3253    fn serialize_workspace(&self, cx: &mut WindowContext) {
3254        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3255            let (items, active) = {
3256                let pane = pane_handle.read(cx);
3257                let active_item_id = pane.active_item().map(|item| item.item_id());
3258                (
3259                    pane.items()
3260                        .filter_map(|item_handle| {
3261                            Some(SerializedItem {
3262                                kind: Arc::from(item_handle.serialized_item_kind()?),
3263                                item_id: item_handle.item_id().as_u64(),
3264                                active: Some(item_handle.item_id()) == active_item_id,
3265                            })
3266                        })
3267                        .collect::<Vec<_>>(),
3268                    pane.has_focus(cx),
3269                )
3270            };
3271
3272            SerializedPane::new(items, active)
3273        }
3274
3275        fn build_serialized_pane_group(
3276            pane_group: &Member,
3277            cx: &WindowContext,
3278        ) -> SerializedPaneGroup {
3279            match pane_group {
3280                Member::Axis(PaneAxis {
3281                    axis,
3282                    members,
3283                    flexes,
3284                    bounding_boxes: _,
3285                }) => SerializedPaneGroup::Group {
3286                    axis: SerializedAxis(*axis),
3287                    children: members
3288                        .iter()
3289                        .map(|member| build_serialized_pane_group(member, cx))
3290                        .collect::<Vec<_>>(),
3291                    flexes: Some(flexes.lock().clone()),
3292                },
3293                Member::Pane(pane_handle) => {
3294                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3295                }
3296            }
3297        }
3298
3299        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3300            let left_dock = this.left_dock.read(cx);
3301            let left_visible = left_dock.is_open();
3302            let left_active_panel = left_dock
3303                .visible_panel()
3304                .and_then(|panel| Some(panel.persistent_name().to_string()));
3305            let left_dock_zoom = left_dock
3306                .visible_panel()
3307                .map(|panel| panel.is_zoomed(cx))
3308                .unwrap_or(false);
3309
3310            let right_dock = this.right_dock.read(cx);
3311            let right_visible = right_dock.is_open();
3312            let right_active_panel = right_dock
3313                .visible_panel()
3314                .and_then(|panel| Some(panel.persistent_name().to_string()));
3315            let right_dock_zoom = right_dock
3316                .visible_panel()
3317                .map(|panel| panel.is_zoomed(cx))
3318                .unwrap_or(false);
3319
3320            let bottom_dock = this.bottom_dock.read(cx);
3321            let bottom_visible = bottom_dock.is_open();
3322            let bottom_active_panel = bottom_dock
3323                .visible_panel()
3324                .and_then(|panel| Some(panel.persistent_name().to_string()));
3325            let bottom_dock_zoom = bottom_dock
3326                .visible_panel()
3327                .map(|panel| panel.is_zoomed(cx))
3328                .unwrap_or(false);
3329
3330            DockStructure {
3331                left: DockData {
3332                    visible: left_visible,
3333                    active_panel: left_active_panel,
3334                    zoom: left_dock_zoom,
3335                },
3336                right: DockData {
3337                    visible: right_visible,
3338                    active_panel: right_active_panel,
3339                    zoom: right_dock_zoom,
3340                },
3341                bottom: DockData {
3342                    visible: bottom_visible,
3343                    active_panel: bottom_active_panel,
3344                    zoom: bottom_dock_zoom,
3345                },
3346            }
3347        }
3348
3349        if let Some(location) = self.location(cx) {
3350            // Load bearing special case:
3351            //  - with_local_workspace() relies on this to not have other stuff open
3352            //    when you open your log
3353            if !location.paths().is_empty() {
3354                let center_group = build_serialized_pane_group(&self.center.root, cx);
3355                let docks = build_serialized_docks(self, cx);
3356
3357                let serialized_workspace = SerializedWorkspace {
3358                    id: self.database_id,
3359                    location,
3360                    center_group,
3361                    bounds: Default::default(),
3362                    display: Default::default(),
3363                    docks,
3364                };
3365
3366                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3367                    .detach();
3368            }
3369        }
3370    }
3371
3372    pub(crate) fn load_workspace(
3373        serialized_workspace: SerializedWorkspace,
3374        paths_to_open: Vec<Option<ProjectPath>>,
3375        cx: &mut ViewContext<Workspace>,
3376    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3377        cx.spawn(|workspace, mut cx| async move {
3378            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3379
3380            let mut center_group = None;
3381            let mut center_items = None;
3382
3383            // Traverse the splits tree and add to things
3384            if let Some((group, active_pane, items)) = serialized_workspace
3385                .center_group
3386                .deserialize(
3387                    &project,
3388                    serialized_workspace.id,
3389                    workspace.clone(),
3390                    &mut cx,
3391                )
3392                .await
3393            {
3394                center_items = Some(items);
3395                center_group = Some((group, active_pane))
3396            }
3397
3398            let mut items_by_project_path = cx.update(|cx| {
3399                center_items
3400                    .unwrap_or_default()
3401                    .into_iter()
3402                    .filter_map(|item| {
3403                        let item = item?;
3404                        let project_path = item.project_path(cx)?;
3405                        Some((project_path, item))
3406                    })
3407                    .collect::<HashMap<_, _>>()
3408            })?;
3409
3410            let opened_items = paths_to_open
3411                .into_iter()
3412                .map(|path_to_open| {
3413                    path_to_open
3414                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3415                })
3416                .collect::<Vec<_>>();
3417
3418            // Remove old panes from workspace panes list
3419            workspace.update(&mut cx, |workspace, cx| {
3420                if let Some((center_group, active_pane)) = center_group {
3421                    workspace.remove_panes(workspace.center.root.clone(), cx);
3422
3423                    // Swap workspace center group
3424                    workspace.center = PaneGroup::with_root(center_group);
3425                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3426                    if let Some(active_pane) = active_pane {
3427                        workspace.active_pane = active_pane;
3428                        cx.focus_self();
3429                    } else {
3430                        workspace.active_pane = workspace.center.first_pane().clone();
3431                    }
3432                }
3433
3434                let docks = serialized_workspace.docks;
3435
3436                let right = docks.right.clone();
3437                workspace
3438                    .right_dock
3439                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3440                let left = docks.left.clone();
3441                workspace
3442                    .left_dock
3443                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3444                let bottom = docks.bottom.clone();
3445                workspace
3446                    .bottom_dock
3447                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3448
3449                cx.notify();
3450            })?;
3451
3452            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3453            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3454
3455            Ok(opened_items)
3456        })
3457    }
3458
3459    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3460        self.add_workspace_actions_listeners(div, cx)
3461            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3462            .on_action(cx.listener(Self::close_all_items_and_panes))
3463            .on_action(cx.listener(Self::save_all))
3464            .on_action(cx.listener(Self::add_folder_to_project))
3465            .on_action(cx.listener(Self::follow_next_collaborator))
3466            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3467                let pane = workspace.active_pane().clone();
3468                workspace.unfollow(&pane, cx);
3469            }))
3470            .on_action(cx.listener(|workspace, action: &Save, cx| {
3471                workspace
3472                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3473                    .detach_and_log_err(cx);
3474            }))
3475            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3476                workspace
3477                    .save_active_item(SaveIntent::SaveAs, cx)
3478                    .detach_and_log_err(cx);
3479            }))
3480            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3481                workspace.activate_previous_pane(cx)
3482            }))
3483            .on_action(
3484                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3485            )
3486            .on_action(
3487                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3488                    workspace.activate_pane_in_direction(action.0, cx)
3489                }),
3490            )
3491            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3492                workspace.swap_pane_in_direction(action.0, cx)
3493            }))
3494            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3495                this.toggle_dock(DockPosition::Left, cx);
3496            }))
3497            .on_action(
3498                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3499                    workspace.toggle_dock(DockPosition::Right, cx);
3500                }),
3501            )
3502            .on_action(
3503                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3504                    workspace.toggle_dock(DockPosition::Bottom, cx);
3505                }),
3506            )
3507            .on_action(
3508                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3509                    workspace.close_all_docks(cx);
3510                }),
3511            )
3512            .on_action(cx.listener(Workspace::open))
3513            .on_action(cx.listener(Workspace::close_window))
3514            .on_action(cx.listener(Workspace::activate_pane_at_index))
3515            .on_action(
3516                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3517                    workspace.reopen_closed_item(cx).detach();
3518                }),
3519            )
3520            .on_action(|_: &ToggleGraphicsProfiler, cx| cx.toggle_graphics_profiler())
3521    }
3522
3523    #[cfg(any(test, feature = "test-support"))]
3524    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3525        use node_runtime::FakeNodeRuntime;
3526
3527        let client = project.read(cx).client();
3528        let user_store = project.read(cx).user_store();
3529
3530        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3531        cx.activate_window();
3532        let app_state = Arc::new(AppState {
3533            languages: project.read(cx).languages().clone(),
3534            workspace_store,
3535            client,
3536            user_store,
3537            fs: project.read(cx).fs().clone(),
3538            build_window_options: |_, _, _| Default::default(),
3539            node_runtime: FakeNodeRuntime::new(),
3540        });
3541        let workspace = Self::new(0, project, app_state, cx);
3542        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3543        workspace
3544    }
3545
3546    pub fn register_action<A: Action>(
3547        &mut self,
3548        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3549    ) -> &mut Self {
3550        let callback = Arc::new(callback);
3551
3552        self.workspace_actions.push(Box::new(move |div, cx| {
3553            let callback = callback.clone();
3554            div.on_action(
3555                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3556            )
3557        }));
3558        self
3559    }
3560
3561    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3562        let mut div = div
3563            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3564            .on_action(cx.listener(Self::close_all_items_and_panes))
3565            .on_action(cx.listener(Self::add_folder_to_project))
3566            .on_action(cx.listener(Self::save_all))
3567            .on_action(cx.listener(Self::open));
3568        for action in self.workspace_actions.iter() {
3569            div = (action)(div, cx)
3570        }
3571        div
3572    }
3573
3574    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3575        self.modal_layer.read(cx).has_active_modal()
3576    }
3577
3578    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3579        self.modal_layer.read(cx).active_modal()
3580    }
3581
3582    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3583    where
3584        B: FnOnce(&mut ViewContext<V>) -> V,
3585    {
3586        self.modal_layer
3587            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3588    }
3589}
3590
3591fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3592    let display_origin = cx
3593        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3594        .ok()??;
3595    ZED_WINDOW_POSITION
3596        .zip(*ZED_WINDOW_SIZE)
3597        .map(|(position, size)| {
3598            WindowBounds::Fixed(Bounds {
3599                origin: display_origin + position,
3600                size,
3601            })
3602        })
3603}
3604
3605fn open_items(
3606    serialized_workspace: Option<SerializedWorkspace>,
3607    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3608    app_state: Arc<AppState>,
3609    cx: &mut ViewContext<Workspace>,
3610) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3611    let restored_items = serialized_workspace.map(|serialized_workspace| {
3612        Workspace::load_workspace(
3613            serialized_workspace,
3614            project_paths_to_open
3615                .iter()
3616                .map(|(_, project_path)| project_path)
3617                .cloned()
3618                .collect(),
3619            cx,
3620        )
3621    });
3622
3623    cx.spawn(|workspace, mut cx| async move {
3624        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3625
3626        if let Some(restored_items) = restored_items {
3627            let restored_items = restored_items.await?;
3628
3629            let restored_project_paths = restored_items
3630                .iter()
3631                .filter_map(|item| {
3632                    cx.update(|cx| item.as_ref()?.project_path(cx))
3633                        .ok()
3634                        .flatten()
3635                })
3636                .collect::<HashSet<_>>();
3637
3638            for restored_item in restored_items {
3639                opened_items.push(restored_item.map(Ok));
3640            }
3641
3642            project_paths_to_open
3643                .iter_mut()
3644                .for_each(|(_, project_path)| {
3645                    if let Some(project_path_to_open) = project_path {
3646                        if restored_project_paths.contains(project_path_to_open) {
3647                            *project_path = None;
3648                        }
3649                    }
3650                });
3651        } else {
3652            for _ in 0..project_paths_to_open.len() {
3653                opened_items.push(None);
3654            }
3655        }
3656        assert!(opened_items.len() == project_paths_to_open.len());
3657
3658        let tasks =
3659            project_paths_to_open
3660                .into_iter()
3661                .enumerate()
3662                .map(|(i, (abs_path, project_path))| {
3663                    let workspace = workspace.clone();
3664                    cx.spawn(|mut cx| {
3665                        let fs = app_state.fs.clone();
3666                        async move {
3667                            let file_project_path = project_path?;
3668                            if fs.is_file(&abs_path).await {
3669                                Some((
3670                                    i,
3671                                    workspace
3672                                        .update(&mut cx, |workspace, cx| {
3673                                            workspace.open_path(file_project_path, None, true, cx)
3674                                        })
3675                                        .log_err()?
3676                                        .await,
3677                                ))
3678                            } else {
3679                                None
3680                            }
3681                        }
3682                    })
3683                });
3684
3685        let tasks = tasks.collect::<Vec<_>>();
3686
3687        let tasks = futures::future::join_all(tasks.into_iter());
3688        for maybe_opened_path in tasks.await.into_iter() {
3689            if let Some((i, path_open_result)) = maybe_opened_path {
3690                opened_items[i] = Some(path_open_result);
3691            }
3692        }
3693
3694        Ok(opened_items)
3695    })
3696}
3697
3698enum ActivateInDirectionTarget {
3699    Pane(View<Pane>),
3700    Dock(View<Dock>),
3701}
3702
3703fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3704    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3705
3706    workspace
3707        .update(cx, |workspace, cx| {
3708            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3709                workspace.show_notification_once(0, cx, |cx| {
3710                    cx.new_view(|_| {
3711                        MessageNotification::new("Failed to load the database file.")
3712                            .with_click_message("Click to let us know about this error")
3713                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3714                    })
3715                });
3716            }
3717        })
3718        .log_err();
3719}
3720
3721impl FocusableView for Workspace {
3722    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3723        self.active_pane.focus_handle(cx)
3724    }
3725}
3726
3727#[derive(Clone, Render)]
3728struct DraggedDock(DockPosition);
3729
3730impl Render for Workspace {
3731    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3732        let mut context = KeyContext::default();
3733        context.add("Workspace");
3734
3735        let (ui_font, ui_font_size) = {
3736            let theme_settings = ThemeSettings::get_global(cx);
3737            (
3738                theme_settings.ui_font.family.clone(),
3739                theme_settings.ui_font_size.clone(),
3740            )
3741        };
3742
3743        let theme = cx.theme().clone();
3744        let colors = theme.colors();
3745        cx.set_rem_size(ui_font_size);
3746
3747        self.actions(div(), cx)
3748            .key_context(context)
3749            .relative()
3750            .size_full()
3751            .flex()
3752            .flex_col()
3753            .font(ui_font)
3754            .gap_0()
3755            .justify_start()
3756            .items_start()
3757            .text_color(colors.text)
3758            .bg(colors.background)
3759            .border()
3760            .border_color(colors.border)
3761            .children(self.titlebar_item.clone())
3762            .child(
3763                div()
3764                    .id("workspace")
3765                    .relative()
3766                    .flex_1()
3767                    .w_full()
3768                    .flex()
3769                    .flex_col()
3770                    .overflow_hidden()
3771                    .border_t()
3772                    .border_b()
3773                    .border_color(colors.border)
3774                    .child(
3775                        canvas({
3776                            let this = cx.view().clone();
3777                            move |bounds, cx| {
3778                                this.update(cx, |this, _cx| {
3779                                    this.bounds = *bounds;
3780                                })
3781                            }
3782                        })
3783                        .absolute()
3784                        .size_full(),
3785                    )
3786                    .on_drag_move(
3787                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3788                            match e.drag(cx).0 {
3789                                DockPosition::Left => {
3790                                    let size = workspace.bounds.left() + e.event.position.x;
3791                                    workspace.left_dock.update(cx, |left_dock, cx| {
3792                                        left_dock.resize_active_panel(Some(size), cx);
3793                                    });
3794                                }
3795                                DockPosition::Right => {
3796                                    let size = workspace.bounds.right() - e.event.position.x;
3797                                    workspace.right_dock.update(cx, |right_dock, cx| {
3798                                        right_dock.resize_active_panel(Some(size), cx);
3799                                    });
3800                                }
3801                                DockPosition::Bottom => {
3802                                    let size = workspace.bounds.bottom() - e.event.position.y;
3803                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3804                                        bottom_dock.resize_active_panel(Some(size), cx);
3805                                    });
3806                                }
3807                            }
3808                        }),
3809                    )
3810                    .child(self.modal_layer.clone())
3811                    .child(
3812                        div()
3813                            .flex()
3814                            .flex_row()
3815                            .h_full()
3816                            // Left Dock
3817                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3818                                || {
3819                                    div()
3820                                        .flex()
3821                                        .flex_none()
3822                                        .overflow_hidden()
3823                                        .child(self.left_dock.clone())
3824                                },
3825                            ))
3826                            // Panes
3827                            .child(
3828                                div()
3829                                    .flex()
3830                                    .flex_col()
3831                                    .flex_1()
3832                                    .overflow_hidden()
3833                                    .child(self.center.render(
3834                                        &self.project,
3835                                        &self.follower_states,
3836                                        self.active_call(),
3837                                        &self.active_pane,
3838                                        self.zoomed.as_ref(),
3839                                        &self.app_state,
3840                                        cx,
3841                                    ))
3842                                    .children(
3843                                        self.zoomed_position
3844                                            .ne(&Some(DockPosition::Bottom))
3845                                            .then(|| self.bottom_dock.clone()),
3846                                    ),
3847                            )
3848                            // Right Dock
3849                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3850                                || {
3851                                    div()
3852                                        .flex()
3853                                        .flex_none()
3854                                        .overflow_hidden()
3855                                        .child(self.right_dock.clone())
3856                                },
3857                            )),
3858                    )
3859                    .children(self.render_notifications(cx))
3860                    .children(self.zoomed.as_ref().and_then(|view| {
3861                        let zoomed_view = view.upgrade()?;
3862                        let div = div()
3863                            .z_index(1)
3864                            .absolute()
3865                            .overflow_hidden()
3866                            .border_color(colors.border)
3867                            .bg(colors.background)
3868                            .child(zoomed_view)
3869                            .inset_0()
3870                            .shadow_lg();
3871
3872                        Some(match self.zoomed_position {
3873                            Some(DockPosition::Left) => div.right_2().border_r(),
3874                            Some(DockPosition::Right) => div.left_2().border_l(),
3875                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3876                            None => div.top_2().bottom_2().left_2().right_2().border(),
3877                        })
3878                    })),
3879            )
3880            .child(self.status_bar.clone())
3881            .children(if self.project.read(cx).is_disconnected() {
3882                Some(DisconnectedOverlay)
3883            } else {
3884                None
3885            })
3886    }
3887}
3888
3889impl WorkspaceStore {
3890    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3891        Self {
3892            workspaces: Default::default(),
3893            _subscriptions: vec![
3894                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3895                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3896            ],
3897            client,
3898        }
3899    }
3900
3901    pub fn update_followers(
3902        &self,
3903        project_id: Option<u64>,
3904        update: proto::update_followers::Variant,
3905        cx: &AppContext,
3906    ) -> Option<()> {
3907        let active_call = ActiveCall::try_global(cx)?;
3908        let room_id = active_call.read(cx).room()?.read(cx).id();
3909        self.client
3910            .send(proto::UpdateFollowers {
3911                room_id,
3912                project_id,
3913                variant: Some(update),
3914            })
3915            .log_err()
3916    }
3917
3918    pub async fn handle_follow(
3919        this: Model<Self>,
3920        envelope: TypedEnvelope<proto::Follow>,
3921        _: Arc<Client>,
3922        mut cx: AsyncAppContext,
3923    ) -> Result<proto::FollowResponse> {
3924        this.update(&mut cx, |this, cx| {
3925            let follower = Follower {
3926                project_id: envelope.payload.project_id,
3927                peer_id: envelope.original_sender_id()?,
3928            };
3929            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3930
3931            let mut response = proto::FollowResponse::default();
3932            this.workspaces.retain(|workspace| {
3933                workspace
3934                    .update(cx, |workspace, cx| {
3935                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3936                        if response.views.is_empty() {
3937                            response.views = handler_response.views;
3938                        } else {
3939                            response.views.extend_from_slice(&handler_response.views);
3940                        }
3941
3942                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3943                            if response.active_view_id.is_none()
3944                                || Some(workspace.project.downgrade()) == active_project
3945                            {
3946                                response.active_view_id = Some(active_view_id);
3947                            }
3948                        }
3949
3950                        if let Some(active_view) = handler_response.active_view.clone() {
3951                            if workspace.project.read(cx).remote_id() == follower.project_id {
3952                                response.active_view = Some(active_view)
3953                            }
3954                        }
3955                    })
3956                    .is_ok()
3957            });
3958
3959            Ok(response)
3960        })?
3961    }
3962
3963    async fn handle_update_followers(
3964        this: Model<Self>,
3965        envelope: TypedEnvelope<proto::UpdateFollowers>,
3966        _: Arc<Client>,
3967        mut cx: AsyncAppContext,
3968    ) -> Result<()> {
3969        let leader_id = envelope.original_sender_id()?;
3970        let update = envelope.payload;
3971
3972        this.update(&mut cx, |this, cx| {
3973            this.workspaces.retain(|workspace| {
3974                workspace
3975                    .update(cx, |workspace, cx| {
3976                        let project_id = workspace.project.read(cx).remote_id();
3977                        if update.project_id != project_id && update.project_id.is_some() {
3978                            return;
3979                        }
3980                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3981                    })
3982                    .is_ok()
3983            });
3984            Ok(())
3985        })?
3986    }
3987}
3988
3989impl ViewId {
3990    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3991        Ok(Self {
3992            creator: message
3993                .creator
3994                .ok_or_else(|| anyhow!("creator is missing"))?,
3995            id: message.id,
3996        })
3997    }
3998
3999    pub(crate) fn to_proto(&self) -> proto::ViewId {
4000        proto::ViewId {
4001            creator: Some(self.creator),
4002            id: self.id,
4003        }
4004    }
4005}
4006
4007pub trait WorkspaceHandle {
4008    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4009}
4010
4011impl WorkspaceHandle for View<Workspace> {
4012    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4013        self.read(cx)
4014            .worktrees(cx)
4015            .flat_map(|worktree| {
4016                let worktree_id = worktree.read(cx).id();
4017                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4018                    worktree_id,
4019                    path: f.path.clone(),
4020                })
4021            })
4022            .collect::<Vec<_>>()
4023    }
4024}
4025
4026impl std::fmt::Debug for OpenPaths {
4027    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4028        f.debug_struct("OpenPaths")
4029            .field("paths", &self.paths)
4030            .finish()
4031    }
4032}
4033
4034pub fn activate_workspace_for_project(
4035    cx: &mut AppContext,
4036    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4037) -> Option<WindowHandle<Workspace>> {
4038    for window in cx.windows() {
4039        let Some(workspace) = window.downcast::<Workspace>() else {
4040            continue;
4041        };
4042
4043        let predicate = workspace
4044            .update(cx, |workspace, cx| {
4045                let project = workspace.project.read(cx);
4046                if predicate(project, cx) {
4047                    cx.activate_window();
4048                    true
4049                } else {
4050                    false
4051                }
4052            })
4053            .log_err()
4054            .unwrap_or(false);
4055
4056        if predicate {
4057            return Some(workspace);
4058        }
4059    }
4060
4061    None
4062}
4063
4064pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4065    DB.last_workspace().await.log_err().flatten()
4066}
4067
4068actions!(collab, [OpenChannelNotes]);
4069
4070async fn join_channel_internal(
4071    channel_id: u64,
4072    app_state: &Arc<AppState>,
4073    requesting_window: Option<WindowHandle<Workspace>>,
4074    active_call: &Model<ActiveCall>,
4075    cx: &mut AsyncAppContext,
4076) -> Result<bool> {
4077    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4078        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4079            return (false, None);
4080        };
4081
4082        let already_in_channel = room.channel_id() == Some(channel_id);
4083        let should_prompt = room.is_sharing_project()
4084            && room.remote_participants().len() > 0
4085            && !already_in_channel;
4086        let open_room = if already_in_channel {
4087            active_call.room().cloned()
4088        } else {
4089            None
4090        };
4091        (should_prompt, open_room)
4092    })?;
4093
4094    if let Some(room) = open_room {
4095        let task = room.update(cx, |room, cx| {
4096            if let Some((project, host)) = room.most_active_project(cx) {
4097                return Some(join_remote_project(project, host, app_state.clone(), cx));
4098            }
4099
4100            None
4101        })?;
4102        if let Some(task) = task {
4103            task.await?;
4104        }
4105        return anyhow::Ok(true);
4106    }
4107
4108    if should_prompt {
4109        if let Some(workspace) = requesting_window {
4110            let answer = workspace
4111                .update(cx, |_, cx| {
4112                    cx.prompt(
4113                        PromptLevel::Warning,
4114                        "Do you want to switch channels?",
4115                        Some("Leaving this call will unshare your current project."),
4116                        &["Yes, Join Channel", "Cancel"],
4117                    )
4118                })?
4119                .await;
4120
4121            if answer == Ok(1) {
4122                return Ok(false);
4123            }
4124        } else {
4125            return Ok(false); // unreachable!() hopefully
4126        }
4127    }
4128
4129    let client = cx.update(|cx| active_call.read(cx).client())?;
4130
4131    let mut client_status = client.status();
4132
4133    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4134    'outer: loop {
4135        let Some(status) = client_status.recv().await else {
4136            return Err(anyhow!("error connecting"));
4137        };
4138
4139        match status {
4140            Status::Connecting
4141            | Status::Authenticating
4142            | Status::Reconnecting
4143            | Status::Reauthenticating => continue,
4144            Status::Connected { .. } => break 'outer,
4145            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4146            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4147            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4148                return Err(ErrorCode::Disconnected.into())
4149            }
4150        }
4151    }
4152
4153    let room = active_call
4154        .update(cx, |active_call, cx| {
4155            active_call.join_channel(channel_id, cx)
4156        })?
4157        .await?;
4158
4159    let Some(room) = room else {
4160        return anyhow::Ok(true);
4161    };
4162
4163    room.update(cx, |room, _| room.room_update_completed())?
4164        .await;
4165
4166    let task = room.update(cx, |room, cx| {
4167        if let Some((project, host)) = room.most_active_project(cx) {
4168            return Some(join_remote_project(project, host, app_state.clone(), cx));
4169        }
4170
4171        // if you are the first to join a channel, share your project
4172        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4173            if let Some(workspace) = requesting_window {
4174                let project = workspace.update(cx, |workspace, cx| {
4175                    if !CallSettings::get_global(cx).share_on_join {
4176                        return None;
4177                    }
4178                    let project = workspace.project.read(cx);
4179                    if project.is_local()
4180                        && project.visible_worktrees(cx).any(|tree| {
4181                            tree.read(cx)
4182                                .root_entry()
4183                                .map_or(false, |entry| entry.is_dir())
4184                        })
4185                    {
4186                        Some(workspace.project.clone())
4187                    } else {
4188                        None
4189                    }
4190                });
4191                if let Ok(Some(project)) = project {
4192                    return Some(cx.spawn(|room, mut cx| async move {
4193                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4194                            .await?;
4195                        Ok(())
4196                    }));
4197                }
4198            }
4199        }
4200
4201        None
4202    })?;
4203    if let Some(task) = task {
4204        task.await?;
4205        return anyhow::Ok(true);
4206    }
4207    anyhow::Ok(false)
4208}
4209
4210pub fn join_channel(
4211    channel_id: u64,
4212    app_state: Arc<AppState>,
4213    requesting_window: Option<WindowHandle<Workspace>>,
4214    cx: &mut AppContext,
4215) -> Task<Result<()>> {
4216    let active_call = ActiveCall::global(cx);
4217    cx.spawn(|mut cx| async move {
4218        let result = join_channel_internal(
4219            channel_id,
4220            &app_state,
4221            requesting_window,
4222            &active_call,
4223            &mut cx,
4224        )
4225        .await;
4226
4227        // join channel succeeded, and opened a window
4228        if matches!(result, Ok(true)) {
4229            return anyhow::Ok(());
4230        }
4231
4232        // find an existing workspace to focus and show call controls
4233        let mut active_window =
4234            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4235        if active_window.is_none() {
4236            // no open workspaces, make one to show the error in (blergh)
4237            let (window_handle, _) = cx
4238                .update(|cx| {
4239                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4240                })?
4241                .await?;
4242
4243            if result.is_ok() {
4244                cx.update(|cx| {
4245                    cx.dispatch_action(&OpenChannelNotes);
4246                }).log_err();
4247            }
4248
4249            active_window = Some(window_handle);
4250        }
4251
4252        if let Err(err) = result {
4253            log::error!("failed to join channel: {}", err);
4254            if let Some(active_window) = active_window {
4255                active_window
4256                    .update(&mut cx, |_, cx| {
4257                        let detail: SharedString = match err.error_code() {
4258                            ErrorCode::SignedOut => {
4259                                "Please sign in to continue.".into()
4260                            },
4261                            ErrorCode::UpgradeRequired => {
4262                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4263                            },
4264                            ErrorCode::NoSuchChannel => {
4265                                "No matching channel was found. Please check the link and try again.".into()
4266                            },
4267                            ErrorCode::Forbidden => {
4268                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4269                            },
4270                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4271                            _ => format!("{}\n\nPlease try again.", err).into(),
4272                        };
4273                        cx.prompt(
4274                            PromptLevel::Critical,
4275                            "Failed to join channel",
4276                            Some(&detail),
4277                            &["Ok"],
4278                        )
4279                    })?
4280                    .await
4281                    .ok();
4282            }
4283        }
4284
4285        // return ok, we showed the error to the user.
4286        return anyhow::Ok(());
4287    })
4288}
4289
4290pub async fn get_any_active_workspace(
4291    app_state: Arc<AppState>,
4292    mut cx: AsyncAppContext,
4293) -> anyhow::Result<WindowHandle<Workspace>> {
4294    // find an existing workspace to focus and show call controls
4295    let active_window = activate_any_workspace_window(&mut cx);
4296    if active_window.is_none() {
4297        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4298            .await?;
4299    }
4300    activate_any_workspace_window(&mut cx).context("could not open zed")
4301}
4302
4303fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4304    cx.update(|cx| {
4305        for window in cx.windows() {
4306            if let Some(workspace_window) = window.downcast::<Workspace>() {
4307                workspace_window
4308                    .update(cx, |_, cx| cx.activate_window())
4309                    .ok();
4310                return Some(workspace_window);
4311            }
4312        }
4313        None
4314    })
4315    .ok()
4316    .flatten()
4317}
4318
4319#[allow(clippy::type_complexity)]
4320pub fn open_paths(
4321    abs_paths: &[PathBuf],
4322    app_state: &Arc<AppState>,
4323    requesting_window: Option<WindowHandle<Workspace>>,
4324    cx: &mut AppContext,
4325) -> Task<
4326    anyhow::Result<(
4327        WindowHandle<Workspace>,
4328        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4329    )>,
4330> {
4331    let app_state = app_state.clone();
4332    let abs_paths = abs_paths.to_vec();
4333    // Open paths in existing workspace if possible
4334    let existing = activate_workspace_for_project(cx, {
4335        let abs_paths = abs_paths.clone();
4336        move |project, cx| project.contains_paths(&abs_paths, cx)
4337    });
4338    cx.spawn(move |mut cx| async move {
4339        if let Some(existing) = existing {
4340            Ok((
4341                existing.clone(),
4342                existing
4343                    .update(&mut cx, |workspace, cx| {
4344                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4345                    })?
4346                    .await,
4347            ))
4348        } else {
4349            cx.update(move |cx| {
4350                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4351            })?
4352            .await
4353        }
4354    })
4355}
4356
4357pub fn open_new(
4358    app_state: &Arc<AppState>,
4359    cx: &mut AppContext,
4360    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4361) -> Task<()> {
4362    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4363    cx.spawn(|mut cx| async move {
4364        if let Some((workspace, opened_paths)) = task.await.log_err() {
4365            workspace
4366                .update(&mut cx, |workspace, cx| {
4367                    if opened_paths.is_empty() {
4368                        init(workspace, cx)
4369                    }
4370                })
4371                .log_err();
4372        }
4373    })
4374}
4375
4376pub fn create_and_open_local_file(
4377    path: &'static Path,
4378    cx: &mut ViewContext<Workspace>,
4379    default_content: impl 'static + Send + FnOnce() -> Rope,
4380) -> Task<Result<Box<dyn ItemHandle>>> {
4381    cx.spawn(|workspace, mut cx| async move {
4382        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4383        if !fs.is_file(path).await {
4384            fs.create_file(path, Default::default()).await?;
4385            fs.save(path, &default_content(), Default::default())
4386                .await?;
4387        }
4388
4389        let mut items = workspace
4390            .update(&mut cx, |workspace, cx| {
4391                workspace.with_local_workspace(cx, |workspace, cx| {
4392                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4393                })
4394            })?
4395            .await?
4396            .await;
4397
4398        let item = items.pop().flatten();
4399        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4400    })
4401}
4402
4403pub fn join_remote_project(
4404    project_id: u64,
4405    follow_user_id: u64,
4406    app_state: Arc<AppState>,
4407    cx: &mut AppContext,
4408) -> Task<Result<()>> {
4409    let windows = cx.windows();
4410    cx.spawn(|mut cx| async move {
4411        let existing_workspace = windows.into_iter().find_map(|window| {
4412            window.downcast::<Workspace>().and_then(|window| {
4413                window
4414                    .update(&mut cx, |workspace, cx| {
4415                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4416                            Some(window)
4417                        } else {
4418                            None
4419                        }
4420                    })
4421                    .unwrap_or(None)
4422            })
4423        });
4424
4425        let workspace = if let Some(existing_workspace) = existing_workspace {
4426            existing_workspace
4427        } else {
4428            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4429            let room = active_call
4430                .read_with(&cx, |call, _| call.room().cloned())?
4431                .ok_or_else(|| anyhow!("not in a call"))?;
4432            let project = room
4433                .update(&mut cx, |room, cx| {
4434                    room.join_project(
4435                        project_id,
4436                        app_state.languages.clone(),
4437                        app_state.fs.clone(),
4438                        cx,
4439                    )
4440                })?
4441                .await?;
4442
4443            let window_bounds_override = window_bounds_env_override(&cx);
4444            cx.update(|cx| {
4445                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4446                cx.open_window(options, |cx| {
4447                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4448                })
4449            })?
4450        };
4451
4452        workspace.update(&mut cx, |workspace, cx| {
4453            cx.activate(true);
4454            cx.activate_window();
4455
4456            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4457                let follow_peer_id = room
4458                    .read(cx)
4459                    .remote_participants()
4460                    .iter()
4461                    .find(|(_, participant)| participant.user.id == follow_user_id)
4462                    .map(|(_, p)| p.peer_id)
4463                    .or_else(|| {
4464                        // If we couldn't follow the given user, follow the host instead.
4465                        let collaborator = workspace
4466                            .project()
4467                            .read(cx)
4468                            .collaborators()
4469                            .values()
4470                            .find(|collaborator| collaborator.replica_id == 0)?;
4471                        Some(collaborator.peer_id)
4472                    });
4473
4474                if let Some(follow_peer_id) = follow_peer_id {
4475                    workspace.follow(follow_peer_id, cx);
4476                }
4477            }
4478        })?;
4479
4480        anyhow::Ok(())
4481    })
4482}
4483
4484pub fn restart(_: &Restart, cx: &mut AppContext) {
4485    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4486    let mut workspace_windows = cx
4487        .windows()
4488        .into_iter()
4489        .filter_map(|window| window.downcast::<Workspace>())
4490        .collect::<Vec<_>>();
4491
4492    // If multiple windows have unsaved changes, and need a save prompt,
4493    // prompt in the active window before switching to a different window.
4494    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4495
4496    let mut prompt = None;
4497    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4498        prompt = window
4499            .update(cx, |_, cx| {
4500                cx.prompt(
4501                    PromptLevel::Info,
4502                    "Are you sure you want to restart?",
4503                    None,
4504                    &["Restart", "Cancel"],
4505                )
4506            })
4507            .ok();
4508    }
4509
4510    cx.spawn(|mut cx| async move {
4511        if let Some(prompt) = prompt {
4512            let answer = prompt.await?;
4513            if answer != 0 {
4514                return Ok(());
4515            }
4516        }
4517
4518        // If the user cancels any save prompt, then keep the app open.
4519        for window in workspace_windows {
4520            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4521                workspace.prepare_to_close(true, cx)
4522            }) {
4523                if !should_close.await? {
4524                    return Ok(());
4525                }
4526            }
4527        }
4528
4529        cx.update(|cx| cx.restart())
4530    })
4531    .detach_and_log_err(cx);
4532}
4533
4534fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4535    let mut parts = value.split(',');
4536    let x: usize = parts.next()?.parse().ok()?;
4537    let y: usize = parts.next()?.parse().ok()?;
4538    Some(point((x as f64).into(), (y as f64).into()))
4539}
4540
4541fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4542    let mut parts = value.split(',');
4543    let width: usize = parts.next()?.parse().ok()?;
4544    let height: usize = parts.next()?.parse().ok()?;
4545    Some(size((width as f64).into(), (height as f64).into()))
4546}
4547
4548pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4549    (1.75 * cx.rem_size()).max(px(32.))
4550}
4551
4552struct DisconnectedOverlay;
4553
4554impl Element for DisconnectedOverlay {
4555    type State = AnyElement;
4556
4557    fn request_layout(
4558        &mut self,
4559        _: Option<Self::State>,
4560        cx: &mut ElementContext,
4561    ) -> (LayoutId, Self::State) {
4562        let mut background = cx.theme().colors().elevated_surface_background;
4563        background.fade_out(0.2);
4564        let mut overlay = div()
4565            .bg(background)
4566            .absolute()
4567            .left_0()
4568            .top(titlebar_height(cx))
4569            .size_full()
4570            .flex()
4571            .items_center()
4572            .justify_center()
4573            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4574            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4575            .child(Label::new(
4576                "Your connection to the remote project has been lost.",
4577            ))
4578            .into_any();
4579        (overlay.request_layout(cx), overlay)
4580    }
4581
4582    fn paint(
4583        &mut self,
4584        bounds: Bounds<Pixels>,
4585        overlay: &mut Self::State,
4586        cx: &mut ElementContext,
4587    ) {
4588        cx.with_z_index(u16::MAX, |cx| {
4589            cx.add_opaque_layer(bounds);
4590            overlay.paint(cx);
4591        })
4592    }
4593}
4594
4595impl IntoElement for DisconnectedOverlay {
4596    type Element = Self;
4597
4598    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4599        None
4600    }
4601
4602    fn into_element(self) -> Self::Element {
4603        self
4604    }
4605}
4606
4607#[cfg(test)]
4608mod tests {
4609    use std::{cell::RefCell, rc::Rc};
4610
4611    use super::*;
4612    use crate::{
4613        dock::{test::TestPanel, PanelEvent},
4614        item::{
4615            test::{TestItem, TestProjectItem},
4616            ItemEvent,
4617        },
4618    };
4619    use fs::FakeFs;
4620    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4621    use project::{Project, ProjectEntryId};
4622    use serde_json::json;
4623    use settings::SettingsStore;
4624
4625    #[gpui::test]
4626    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4627        init_test(cx);
4628
4629        let fs = FakeFs::new(cx.executor());
4630        let project = Project::test(fs, [], cx).await;
4631        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4632
4633        // Adding an item with no ambiguity renders the tab without detail.
4634        let item1 = cx.new_view(|cx| {
4635            let mut item = TestItem::new(cx);
4636            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4637            item
4638        });
4639        workspace.update(cx, |workspace, cx| {
4640            workspace.add_item(Box::new(item1.clone()), cx);
4641        });
4642        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4643
4644        // Adding an item that creates ambiguity increases the level of detail on
4645        // both tabs.
4646        let item2 = cx.new_view(|cx| {
4647            let mut item = TestItem::new(cx);
4648            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4649            item
4650        });
4651        workspace.update(cx, |workspace, cx| {
4652            workspace.add_item(Box::new(item2.clone()), cx);
4653        });
4654        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4655        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4656
4657        // Adding an item that creates ambiguity increases the level of detail only
4658        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4659        // we stop at the highest detail available.
4660        let item3 = cx.new_view(|cx| {
4661            let mut item = TestItem::new(cx);
4662            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4663            item
4664        });
4665        workspace.update(cx, |workspace, cx| {
4666            workspace.add_item(Box::new(item3.clone()), cx);
4667        });
4668        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4669        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4670        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4671    }
4672
4673    #[gpui::test]
4674    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4675        init_test(cx);
4676
4677        let fs = FakeFs::new(cx.executor());
4678        fs.insert_tree(
4679            "/root1",
4680            json!({
4681                "one.txt": "",
4682                "two.txt": "",
4683            }),
4684        )
4685        .await;
4686        fs.insert_tree(
4687            "/root2",
4688            json!({
4689                "three.txt": "",
4690            }),
4691        )
4692        .await;
4693
4694        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4695        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4696        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4697        let worktree_id = project.update(cx, |project, cx| {
4698            project.worktrees().next().unwrap().read(cx).id()
4699        });
4700
4701        let item1 = cx.new_view(|cx| {
4702            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4703        });
4704        let item2 = cx.new_view(|cx| {
4705            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4706        });
4707
4708        // Add an item to an empty pane
4709        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4710        project.update(cx, |project, cx| {
4711            assert_eq!(
4712                project.active_entry(),
4713                project
4714                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4715                    .map(|e| e.id)
4716            );
4717        });
4718        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4719
4720        // Add a second item to a non-empty pane
4721        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4722        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4723        project.update(cx, |project, cx| {
4724            assert_eq!(
4725                project.active_entry(),
4726                project
4727                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4728                    .map(|e| e.id)
4729            );
4730        });
4731
4732        // Close the active item
4733        pane.update(cx, |pane, cx| {
4734            pane.close_active_item(&Default::default(), cx).unwrap()
4735        })
4736        .await
4737        .unwrap();
4738        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4739        project.update(cx, |project, cx| {
4740            assert_eq!(
4741                project.active_entry(),
4742                project
4743                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4744                    .map(|e| e.id)
4745            );
4746        });
4747
4748        // Add a project folder
4749        project
4750            .update(cx, |project, cx| {
4751                project.find_or_create_local_worktree("/root2", true, cx)
4752            })
4753            .await
4754            .unwrap();
4755        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4756
4757        // Remove a project folder
4758        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4759        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4760    }
4761
4762    #[gpui::test]
4763    async fn test_close_window(cx: &mut TestAppContext) {
4764        init_test(cx);
4765
4766        let fs = FakeFs::new(cx.executor());
4767        fs.insert_tree("/root", json!({ "one": "" })).await;
4768
4769        let project = Project::test(fs, ["root".as_ref()], cx).await;
4770        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4771
4772        // When there are no dirty items, there's nothing to do.
4773        let item1 = cx.new_view(|cx| TestItem::new(cx));
4774        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4775        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4776        assert!(task.await.unwrap());
4777
4778        // When there are dirty untitled items, prompt to save each one. If the user
4779        // cancels any prompt, then abort.
4780        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4781        let item3 = cx.new_view(|cx| {
4782            TestItem::new(cx)
4783                .with_dirty(true)
4784                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4785        });
4786        workspace.update(cx, |w, cx| {
4787            w.add_item(Box::new(item2.clone()), cx);
4788            w.add_item(Box::new(item3.clone()), cx);
4789        });
4790        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4791        cx.executor().run_until_parked();
4792        cx.simulate_prompt_answer(2); // cancel save all
4793        cx.executor().run_until_parked();
4794        cx.simulate_prompt_answer(2); // cancel save all
4795        cx.executor().run_until_parked();
4796        assert!(!cx.has_pending_prompt());
4797        assert!(!task.await.unwrap());
4798    }
4799
4800    #[gpui::test]
4801    async fn test_close_pane_items(cx: &mut TestAppContext) {
4802        init_test(cx);
4803
4804        let fs = FakeFs::new(cx.executor());
4805
4806        let project = Project::test(fs, None, cx).await;
4807        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4808
4809        let item1 = cx.new_view(|cx| {
4810            TestItem::new(cx)
4811                .with_dirty(true)
4812                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4813        });
4814        let item2 = cx.new_view(|cx| {
4815            TestItem::new(cx)
4816                .with_dirty(true)
4817                .with_conflict(true)
4818                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4819        });
4820        let item3 = cx.new_view(|cx| {
4821            TestItem::new(cx)
4822                .with_dirty(true)
4823                .with_conflict(true)
4824                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4825        });
4826        let item4 = cx.new_view(|cx| {
4827            TestItem::new(cx)
4828                .with_dirty(true)
4829                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4830        });
4831        let pane = workspace.update(cx, |workspace, cx| {
4832            workspace.add_item(Box::new(item1.clone()), cx);
4833            workspace.add_item(Box::new(item2.clone()), cx);
4834            workspace.add_item(Box::new(item3.clone()), cx);
4835            workspace.add_item(Box::new(item4.clone()), cx);
4836            workspace.active_pane().clone()
4837        });
4838
4839        let close_items = pane.update(cx, |pane, cx| {
4840            pane.activate_item(1, true, true, cx);
4841            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4842            let item1_id = item1.item_id();
4843            let item3_id = item3.item_id();
4844            let item4_id = item4.item_id();
4845            pane.close_items(cx, SaveIntent::Close, move |id| {
4846                [item1_id, item3_id, item4_id].contains(&id)
4847            })
4848        });
4849        cx.executor().run_until_parked();
4850
4851        assert!(cx.has_pending_prompt());
4852        // Ignore "Save all" prompt
4853        cx.simulate_prompt_answer(2);
4854        cx.executor().run_until_parked();
4855        // There's a prompt to save item 1.
4856        pane.update(cx, |pane, _| {
4857            assert_eq!(pane.items_len(), 4);
4858            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4859        });
4860        // Confirm saving item 1.
4861        cx.simulate_prompt_answer(0);
4862        cx.executor().run_until_parked();
4863
4864        // Item 1 is saved. There's a prompt to save item 3.
4865        pane.update(cx, |pane, cx| {
4866            assert_eq!(item1.read(cx).save_count, 1);
4867            assert_eq!(item1.read(cx).save_as_count, 0);
4868            assert_eq!(item1.read(cx).reload_count, 0);
4869            assert_eq!(pane.items_len(), 3);
4870            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4871        });
4872        assert!(cx.has_pending_prompt());
4873
4874        // Cancel saving item 3.
4875        cx.simulate_prompt_answer(1);
4876        cx.executor().run_until_parked();
4877
4878        // Item 3 is reloaded. There's a prompt to save item 4.
4879        pane.update(cx, |pane, cx| {
4880            assert_eq!(item3.read(cx).save_count, 0);
4881            assert_eq!(item3.read(cx).save_as_count, 0);
4882            assert_eq!(item3.read(cx).reload_count, 1);
4883            assert_eq!(pane.items_len(), 2);
4884            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4885        });
4886        assert!(cx.has_pending_prompt());
4887
4888        // Confirm saving item 4.
4889        cx.simulate_prompt_answer(0);
4890        cx.executor().run_until_parked();
4891
4892        // There's a prompt for a path for item 4.
4893        cx.simulate_new_path_selection(|_| Some(Default::default()));
4894        close_items.await.unwrap();
4895
4896        // The requested items are closed.
4897        pane.update(cx, |pane, cx| {
4898            assert_eq!(item4.read(cx).save_count, 0);
4899            assert_eq!(item4.read(cx).save_as_count, 1);
4900            assert_eq!(item4.read(cx).reload_count, 0);
4901            assert_eq!(pane.items_len(), 1);
4902            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4903        });
4904    }
4905
4906    #[gpui::test]
4907    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4908        init_test(cx);
4909
4910        let fs = FakeFs::new(cx.executor());
4911        let project = Project::test(fs, [], cx).await;
4912        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4913
4914        // Create several workspace items with single project entries, and two
4915        // workspace items with multiple project entries.
4916        let single_entry_items = (0..=4)
4917            .map(|project_entry_id| {
4918                cx.new_view(|cx| {
4919                    TestItem::new(cx)
4920                        .with_dirty(true)
4921                        .with_project_items(&[TestProjectItem::new(
4922                            project_entry_id,
4923                            &format!("{project_entry_id}.txt"),
4924                            cx,
4925                        )])
4926                })
4927            })
4928            .collect::<Vec<_>>();
4929        let item_2_3 = cx.new_view(|cx| {
4930            TestItem::new(cx)
4931                .with_dirty(true)
4932                .with_singleton(false)
4933                .with_project_items(&[
4934                    single_entry_items[2].read(cx).project_items[0].clone(),
4935                    single_entry_items[3].read(cx).project_items[0].clone(),
4936                ])
4937        });
4938        let item_3_4 = cx.new_view(|cx| {
4939            TestItem::new(cx)
4940                .with_dirty(true)
4941                .with_singleton(false)
4942                .with_project_items(&[
4943                    single_entry_items[3].read(cx).project_items[0].clone(),
4944                    single_entry_items[4].read(cx).project_items[0].clone(),
4945                ])
4946        });
4947
4948        // Create two panes that contain the following project entries:
4949        //   left pane:
4950        //     multi-entry items:   (2, 3)
4951        //     single-entry items:  0, 1, 2, 3, 4
4952        //   right pane:
4953        //     single-entry items:  1
4954        //     multi-entry items:   (3, 4)
4955        let left_pane = workspace.update(cx, |workspace, cx| {
4956            let left_pane = workspace.active_pane().clone();
4957            workspace.add_item(Box::new(item_2_3.clone()), cx);
4958            for item in single_entry_items {
4959                workspace.add_item(Box::new(item), cx);
4960            }
4961            left_pane.update(cx, |pane, cx| {
4962                pane.activate_item(2, true, true, cx);
4963            });
4964
4965            let right_pane = workspace
4966                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4967                .unwrap();
4968
4969            right_pane.update(cx, |pane, cx| {
4970                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4971            });
4972
4973            left_pane
4974        });
4975
4976        cx.focus_view(&left_pane);
4977
4978        // When closing all of the items in the left pane, we should be prompted twice:
4979        // once for project entry 0, and once for project entry 2. Project entries 1,
4980        // 3, and 4 are all still open in the other paten. After those two
4981        // prompts, the task should complete.
4982
4983        let close = left_pane.update(cx, |pane, cx| {
4984            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4985        });
4986        cx.executor().run_until_parked();
4987
4988        // Discard "Save all" prompt
4989        cx.simulate_prompt_answer(2);
4990
4991        cx.executor().run_until_parked();
4992        left_pane.update(cx, |pane, cx| {
4993            assert_eq!(
4994                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4995                &[ProjectEntryId::from_proto(0)]
4996            );
4997        });
4998        cx.simulate_prompt_answer(0);
4999
5000        cx.executor().run_until_parked();
5001        left_pane.update(cx, |pane, cx| {
5002            assert_eq!(
5003                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5004                &[ProjectEntryId::from_proto(2)]
5005            );
5006        });
5007        cx.simulate_prompt_answer(0);
5008
5009        cx.executor().run_until_parked();
5010        close.await.unwrap();
5011        left_pane.update(cx, |pane, _| {
5012            assert_eq!(pane.items_len(), 0);
5013        });
5014    }
5015
5016    #[gpui::test]
5017    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5018        init_test(cx);
5019
5020        let fs = FakeFs::new(cx.executor());
5021        let project = Project::test(fs, [], cx).await;
5022        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5023        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5024
5025        let item = cx.new_view(|cx| {
5026            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5027        });
5028        let item_id = item.entity_id();
5029        workspace.update(cx, |workspace, cx| {
5030            workspace.add_item(Box::new(item.clone()), cx);
5031        });
5032
5033        // Autosave on window change.
5034        item.update(cx, |item, cx| {
5035            cx.update_global(|settings: &mut SettingsStore, cx| {
5036                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5037                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5038                })
5039            });
5040            item.is_dirty = true;
5041        });
5042
5043        // Deactivating the window saves the file.
5044        cx.deactivate_window();
5045        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5046
5047        // Autosave on focus change.
5048        item.update(cx, |item, cx| {
5049            cx.focus_self();
5050            cx.update_global(|settings: &mut SettingsStore, cx| {
5051                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5052                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5053                })
5054            });
5055            item.is_dirty = true;
5056        });
5057
5058        // Blurring the item saves the file.
5059        item.update(cx, |_, cx| cx.blur());
5060        cx.executor().run_until_parked();
5061        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5062
5063        // Deactivating the window still saves the file.
5064        cx.update(|cx| cx.activate_window());
5065        item.update(cx, |item, cx| {
5066            cx.focus_self();
5067            item.is_dirty = true;
5068        });
5069        cx.deactivate_window();
5070
5071        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5072
5073        // Autosave after delay.
5074        item.update(cx, |item, cx| {
5075            cx.update_global(|settings: &mut SettingsStore, cx| {
5076                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5077                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5078                })
5079            });
5080            item.is_dirty = true;
5081            cx.emit(ItemEvent::Edit);
5082        });
5083
5084        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5085        cx.executor().advance_clock(Duration::from_millis(250));
5086        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5087
5088        // After delay expires, the file is saved.
5089        cx.executor().advance_clock(Duration::from_millis(250));
5090        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5091
5092        // Autosave on focus change, ensuring closing the tab counts as such.
5093        item.update(cx, |item, cx| {
5094            cx.update_global(|settings: &mut SettingsStore, cx| {
5095                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5096                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5097                })
5098            });
5099            item.is_dirty = true;
5100        });
5101
5102        pane.update(cx, |pane, cx| {
5103            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5104        })
5105        .await
5106        .unwrap();
5107        assert!(!cx.has_pending_prompt());
5108        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5109
5110        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5111        workspace.update(cx, |workspace, cx| {
5112            workspace.add_item(Box::new(item.clone()), cx);
5113        });
5114        item.update(cx, |item, cx| {
5115            item.project_items[0].update(cx, |item, _| {
5116                item.entry_id = None;
5117            });
5118            item.is_dirty = true;
5119            cx.blur();
5120        });
5121        cx.run_until_parked();
5122        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5123
5124        // Ensure autosave is prevented for deleted files also when closing the buffer.
5125        let _close_items = pane.update(cx, |pane, cx| {
5126            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5127        });
5128        cx.run_until_parked();
5129        assert!(cx.has_pending_prompt());
5130        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5131    }
5132
5133    #[gpui::test]
5134    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5135        init_test(cx);
5136
5137        let fs = FakeFs::new(cx.executor());
5138
5139        let project = Project::test(fs, [], cx).await;
5140        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5141
5142        let item = cx.new_view(|cx| {
5143            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5144        });
5145        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5146        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5147        let toolbar_notify_count = Rc::new(RefCell::new(0));
5148
5149        workspace.update(cx, |workspace, cx| {
5150            workspace.add_item(Box::new(item.clone()), cx);
5151            let toolbar_notification_count = toolbar_notify_count.clone();
5152            cx.observe(&toolbar, move |_, _, _| {
5153                *toolbar_notification_count.borrow_mut() += 1
5154            })
5155            .detach();
5156        });
5157
5158        pane.update(cx, |pane, _| {
5159            assert!(!pane.can_navigate_backward());
5160            assert!(!pane.can_navigate_forward());
5161        });
5162
5163        item.update(cx, |item, cx| {
5164            item.set_state("one".to_string(), cx);
5165        });
5166
5167        // Toolbar must be notified to re-render the navigation buttons
5168        assert_eq!(*toolbar_notify_count.borrow(), 1);
5169
5170        pane.update(cx, |pane, _| {
5171            assert!(pane.can_navigate_backward());
5172            assert!(!pane.can_navigate_forward());
5173        });
5174
5175        workspace
5176            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5177            .await
5178            .unwrap();
5179
5180        assert_eq!(*toolbar_notify_count.borrow(), 2);
5181        pane.update(cx, |pane, _| {
5182            assert!(!pane.can_navigate_backward());
5183            assert!(pane.can_navigate_forward());
5184        });
5185    }
5186
5187    #[gpui::test]
5188    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5189        init_test(cx);
5190        let fs = FakeFs::new(cx.executor());
5191
5192        let project = Project::test(fs, [], cx).await;
5193        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5194
5195        let panel = workspace.update(cx, |workspace, cx| {
5196            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5197            workspace.add_panel(panel.clone(), cx);
5198
5199            workspace
5200                .right_dock()
5201                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5202
5203            panel
5204        });
5205
5206        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5207        pane.update(cx, |pane, cx| {
5208            let item = cx.new_view(|cx| TestItem::new(cx));
5209            pane.add_item(Box::new(item), true, true, None, cx);
5210        });
5211
5212        // Transfer focus from center to panel
5213        workspace.update(cx, |workspace, cx| {
5214            workspace.toggle_panel_focus::<TestPanel>(cx);
5215        });
5216
5217        workspace.update(cx, |workspace, cx| {
5218            assert!(workspace.right_dock().read(cx).is_open());
5219            assert!(!panel.is_zoomed(cx));
5220            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5221        });
5222
5223        // Transfer focus from panel to center
5224        workspace.update(cx, |workspace, cx| {
5225            workspace.toggle_panel_focus::<TestPanel>(cx);
5226        });
5227
5228        workspace.update(cx, |workspace, cx| {
5229            assert!(workspace.right_dock().read(cx).is_open());
5230            assert!(!panel.is_zoomed(cx));
5231            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5232        });
5233
5234        // Close the dock
5235        workspace.update(cx, |workspace, cx| {
5236            workspace.toggle_dock(DockPosition::Right, cx);
5237        });
5238
5239        workspace.update(cx, |workspace, cx| {
5240            assert!(!workspace.right_dock().read(cx).is_open());
5241            assert!(!panel.is_zoomed(cx));
5242            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5243        });
5244
5245        // Open the dock
5246        workspace.update(cx, |workspace, cx| {
5247            workspace.toggle_dock(DockPosition::Right, cx);
5248        });
5249
5250        workspace.update(cx, |workspace, cx| {
5251            assert!(workspace.right_dock().read(cx).is_open());
5252            assert!(!panel.is_zoomed(cx));
5253            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5254        });
5255
5256        // Focus and zoom panel
5257        panel.update(cx, |panel, cx| {
5258            cx.focus_self();
5259            panel.set_zoomed(true, cx)
5260        });
5261
5262        workspace.update(cx, |workspace, cx| {
5263            assert!(workspace.right_dock().read(cx).is_open());
5264            assert!(panel.is_zoomed(cx));
5265            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5266        });
5267
5268        // Transfer focus to the center closes the dock
5269        workspace.update(cx, |workspace, cx| {
5270            workspace.toggle_panel_focus::<TestPanel>(cx);
5271        });
5272
5273        workspace.update(cx, |workspace, cx| {
5274            assert!(!workspace.right_dock().read(cx).is_open());
5275            assert!(panel.is_zoomed(cx));
5276            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5277        });
5278
5279        // Transferring focus back to the panel keeps it zoomed
5280        workspace.update(cx, |workspace, cx| {
5281            workspace.toggle_panel_focus::<TestPanel>(cx);
5282        });
5283
5284        workspace.update(cx, |workspace, cx| {
5285            assert!(workspace.right_dock().read(cx).is_open());
5286            assert!(panel.is_zoomed(cx));
5287            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5288        });
5289
5290        // Close the dock while it is zoomed
5291        workspace.update(cx, |workspace, cx| {
5292            workspace.toggle_dock(DockPosition::Right, cx)
5293        });
5294
5295        workspace.update(cx, |workspace, cx| {
5296            assert!(!workspace.right_dock().read(cx).is_open());
5297            assert!(panel.is_zoomed(cx));
5298            assert!(workspace.zoomed.is_none());
5299            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5300        });
5301
5302        // Opening the dock, when it's zoomed, retains focus
5303        workspace.update(cx, |workspace, cx| {
5304            workspace.toggle_dock(DockPosition::Right, cx)
5305        });
5306
5307        workspace.update(cx, |workspace, cx| {
5308            assert!(workspace.right_dock().read(cx).is_open());
5309            assert!(panel.is_zoomed(cx));
5310            assert!(workspace.zoomed.is_some());
5311            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5312        });
5313
5314        // Unzoom and close the panel, zoom the active pane.
5315        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5316        workspace.update(cx, |workspace, cx| {
5317            workspace.toggle_dock(DockPosition::Right, cx)
5318        });
5319        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5320
5321        // Opening a dock unzooms the pane.
5322        workspace.update(cx, |workspace, cx| {
5323            workspace.toggle_dock(DockPosition::Right, cx)
5324        });
5325        workspace.update(cx, |workspace, cx| {
5326            let pane = pane.read(cx);
5327            assert!(!pane.is_zoomed());
5328            assert!(!pane.focus_handle(cx).is_focused(cx));
5329            assert!(workspace.right_dock().read(cx).is_open());
5330            assert!(workspace.zoomed.is_none());
5331        });
5332    }
5333
5334    struct TestModal(FocusHandle);
5335
5336    impl TestModal {
5337        fn new(cx: &mut ViewContext<Self>) -> Self {
5338            Self(cx.focus_handle())
5339        }
5340    }
5341
5342    impl EventEmitter<DismissEvent> for TestModal {}
5343
5344    impl FocusableView for TestModal {
5345        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5346            self.0.clone()
5347        }
5348    }
5349
5350    impl ModalView for TestModal {}
5351
5352    impl Render for TestModal {
5353        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5354            div().track_focus(&self.0)
5355        }
5356    }
5357
5358    #[gpui::test]
5359    async fn test_panels(cx: &mut gpui::TestAppContext) {
5360        init_test(cx);
5361        let fs = FakeFs::new(cx.executor());
5362
5363        let project = Project::test(fs, [], cx).await;
5364        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5365
5366        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5367            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5368            workspace.add_panel(panel_1.clone(), cx);
5369            workspace
5370                .left_dock()
5371                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5372            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5373            workspace.add_panel(panel_2.clone(), cx);
5374            workspace
5375                .right_dock()
5376                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5377
5378            let left_dock = workspace.left_dock();
5379            assert_eq!(
5380                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5381                panel_1.panel_id()
5382            );
5383            assert_eq!(
5384                left_dock.read(cx).active_panel_size(cx).unwrap(),
5385                panel_1.size(cx)
5386            );
5387
5388            left_dock.update(cx, |left_dock, cx| {
5389                left_dock.resize_active_panel(Some(px(1337.)), cx)
5390            });
5391            assert_eq!(
5392                workspace
5393                    .right_dock()
5394                    .read(cx)
5395                    .visible_panel()
5396                    .unwrap()
5397                    .panel_id(),
5398                panel_2.panel_id(),
5399            );
5400
5401            (panel_1, panel_2)
5402        });
5403
5404        // Move panel_1 to the right
5405        panel_1.update(cx, |panel_1, cx| {
5406            panel_1.set_position(DockPosition::Right, cx)
5407        });
5408
5409        workspace.update(cx, |workspace, cx| {
5410            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5411            // Since it was the only panel on the left, the left dock should now be closed.
5412            assert!(!workspace.left_dock().read(cx).is_open());
5413            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5414            let right_dock = workspace.right_dock();
5415            assert_eq!(
5416                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5417                panel_1.panel_id()
5418            );
5419            assert_eq!(
5420                right_dock.read(cx).active_panel_size(cx).unwrap(),
5421                px(1337.)
5422            );
5423
5424            // Now we move panel_2 to the left
5425            panel_2.set_position(DockPosition::Left, cx);
5426        });
5427
5428        workspace.update(cx, |workspace, cx| {
5429            // Since panel_2 was not visible on the right, we don't open the left dock.
5430            assert!(!workspace.left_dock().read(cx).is_open());
5431            // And the right dock is unaffected in it's displaying of panel_1
5432            assert!(workspace.right_dock().read(cx).is_open());
5433            assert_eq!(
5434                workspace
5435                    .right_dock()
5436                    .read(cx)
5437                    .visible_panel()
5438                    .unwrap()
5439                    .panel_id(),
5440                panel_1.panel_id(),
5441            );
5442        });
5443
5444        // Move panel_1 back to the left
5445        panel_1.update(cx, |panel_1, cx| {
5446            panel_1.set_position(DockPosition::Left, cx)
5447        });
5448
5449        workspace.update(cx, |workspace, cx| {
5450            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5451            let left_dock = workspace.left_dock();
5452            assert!(left_dock.read(cx).is_open());
5453            assert_eq!(
5454                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5455                panel_1.panel_id()
5456            );
5457            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5458            // And the right dock should be closed as it no longer has any panels.
5459            assert!(!workspace.right_dock().read(cx).is_open());
5460
5461            // Now we move panel_1 to the bottom
5462            panel_1.set_position(DockPosition::Bottom, cx);
5463        });
5464
5465        workspace.update(cx, |workspace, cx| {
5466            // Since panel_1 was visible on the left, we close the left dock.
5467            assert!(!workspace.left_dock().read(cx).is_open());
5468            // The bottom dock is sized based on the panel's default size,
5469            // since the panel orientation changed from vertical to horizontal.
5470            let bottom_dock = workspace.bottom_dock();
5471            assert_eq!(
5472                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5473                panel_1.size(cx),
5474            );
5475            // Close bottom dock and move panel_1 back to the left.
5476            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5477            panel_1.set_position(DockPosition::Left, cx);
5478        });
5479
5480        // Emit activated event on panel 1
5481        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5482
5483        // Now the left dock is open and panel_1 is active and focused.
5484        workspace.update(cx, |workspace, cx| {
5485            let left_dock = workspace.left_dock();
5486            assert!(left_dock.read(cx).is_open());
5487            assert_eq!(
5488                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5489                panel_1.panel_id(),
5490            );
5491            assert!(panel_1.focus_handle(cx).is_focused(cx));
5492        });
5493
5494        // Emit closed event on panel 2, which is not active
5495        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5496
5497        // Wo don't close the left dock, because panel_2 wasn't the active panel
5498        workspace.update(cx, |workspace, cx| {
5499            let left_dock = workspace.left_dock();
5500            assert!(left_dock.read(cx).is_open());
5501            assert_eq!(
5502                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5503                panel_1.panel_id(),
5504            );
5505        });
5506
5507        // Emitting a ZoomIn event shows the panel as zoomed.
5508        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5509        workspace.update(cx, |workspace, _| {
5510            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5511            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5512        });
5513
5514        // Move panel to another dock while it is zoomed
5515        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5516        workspace.update(cx, |workspace, _| {
5517            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5518
5519            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5520        });
5521
5522        // This is a helper for getting a:
5523        // - valid focus on an element,
5524        // - that isn't a part of the panes and panels system of the Workspace,
5525        // - and doesn't trigger the 'on_focus_lost' API.
5526        let focus_other_view = {
5527            let workspace = workspace.clone();
5528            move |cx: &mut VisualTestContext| {
5529                workspace.update(cx, |workspace, cx| {
5530                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5531                        workspace.toggle_modal(cx, TestModal::new);
5532                        workspace.toggle_modal(cx, TestModal::new);
5533                    } else {
5534                        workspace.toggle_modal(cx, TestModal::new);
5535                    }
5536                })
5537            }
5538        };
5539
5540        // If focus is transferred to another view that's not a panel or another pane, we still show
5541        // the panel as zoomed.
5542        focus_other_view(cx);
5543        workspace.update(cx, |workspace, _| {
5544            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5545            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5546        });
5547
5548        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5549        workspace.update(cx, |_, cx| cx.focus_self());
5550        workspace.update(cx, |workspace, _| {
5551            assert_eq!(workspace.zoomed, None);
5552            assert_eq!(workspace.zoomed_position, None);
5553        });
5554
5555        // If focus is transferred again to another view that's not a panel or a pane, we won't
5556        // show the panel as zoomed because it wasn't zoomed before.
5557        focus_other_view(cx);
5558        workspace.update(cx, |workspace, _| {
5559            assert_eq!(workspace.zoomed, None);
5560            assert_eq!(workspace.zoomed_position, None);
5561        });
5562
5563        // When the panel is activated, it is zoomed again.
5564        cx.dispatch_action(ToggleRightDock);
5565        workspace.update(cx, |workspace, _| {
5566            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5567            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5568        });
5569
5570        // Emitting a ZoomOut event unzooms the panel.
5571        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5572        workspace.update(cx, |workspace, _| {
5573            assert_eq!(workspace.zoomed, None);
5574            assert_eq!(workspace.zoomed_position, None);
5575        });
5576
5577        // Emit closed event on panel 1, which is active
5578        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5579
5580        // Now the left dock is closed, because panel_1 was the active panel
5581        workspace.update(cx, |workspace, cx| {
5582            let right_dock = workspace.right_dock();
5583            assert!(!right_dock.read(cx).is_open());
5584        });
5585    }
5586
5587    pub fn init_test(cx: &mut TestAppContext) {
5588        cx.update(|cx| {
5589            let settings_store = SettingsStore::test(cx);
5590            cx.set_global(settings_store);
5591            theme::init(theme::LoadThemes::JustBase, cx);
5592            language::init(cx);
5593            crate::init_settings(cx);
5594            Project::init_settings(cx);
5595        });
5596    }
5597}