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