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