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