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