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