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