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