workspace.rs

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