workspace.rs

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