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    ]
 126);
 127
 128#[derive(Clone, PartialEq)]
 129pub struct OpenPaths {
 130    pub paths: Vec<PathBuf>,
 131}
 132
 133#[derive(Clone, Deserialize, PartialEq)]
 134pub struct ActivatePane(pub usize);
 135
 136#[derive(Clone, Deserialize, PartialEq)]
 137pub struct ActivatePaneInDirection(pub SplitDirection);
 138
 139#[derive(Clone, Deserialize, PartialEq)]
 140pub struct SwapPaneInDirection(pub SplitDirection);
 141
 142#[derive(Clone, Deserialize, PartialEq)]
 143pub struct NewFileInDirection(pub SplitDirection);
 144
 145#[derive(Clone, PartialEq, Debug, Deserialize)]
 146#[serde(rename_all = "camelCase")]
 147pub struct SaveAll {
 148    pub save_intent: Option<SaveIntent>,
 149}
 150
 151#[derive(Clone, PartialEq, Debug, Deserialize)]
 152#[serde(rename_all = "camelCase")]
 153pub struct Save {
 154    pub save_intent: Option<SaveIntent>,
 155}
 156
 157#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 158#[serde(rename_all = "camelCase")]
 159pub struct CloseAllItemsAndPanes {
 160    pub save_intent: Option<SaveIntent>,
 161}
 162
 163#[derive(Clone, Deserialize, PartialEq)]
 164pub struct SendKeystrokes(pub String);
 165
 166impl_actions!(
 167    workspace,
 168    [
 169        ActivatePane,
 170        ActivatePaneInDirection,
 171        CloseAllItemsAndPanes,
 172        NewFileInDirection,
 173        OpenTerminal,
 174        Save,
 175        SaveAll,
 176        SwapPaneInDirection,
 177        SendKeystrokes,
 178    ]
 179);
 180
 181#[derive(Deserialize)]
 182pub struct Toast {
 183    id: usize,
 184    msg: Cow<'static, str>,
 185    #[serde(skip)]
 186    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 187}
 188
 189impl Toast {
 190    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 191        Toast {
 192            id,
 193            msg: msg.into(),
 194            on_click: None,
 195        }
 196    }
 197
 198    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 199    where
 200        M: Into<Cow<'static, str>>,
 201        F: Fn(&mut WindowContext) + 'static,
 202    {
 203        self.on_click = Some((message.into(), Arc::new(on_click)));
 204        self
 205    }
 206}
 207
 208impl PartialEq for Toast {
 209    fn eq(&self, other: &Self) -> bool {
 210        self.id == other.id
 211            && self.msg == other.msg
 212            && self.on_click.is_some() == other.on_click.is_some()
 213    }
 214}
 215
 216impl Clone for Toast {
 217    fn clone(&self) -> Self {
 218        Toast {
 219            id: self.id,
 220            msg: self.msg.to_owned(),
 221            on_click: self.on_click.clone(),
 222        }
 223    }
 224}
 225
 226#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 227pub struct OpenTerminal {
 228    pub working_directory: PathBuf,
 229}
 230
 231pub type WorkspaceId = i64;
 232
 233pub fn init_settings(cx: &mut AppContext) {
 234    WorkspaceSettings::register(cx);
 235    ItemSettings::register(cx);
 236}
 237
 238pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 239    init_settings(cx);
 240    notifications::init(cx);
 241
 242    cx.on_action(Workspace::close_global);
 243    cx.on_action(restart);
 244
 245    cx.on_action({
 246        let app_state = Arc::downgrade(&app_state);
 247        move |_: &Open, cx: &mut AppContext| {
 248            let paths = cx.prompt_for_paths(PathPromptOptions {
 249                files: true,
 250                directories: true,
 251                multiple: true,
 252            });
 253
 254            if let Some(app_state) = app_state.upgrade() {
 255                cx.spawn(move |cx| async move {
 256                    if let Some(paths) = paths.await.log_err().flatten() {
 257                        cx.update(|cx| {
 258                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 259                        })
 260                        .ok();
 261                    }
 262                })
 263                .detach();
 264            }
 265        }
 266    });
 267}
 268
 269#[derive(Clone, Default, Deref, DerefMut)]
 270struct ProjectItemBuilders(
 271    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>,
 272);
 273
 274impl Global for ProjectItemBuilders {}
 275
 276pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 277    let builders = cx.default_global::<ProjectItemBuilders>();
 278    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 279        let item = model.downcast::<I::Item>().unwrap();
 280        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 281    });
 282}
 283
 284type FollowableItemBuilder = fn(
 285    View<Pane>,
 286    View<Workspace>,
 287    ViewId,
 288    &mut Option<proto::view::Variant>,
 289    &mut WindowContext,
 290) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 291
 292#[derive(Default, Deref, DerefMut)]
 293struct FollowableItemBuilders(
 294    HashMap<
 295        TypeId,
 296        (
 297            FollowableItemBuilder,
 298            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 299        ),
 300    >,
 301);
 302
 303impl Global for FollowableItemBuilders {}
 304
 305pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 306    let builders = cx.default_global::<FollowableItemBuilders>();
 307    builders.insert(
 308        TypeId::of::<I>(),
 309        (
 310            |pane, workspace, id, state, cx| {
 311                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 312                    cx.foreground_executor()
 313                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 314                })
 315            },
 316            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 317        ),
 318    );
 319}
 320
 321#[derive(Default, Deref, DerefMut)]
 322struct ItemDeserializers(
 323    HashMap<
 324        Arc<str>,
 325        fn(
 326            Model<Project>,
 327            WeakView<Workspace>,
 328            WorkspaceId,
 329            ItemId,
 330            &mut ViewContext<Pane>,
 331        ) -> Task<Result<Box<dyn ItemHandle>>>,
 332    >,
 333);
 334
 335impl Global for ItemDeserializers {}
 336
 337pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 338    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 339        let deserializers = cx.default_global::<ItemDeserializers>();
 340        deserializers.insert(
 341            Arc::from(serialized_item_kind),
 342            |project, workspace, workspace_id, item_id, cx| {
 343                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 344                cx.foreground_executor()
 345                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 346            },
 347        );
 348    }
 349}
 350
 351pub struct AppState {
 352    pub languages: Arc<LanguageRegistry>,
 353    pub client: Arc<Client>,
 354    pub user_store: Model<UserStore>,
 355    pub workspace_store: Model<WorkspaceStore>,
 356    pub fs: Arc<dyn fs::Fs>,
 357    pub build_window_options:
 358        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 359    pub node_runtime: Arc<dyn NodeRuntime>,
 360}
 361
 362struct GlobalAppState(Weak<AppState>);
 363
 364impl Global for GlobalAppState {}
 365
 366pub struct WorkspaceStore {
 367    workspaces: HashSet<WindowHandle<Workspace>>,
 368    client: Arc<Client>,
 369    _subscriptions: Vec<client::Subscription>,
 370}
 371
 372#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 373struct Follower {
 374    project_id: Option<u64>,
 375    peer_id: PeerId,
 376}
 377
 378impl AppState {
 379    pub fn global(cx: &AppContext) -> Weak<Self> {
 380        cx.global::<GlobalAppState>().0.clone()
 381    }
 382    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 383        cx.try_global::<GlobalAppState>()
 384            .map(|state| state.0.clone())
 385    }
 386    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 387        cx.set_global(GlobalAppState(state));
 388    }
 389
 390    #[cfg(any(test, feature = "test-support"))]
 391    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 392        use node_runtime::FakeNodeRuntime;
 393        use settings::SettingsStore;
 394
 395        if !cx.has_global::<SettingsStore>() {
 396            let settings_store = SettingsStore::test(cx);
 397            cx.set_global(settings_store);
 398        }
 399
 400        let fs = fs::FakeFs::new(cx.background_executor().clone());
 401        let languages = Arc::new(LanguageRegistry::test());
 402        let clock = Arc::new(clock::FakeSystemClock::default());
 403        let http_client = util::http::FakeHttpClient::with_404_response();
 404        let client = Client::new(clock, 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    }
3563
3564    #[cfg(any(test, feature = "test-support"))]
3565    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3566        use node_runtime::FakeNodeRuntime;
3567
3568        let client = project.read(cx).client();
3569        let user_store = project.read(cx).user_store();
3570
3571        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3572        cx.activate_window();
3573        let app_state = Arc::new(AppState {
3574            languages: project.read(cx).languages().clone(),
3575            workspace_store,
3576            client,
3577            user_store,
3578            fs: project.read(cx).fs().clone(),
3579            build_window_options: |_, _, _| Default::default(),
3580            node_runtime: FakeNodeRuntime::new(),
3581        });
3582        let workspace = Self::new(0, project, app_state, cx);
3583        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3584        workspace
3585    }
3586
3587    pub fn register_action<A: Action>(
3588        &mut self,
3589        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3590    ) -> &mut Self {
3591        let callback = Arc::new(callback);
3592
3593        self.workspace_actions.push(Box::new(move |div, cx| {
3594            let callback = callback.clone();
3595            div.on_action(
3596                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3597            )
3598        }));
3599        self
3600    }
3601
3602    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3603        let mut div = div
3604            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3605            .on_action(cx.listener(Self::close_all_items_and_panes))
3606            .on_action(cx.listener(Self::add_folder_to_project))
3607            .on_action(cx.listener(Self::save_all))
3608            .on_action(cx.listener(Self::open));
3609        for action in self.workspace_actions.iter() {
3610            div = (action)(div, cx)
3611        }
3612        div
3613    }
3614
3615    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3616        self.modal_layer.read(cx).has_active_modal()
3617    }
3618
3619    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3620        self.modal_layer.read(cx).active_modal()
3621    }
3622
3623    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3624    where
3625        B: FnOnce(&mut ViewContext<V>) -> V,
3626    {
3627        self.modal_layer
3628            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3629    }
3630}
3631
3632fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3633    let display_origin = cx
3634        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3635        .ok()??;
3636    ZED_WINDOW_POSITION
3637        .zip(*ZED_WINDOW_SIZE)
3638        .map(|(position, size)| {
3639            WindowBounds::Fixed(Bounds {
3640                origin: display_origin + position,
3641                size,
3642            })
3643        })
3644}
3645
3646fn open_items(
3647    serialized_workspace: Option<SerializedWorkspace>,
3648    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3649    app_state: Arc<AppState>,
3650    cx: &mut ViewContext<Workspace>,
3651) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3652    let restored_items = serialized_workspace.map(|serialized_workspace| {
3653        Workspace::load_workspace(
3654            serialized_workspace,
3655            project_paths_to_open
3656                .iter()
3657                .map(|(_, project_path)| project_path)
3658                .cloned()
3659                .collect(),
3660            cx,
3661        )
3662    });
3663
3664    cx.spawn(|workspace, mut cx| async move {
3665        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3666
3667        if let Some(restored_items) = restored_items {
3668            let restored_items = restored_items.await?;
3669
3670            let restored_project_paths = restored_items
3671                .iter()
3672                .filter_map(|item| {
3673                    cx.update(|cx| item.as_ref()?.project_path(cx))
3674                        .ok()
3675                        .flatten()
3676                })
3677                .collect::<HashSet<_>>();
3678
3679            for restored_item in restored_items {
3680                opened_items.push(restored_item.map(Ok));
3681            }
3682
3683            project_paths_to_open
3684                .iter_mut()
3685                .for_each(|(_, project_path)| {
3686                    if let Some(project_path_to_open) = project_path {
3687                        if restored_project_paths.contains(project_path_to_open) {
3688                            *project_path = None;
3689                        }
3690                    }
3691                });
3692        } else {
3693            for _ in 0..project_paths_to_open.len() {
3694                opened_items.push(None);
3695            }
3696        }
3697        assert!(opened_items.len() == project_paths_to_open.len());
3698
3699        let tasks =
3700            project_paths_to_open
3701                .into_iter()
3702                .enumerate()
3703                .map(|(i, (abs_path, project_path))| {
3704                    let workspace = workspace.clone();
3705                    cx.spawn(|mut cx| {
3706                        let fs = app_state.fs.clone();
3707                        async move {
3708                            let file_project_path = project_path?;
3709                            if fs.is_file(&abs_path).await {
3710                                Some((
3711                                    i,
3712                                    workspace
3713                                        .update(&mut cx, |workspace, cx| {
3714                                            workspace.open_path(file_project_path, None, true, cx)
3715                                        })
3716                                        .log_err()?
3717                                        .await,
3718                                ))
3719                            } else {
3720                                None
3721                            }
3722                        }
3723                    })
3724                });
3725
3726        let tasks = tasks.collect::<Vec<_>>();
3727
3728        let tasks = futures::future::join_all(tasks.into_iter());
3729        for maybe_opened_path in tasks.await.into_iter() {
3730            if let Some((i, path_open_result)) = maybe_opened_path {
3731                opened_items[i] = Some(path_open_result);
3732            }
3733        }
3734
3735        Ok(opened_items)
3736    })
3737}
3738
3739enum ActivateInDirectionTarget {
3740    Pane(View<Pane>),
3741    Dock(View<Dock>),
3742}
3743
3744fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3745    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3746
3747    workspace
3748        .update(cx, |workspace, cx| {
3749            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3750                workspace.show_notification_once(0, cx, |cx| {
3751                    cx.new_view(|_| {
3752                        MessageNotification::new("Failed to load the database file.")
3753                            .with_click_message("Click to let us know about this error")
3754                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3755                    })
3756                });
3757            }
3758        })
3759        .log_err();
3760}
3761
3762impl FocusableView for Workspace {
3763    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3764        self.active_pane.focus_handle(cx)
3765    }
3766}
3767
3768#[derive(Clone, Render)]
3769struct DraggedDock(DockPosition);
3770
3771impl Render for Workspace {
3772    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3773        let mut context = KeyContext::default();
3774        context.add("Workspace");
3775
3776        let (ui_font, ui_font_size) = {
3777            let theme_settings = ThemeSettings::get_global(cx);
3778            (
3779                theme_settings.ui_font.family.clone(),
3780                theme_settings.ui_font_size.clone(),
3781            )
3782        };
3783
3784        let theme = cx.theme().clone();
3785        let colors = theme.colors();
3786        cx.set_rem_size(ui_font_size);
3787
3788        self.actions(div(), cx)
3789            .key_context(context)
3790            .relative()
3791            .size_full()
3792            .flex()
3793            .flex_col()
3794            .font(ui_font)
3795            .gap_0()
3796            .justify_start()
3797            .items_start()
3798            .text_color(colors.text)
3799            .bg(colors.background)
3800            .border()
3801            .border_color(colors.border)
3802            .children(self.titlebar_item.clone())
3803            .child(
3804                div()
3805                    .id("workspace")
3806                    .relative()
3807                    .flex_1()
3808                    .w_full()
3809                    .flex()
3810                    .flex_col()
3811                    .overflow_hidden()
3812                    .border_t()
3813                    .border_b()
3814                    .border_color(colors.border)
3815                    .child(
3816                        canvas({
3817                            let this = cx.view().clone();
3818                            move |bounds, cx| {
3819                                this.update(cx, |this, _cx| {
3820                                    this.bounds = *bounds;
3821                                })
3822                            }
3823                        })
3824                        .absolute()
3825                        .size_full(),
3826                    )
3827                    .on_drag_move(
3828                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3829                            match e.drag(cx).0 {
3830                                DockPosition::Left => {
3831                                    let size = workspace.bounds.left() + e.event.position.x;
3832                                    workspace.left_dock.update(cx, |left_dock, cx| {
3833                                        left_dock.resize_active_panel(Some(size), cx);
3834                                    });
3835                                }
3836                                DockPosition::Right => {
3837                                    let size = workspace.bounds.right() - e.event.position.x;
3838                                    workspace.right_dock.update(cx, |right_dock, cx| {
3839                                        right_dock.resize_active_panel(Some(size), cx);
3840                                    });
3841                                }
3842                                DockPosition::Bottom => {
3843                                    let size = workspace.bounds.bottom() - e.event.position.y;
3844                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3845                                        bottom_dock.resize_active_panel(Some(size), cx);
3846                                    });
3847                                }
3848                            }
3849                        }),
3850                    )
3851                    .child(self.modal_layer.clone())
3852                    .child(
3853                        div()
3854                            .flex()
3855                            .flex_row()
3856                            .h_full()
3857                            // Left Dock
3858                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3859                                || {
3860                                    div()
3861                                        .flex()
3862                                        .flex_none()
3863                                        .overflow_hidden()
3864                                        .child(self.left_dock.clone())
3865                                },
3866                            ))
3867                            // Panes
3868                            .child(
3869                                div()
3870                                    .flex()
3871                                    .flex_col()
3872                                    .flex_1()
3873                                    .overflow_hidden()
3874                                    .child(self.center.render(
3875                                        &self.project,
3876                                        &self.follower_states,
3877                                        self.active_call(),
3878                                        &self.active_pane,
3879                                        self.zoomed.as_ref(),
3880                                        &self.app_state,
3881                                        cx,
3882                                    ))
3883                                    .children(
3884                                        self.zoomed_position
3885                                            .ne(&Some(DockPosition::Bottom))
3886                                            .then(|| self.bottom_dock.clone()),
3887                                    ),
3888                            )
3889                            // Right Dock
3890                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3891                                || {
3892                                    div()
3893                                        .flex()
3894                                        .flex_none()
3895                                        .overflow_hidden()
3896                                        .child(self.right_dock.clone())
3897                                },
3898                            )),
3899                    )
3900                    .children(self.render_notifications(cx))
3901                    .children(self.zoomed.as_ref().and_then(|view| {
3902                        let zoomed_view = view.upgrade()?;
3903                        let div = div()
3904                            .z_index(1)
3905                            .absolute()
3906                            .overflow_hidden()
3907                            .border_color(colors.border)
3908                            .bg(colors.background)
3909                            .child(zoomed_view)
3910                            .inset_0()
3911                            .shadow_lg();
3912
3913                        Some(match self.zoomed_position {
3914                            Some(DockPosition::Left) => div.right_2().border_r(),
3915                            Some(DockPosition::Right) => div.left_2().border_l(),
3916                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3917                            None => div.top_2().bottom_2().left_2().right_2().border(),
3918                        })
3919                    })),
3920            )
3921            .child(self.status_bar.clone())
3922            .children(if self.project.read(cx).is_disconnected() {
3923                Some(DisconnectedOverlay)
3924            } else {
3925                None
3926            })
3927    }
3928}
3929
3930impl WorkspaceStore {
3931    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3932        Self {
3933            workspaces: Default::default(),
3934            _subscriptions: vec![
3935                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3936                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3937            ],
3938            client,
3939        }
3940    }
3941
3942    pub fn update_followers(
3943        &self,
3944        project_id: Option<u64>,
3945        update: proto::update_followers::Variant,
3946        cx: &AppContext,
3947    ) -> Option<()> {
3948        let active_call = ActiveCall::try_global(cx)?;
3949        let room_id = active_call.read(cx).room()?.read(cx).id();
3950        self.client
3951            .send(proto::UpdateFollowers {
3952                room_id,
3953                project_id,
3954                variant: Some(update),
3955            })
3956            .log_err()
3957    }
3958
3959    pub async fn handle_follow(
3960        this: Model<Self>,
3961        envelope: TypedEnvelope<proto::Follow>,
3962        _: Arc<Client>,
3963        mut cx: AsyncAppContext,
3964    ) -> Result<proto::FollowResponse> {
3965        this.update(&mut cx, |this, cx| {
3966            let follower = Follower {
3967                project_id: envelope.payload.project_id,
3968                peer_id: envelope.original_sender_id()?,
3969            };
3970            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3971
3972            let mut response = proto::FollowResponse::default();
3973            this.workspaces.retain(|workspace| {
3974                workspace
3975                    .update(cx, |workspace, cx| {
3976                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3977                        if response.views.is_empty() {
3978                            response.views = handler_response.views;
3979                        } else {
3980                            response.views.extend_from_slice(&handler_response.views);
3981                        }
3982
3983                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3984                            if response.active_view_id.is_none()
3985                                || Some(workspace.project.downgrade()) == active_project
3986                            {
3987                                response.active_view_id = Some(active_view_id);
3988                            }
3989                        }
3990
3991                        if let Some(active_view) = handler_response.active_view.clone() {
3992                            if workspace.project.read(cx).remote_id() == follower.project_id {
3993                                response.active_view = Some(active_view)
3994                            }
3995                        }
3996                    })
3997                    .is_ok()
3998            });
3999
4000            Ok(response)
4001        })?
4002    }
4003
4004    async fn handle_update_followers(
4005        this: Model<Self>,
4006        envelope: TypedEnvelope<proto::UpdateFollowers>,
4007        _: Arc<Client>,
4008        mut cx: AsyncAppContext,
4009    ) -> Result<()> {
4010        let leader_id = envelope.original_sender_id()?;
4011        let update = envelope.payload;
4012
4013        this.update(&mut cx, |this, cx| {
4014            this.workspaces.retain(|workspace| {
4015                workspace
4016                    .update(cx, |workspace, cx| {
4017                        let project_id = workspace.project.read(cx).remote_id();
4018                        if update.project_id != project_id && update.project_id.is_some() {
4019                            return;
4020                        }
4021                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4022                    })
4023                    .is_ok()
4024            });
4025            Ok(())
4026        })?
4027    }
4028}
4029
4030impl ViewId {
4031    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4032        Ok(Self {
4033            creator: message
4034                .creator
4035                .ok_or_else(|| anyhow!("creator is missing"))?,
4036            id: message.id,
4037        })
4038    }
4039
4040    pub(crate) fn to_proto(&self) -> proto::ViewId {
4041        proto::ViewId {
4042            creator: Some(self.creator),
4043            id: self.id,
4044        }
4045    }
4046}
4047
4048pub trait WorkspaceHandle {
4049    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4050}
4051
4052impl WorkspaceHandle for View<Workspace> {
4053    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4054        self.read(cx)
4055            .worktrees(cx)
4056            .flat_map(|worktree| {
4057                let worktree_id = worktree.read(cx).id();
4058                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4059                    worktree_id,
4060                    path: f.path.clone(),
4061                })
4062            })
4063            .collect::<Vec<_>>()
4064    }
4065}
4066
4067impl std::fmt::Debug for OpenPaths {
4068    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4069        f.debug_struct("OpenPaths")
4070            .field("paths", &self.paths)
4071            .finish()
4072    }
4073}
4074
4075pub fn activate_workspace_for_project(
4076    cx: &mut AppContext,
4077    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4078) -> Option<WindowHandle<Workspace>> {
4079    for window in cx.windows() {
4080        let Some(workspace) = window.downcast::<Workspace>() else {
4081            continue;
4082        };
4083
4084        let predicate = workspace
4085            .update(cx, |workspace, cx| {
4086                let project = workspace.project.read(cx);
4087                if predicate(project, cx) {
4088                    cx.activate_window();
4089                    true
4090                } else {
4091                    false
4092                }
4093            })
4094            .log_err()
4095            .unwrap_or(false);
4096
4097        if predicate {
4098            return Some(workspace);
4099        }
4100    }
4101
4102    None
4103}
4104
4105pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4106    DB.last_workspace().await.log_err().flatten()
4107}
4108
4109actions!(collab, [OpenChannelNotes]);
4110
4111async fn join_channel_internal(
4112    channel_id: u64,
4113    app_state: &Arc<AppState>,
4114    requesting_window: Option<WindowHandle<Workspace>>,
4115    active_call: &Model<ActiveCall>,
4116    cx: &mut AsyncAppContext,
4117) -> Result<bool> {
4118    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4119        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4120            return (false, None);
4121        };
4122
4123        let already_in_channel = room.channel_id() == Some(channel_id);
4124        let should_prompt = room.is_sharing_project()
4125            && room.remote_participants().len() > 0
4126            && !already_in_channel;
4127        let open_room = if already_in_channel {
4128            active_call.room().cloned()
4129        } else {
4130            None
4131        };
4132        (should_prompt, open_room)
4133    })?;
4134
4135    if let Some(room) = open_room {
4136        let task = room.update(cx, |room, cx| {
4137            if let Some((project, host)) = room.most_active_project(cx) {
4138                return Some(join_remote_project(project, host, app_state.clone(), cx));
4139            }
4140
4141            None
4142        })?;
4143        if let Some(task) = task {
4144            task.await?;
4145        }
4146        return anyhow::Ok(true);
4147    }
4148
4149    if should_prompt {
4150        if let Some(workspace) = requesting_window {
4151            let answer = workspace
4152                .update(cx, |_, cx| {
4153                    cx.prompt(
4154                        PromptLevel::Warning,
4155                        "Do you want to switch channels?",
4156                        Some("Leaving this call will unshare your current project."),
4157                        &["Yes, Join Channel", "Cancel"],
4158                    )
4159                })?
4160                .await;
4161
4162            if answer == Ok(1) {
4163                return Ok(false);
4164            }
4165        } else {
4166            return Ok(false); // unreachable!() hopefully
4167        }
4168    }
4169
4170    let client = cx.update(|cx| active_call.read(cx).client())?;
4171
4172    let mut client_status = client.status();
4173
4174    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4175    'outer: loop {
4176        let Some(status) = client_status.recv().await else {
4177            return Err(anyhow!("error connecting"));
4178        };
4179
4180        match status {
4181            Status::Connecting
4182            | Status::Authenticating
4183            | Status::Reconnecting
4184            | Status::Reauthenticating => continue,
4185            Status::Connected { .. } => break 'outer,
4186            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4187            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4188            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4189                return Err(ErrorCode::Disconnected.into());
4190            }
4191        }
4192    }
4193
4194    let room = active_call
4195        .update(cx, |active_call, cx| {
4196            active_call.join_channel(channel_id, cx)
4197        })?
4198        .await?;
4199
4200    let Some(room) = room else {
4201        return anyhow::Ok(true);
4202    };
4203
4204    room.update(cx, |room, _| room.room_update_completed())?
4205        .await;
4206
4207    let task = room.update(cx, |room, cx| {
4208        if let Some((project, host)) = room.most_active_project(cx) {
4209            return Some(join_remote_project(project, host, app_state.clone(), cx));
4210        }
4211
4212        // if you are the first to join a channel, share your project
4213        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4214            if let Some(workspace) = requesting_window {
4215                let project = workspace.update(cx, |workspace, cx| {
4216                    if !CallSettings::get_global(cx).share_on_join {
4217                        return None;
4218                    }
4219                    let project = workspace.project.read(cx);
4220                    if project.is_local()
4221                        && project.visible_worktrees(cx).any(|tree| {
4222                            tree.read(cx)
4223                                .root_entry()
4224                                .map_or(false, |entry| entry.is_dir())
4225                        })
4226                    {
4227                        Some(workspace.project.clone())
4228                    } else {
4229                        None
4230                    }
4231                });
4232                if let Ok(Some(project)) = project {
4233                    return Some(cx.spawn(|room, mut cx| async move {
4234                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4235                            .await?;
4236                        Ok(())
4237                    }));
4238                }
4239            }
4240        }
4241
4242        None
4243    })?;
4244    if let Some(task) = task {
4245        task.await?;
4246        return anyhow::Ok(true);
4247    }
4248    anyhow::Ok(false)
4249}
4250
4251pub fn join_channel(
4252    channel_id: u64,
4253    app_state: Arc<AppState>,
4254    requesting_window: Option<WindowHandle<Workspace>>,
4255    cx: &mut AppContext,
4256) -> Task<Result<()>> {
4257    let active_call = ActiveCall::global(cx);
4258    cx.spawn(|mut cx| async move {
4259        let result = join_channel_internal(
4260            channel_id,
4261            &app_state,
4262            requesting_window,
4263            &active_call,
4264            &mut cx,
4265        )
4266            .await;
4267
4268        // join channel succeeded, and opened a window
4269        if matches!(result, Ok(true)) {
4270            return anyhow::Ok(());
4271        }
4272
4273        // find an existing workspace to focus and show call controls
4274        let mut active_window =
4275            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4276        if active_window.is_none() {
4277            // no open workspaces, make one to show the error in (blergh)
4278            let (window_handle, _) = cx
4279                .update(|cx| {
4280                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4281                })?
4282                .await?;
4283
4284            if result.is_ok() {
4285                cx.update(|cx| {
4286                    cx.dispatch_action(&OpenChannelNotes);
4287                }).log_err();
4288            }
4289
4290            active_window = Some(window_handle);
4291        }
4292
4293        if let Err(err) = result {
4294            log::error!("failed to join channel: {}", err);
4295            if let Some(active_window) = active_window {
4296                active_window
4297                    .update(&mut cx, |_, cx| {
4298                        let detail: SharedString = match err.error_code() {
4299                            ErrorCode::SignedOut => {
4300                                "Please sign in to continue.".into()
4301                            }
4302                            ErrorCode::UpgradeRequired => {
4303                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4304                            }
4305                            ErrorCode::NoSuchChannel => {
4306                                "No matching channel was found. Please check the link and try again.".into()
4307                            }
4308                            ErrorCode::Forbidden => {
4309                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4310                            }
4311                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4312                            _ => format!("{}\n\nPlease try again.", err).into(),
4313                        };
4314                        cx.prompt(
4315                            PromptLevel::Critical,
4316                            "Failed to join channel",
4317                            Some(&detail),
4318                            &["Ok"],
4319                        )
4320                    })?
4321                    .await
4322                    .ok();
4323            }
4324        }
4325
4326        // return ok, we showed the error to the user.
4327        return anyhow::Ok(());
4328    })
4329}
4330
4331pub async fn get_any_active_workspace(
4332    app_state: Arc<AppState>,
4333    mut cx: AsyncAppContext,
4334) -> anyhow::Result<WindowHandle<Workspace>> {
4335    // find an existing workspace to focus and show call controls
4336    let active_window = activate_any_workspace_window(&mut cx);
4337    if active_window.is_none() {
4338        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4339            .await?;
4340    }
4341    activate_any_workspace_window(&mut cx).context("could not open zed")
4342}
4343
4344fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4345    cx.update(|cx| {
4346        for window in cx.windows() {
4347            if let Some(workspace_window) = window.downcast::<Workspace>() {
4348                workspace_window
4349                    .update(cx, |_, cx| cx.activate_window())
4350                    .ok();
4351                return Some(workspace_window);
4352            }
4353        }
4354        None
4355    })
4356    .ok()
4357    .flatten()
4358}
4359
4360#[allow(clippy::type_complexity)]
4361pub fn open_paths(
4362    abs_paths: &[PathBuf],
4363    app_state: &Arc<AppState>,
4364    requesting_window: Option<WindowHandle<Workspace>>,
4365    cx: &mut AppContext,
4366) -> Task<
4367    anyhow::Result<(
4368        WindowHandle<Workspace>,
4369        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4370    )>,
4371> {
4372    let app_state = app_state.clone();
4373    let abs_paths = abs_paths.to_vec();
4374    // Open paths in existing workspace if possible
4375    let existing = activate_workspace_for_project(cx, {
4376        let abs_paths = abs_paths.clone();
4377        move |project, cx| project.contains_paths(&abs_paths, cx)
4378    });
4379    cx.spawn(move |mut cx| async move {
4380        if let Some(existing) = existing {
4381            Ok((
4382                existing.clone(),
4383                existing
4384                    .update(&mut cx, |workspace, cx| {
4385                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4386                    })?
4387                    .await,
4388            ))
4389        } else {
4390            cx.update(move |cx| {
4391                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4392            })?
4393            .await
4394        }
4395    })
4396}
4397
4398pub fn open_new(
4399    app_state: &Arc<AppState>,
4400    cx: &mut AppContext,
4401    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4402) -> Task<()> {
4403    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4404    cx.spawn(|mut cx| async move {
4405        if let Some((workspace, opened_paths)) = task.await.log_err() {
4406            workspace
4407                .update(&mut cx, |workspace, cx| {
4408                    if opened_paths.is_empty() {
4409                        init(workspace, cx)
4410                    }
4411                })
4412                .log_err();
4413        }
4414    })
4415}
4416
4417pub fn create_and_open_local_file(
4418    path: &'static Path,
4419    cx: &mut ViewContext<Workspace>,
4420    default_content: impl 'static + Send + FnOnce() -> Rope,
4421) -> Task<Result<Box<dyn ItemHandle>>> {
4422    cx.spawn(|workspace, mut cx| async move {
4423        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4424        if !fs.is_file(path).await {
4425            fs.create_file(path, Default::default()).await?;
4426            fs.save(path, &default_content(), Default::default())
4427                .await?;
4428        }
4429
4430        let mut items = workspace
4431            .update(&mut cx, |workspace, cx| {
4432                workspace.with_local_workspace(cx, |workspace, cx| {
4433                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4434                })
4435            })?
4436            .await?
4437            .await;
4438
4439        let item = items.pop().flatten();
4440        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4441    })
4442}
4443
4444pub fn join_remote_project(
4445    project_id: u64,
4446    follow_user_id: u64,
4447    app_state: Arc<AppState>,
4448    cx: &mut AppContext,
4449) -> Task<Result<()>> {
4450    let windows = cx.windows();
4451    cx.spawn(|mut cx| async move {
4452        let existing_workspace = windows.into_iter().find_map(|window| {
4453            window.downcast::<Workspace>().and_then(|window| {
4454                window
4455                    .update(&mut cx, |workspace, cx| {
4456                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4457                            Some(window)
4458                        } else {
4459                            None
4460                        }
4461                    })
4462                    .unwrap_or(None)
4463            })
4464        });
4465
4466        let workspace = if let Some(existing_workspace) = existing_workspace {
4467            existing_workspace
4468        } else {
4469            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4470            let room = active_call
4471                .read_with(&cx, |call, _| call.room().cloned())?
4472                .ok_or_else(|| anyhow!("not in a call"))?;
4473            let project = room
4474                .update(&mut cx, |room, cx| {
4475                    room.join_project(
4476                        project_id,
4477                        app_state.languages.clone(),
4478                        app_state.fs.clone(),
4479                        cx,
4480                    )
4481                })?
4482                .await?;
4483
4484            let window_bounds_override = window_bounds_env_override(&cx);
4485            cx.update(|cx| {
4486                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4487                cx.open_window(options, |cx| {
4488                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4489                })
4490            })?
4491        };
4492
4493        workspace.update(&mut cx, |workspace, cx| {
4494            cx.activate(true);
4495            cx.activate_window();
4496
4497            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4498                let follow_peer_id = room
4499                    .read(cx)
4500                    .remote_participants()
4501                    .iter()
4502                    .find(|(_, participant)| participant.user.id == follow_user_id)
4503                    .map(|(_, p)| p.peer_id)
4504                    .or_else(|| {
4505                        // If we couldn't follow the given user, follow the host instead.
4506                        let collaborator = workspace
4507                            .project()
4508                            .read(cx)
4509                            .collaborators()
4510                            .values()
4511                            .find(|collaborator| collaborator.replica_id == 0)?;
4512                        Some(collaborator.peer_id)
4513                    });
4514
4515                if let Some(follow_peer_id) = follow_peer_id {
4516                    workspace.follow(follow_peer_id, cx);
4517                }
4518            }
4519        })?;
4520
4521        anyhow::Ok(())
4522    })
4523}
4524
4525pub fn restart(_: &Restart, cx: &mut AppContext) {
4526    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4527    let mut workspace_windows = cx
4528        .windows()
4529        .into_iter()
4530        .filter_map(|window| window.downcast::<Workspace>())
4531        .collect::<Vec<_>>();
4532
4533    // If multiple windows have unsaved changes, and need a save prompt,
4534    // prompt in the active window before switching to a different window.
4535    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4536
4537    let mut prompt = None;
4538    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4539        prompt = window
4540            .update(cx, |_, cx| {
4541                cx.prompt(
4542                    PromptLevel::Info,
4543                    "Are you sure you want to restart?",
4544                    None,
4545                    &["Restart", "Cancel"],
4546                )
4547            })
4548            .ok();
4549    }
4550
4551    cx.spawn(|mut cx| async move {
4552        if let Some(prompt) = prompt {
4553            let answer = prompt.await?;
4554            if answer != 0 {
4555                return Ok(());
4556            }
4557        }
4558
4559        // If the user cancels any save prompt, then keep the app open.
4560        for window in workspace_windows {
4561            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4562                workspace.prepare_to_close(true, cx)
4563            }) {
4564                if !should_close.await? {
4565                    return Ok(());
4566                }
4567            }
4568        }
4569
4570        cx.update(|cx| cx.restart())
4571    })
4572    .detach_and_log_err(cx);
4573}
4574
4575fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4576    let mut parts = value.split(',');
4577    let x: usize = parts.next()?.parse().ok()?;
4578    let y: usize = parts.next()?.parse().ok()?;
4579    Some(point((x as f64).into(), (y as f64).into()))
4580}
4581
4582fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4583    let mut parts = value.split(',');
4584    let width: usize = parts.next()?.parse().ok()?;
4585    let height: usize = parts.next()?.parse().ok()?;
4586    Some(size((width as f64).into(), (height as f64).into()))
4587}
4588
4589pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4590    (1.75 * cx.rem_size()).max(px(32.))
4591}
4592
4593struct DisconnectedOverlay;
4594
4595impl Element for DisconnectedOverlay {
4596    type State = AnyElement;
4597
4598    fn request_layout(
4599        &mut self,
4600        _: Option<Self::State>,
4601        cx: &mut ElementContext,
4602    ) -> (LayoutId, Self::State) {
4603        let mut background = cx.theme().colors().elevated_surface_background;
4604        background.fade_out(0.2);
4605        let mut overlay = div()
4606            .bg(background)
4607            .absolute()
4608            .left_0()
4609            .top(titlebar_height(cx))
4610            .size_full()
4611            .flex()
4612            .items_center()
4613            .justify_center()
4614            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4615            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4616            .child(Label::new(
4617                "Your connection to the remote project has been lost.",
4618            ))
4619            .into_any();
4620        (overlay.request_layout(cx), overlay)
4621    }
4622
4623    fn paint(
4624        &mut self,
4625        bounds: Bounds<Pixels>,
4626        overlay: &mut Self::State,
4627        cx: &mut ElementContext,
4628    ) {
4629        cx.with_z_index(u16::MAX, |cx| {
4630            cx.add_opaque_layer(bounds);
4631            overlay.paint(cx);
4632        })
4633    }
4634}
4635
4636impl IntoElement for DisconnectedOverlay {
4637    type Element = Self;
4638
4639    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4640        None
4641    }
4642
4643    fn into_element(self) -> Self::Element {
4644        self
4645    }
4646}
4647
4648#[cfg(test)]
4649mod tests {
4650    use std::{cell::RefCell, rc::Rc};
4651
4652    use super::*;
4653    use crate::{
4654        dock::{test::TestPanel, PanelEvent},
4655        item::{
4656            test::{TestItem, TestProjectItem},
4657            ItemEvent,
4658        },
4659    };
4660    use fs::FakeFs;
4661    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4662    use project::{Project, ProjectEntryId};
4663    use serde_json::json;
4664    use settings::SettingsStore;
4665
4666    #[gpui::test]
4667    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4668        init_test(cx);
4669
4670        let fs = FakeFs::new(cx.executor());
4671        let project = Project::test(fs, [], cx).await;
4672        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4673
4674        // Adding an item with no ambiguity renders the tab without detail.
4675        let item1 = cx.new_view(|cx| {
4676            let mut item = TestItem::new(cx);
4677            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4678            item
4679        });
4680        workspace.update(cx, |workspace, cx| {
4681            workspace.add_item(Box::new(item1.clone()), cx);
4682        });
4683        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4684
4685        // Adding an item that creates ambiguity increases the level of detail on
4686        // both tabs.
4687        let item2 = cx.new_view(|cx| {
4688            let mut item = TestItem::new(cx);
4689            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4690            item
4691        });
4692        workspace.update(cx, |workspace, cx| {
4693            workspace.add_item(Box::new(item2.clone()), cx);
4694        });
4695        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4696        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4697
4698        // Adding an item that creates ambiguity increases the level of detail only
4699        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4700        // we stop at the highest detail available.
4701        let item3 = cx.new_view(|cx| {
4702            let mut item = TestItem::new(cx);
4703            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4704            item
4705        });
4706        workspace.update(cx, |workspace, cx| {
4707            workspace.add_item(Box::new(item3.clone()), cx);
4708        });
4709        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4710        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4711        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4712    }
4713
4714    #[gpui::test]
4715    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4716        init_test(cx);
4717
4718        let fs = FakeFs::new(cx.executor());
4719        fs.insert_tree(
4720            "/root1",
4721            json!({
4722                "one.txt": "",
4723                "two.txt": "",
4724            }),
4725        )
4726        .await;
4727        fs.insert_tree(
4728            "/root2",
4729            json!({
4730                "three.txt": "",
4731            }),
4732        )
4733        .await;
4734
4735        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4736        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4737        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4738        let worktree_id = project.update(cx, |project, cx| {
4739            project.worktrees().next().unwrap().read(cx).id()
4740        });
4741
4742        let item1 = cx.new_view(|cx| {
4743            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4744        });
4745        let item2 = cx.new_view(|cx| {
4746            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4747        });
4748
4749        // Add an item to an empty pane
4750        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4751        project.update(cx, |project, cx| {
4752            assert_eq!(
4753                project.active_entry(),
4754                project
4755                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4756                    .map(|e| e.id)
4757            );
4758        });
4759        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4760
4761        // Add a second item to a non-empty pane
4762        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4763        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4764        project.update(cx, |project, cx| {
4765            assert_eq!(
4766                project.active_entry(),
4767                project
4768                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4769                    .map(|e| e.id)
4770            );
4771        });
4772
4773        // Close the active item
4774        pane.update(cx, |pane, cx| {
4775            pane.close_active_item(&Default::default(), cx).unwrap()
4776        })
4777        .await
4778        .unwrap();
4779        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4780        project.update(cx, |project, cx| {
4781            assert_eq!(
4782                project.active_entry(),
4783                project
4784                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4785                    .map(|e| e.id)
4786            );
4787        });
4788
4789        // Add a project folder
4790        project
4791            .update(cx, |project, cx| {
4792                project.find_or_create_local_worktree("/root2", true, cx)
4793            })
4794            .await
4795            .unwrap();
4796        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4797
4798        // Remove a project folder
4799        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4800        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4801    }
4802
4803    #[gpui::test]
4804    async fn test_close_window(cx: &mut TestAppContext) {
4805        init_test(cx);
4806
4807        let fs = FakeFs::new(cx.executor());
4808        fs.insert_tree("/root", json!({ "one": "" })).await;
4809
4810        let project = Project::test(fs, ["root".as_ref()], cx).await;
4811        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4812
4813        // When there are no dirty items, there's nothing to do.
4814        let item1 = cx.new_view(|cx| TestItem::new(cx));
4815        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4816        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4817        assert!(task.await.unwrap());
4818
4819        // When there are dirty untitled items, prompt to save each one. If the user
4820        // cancels any prompt, then abort.
4821        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4822        let item3 = cx.new_view(|cx| {
4823            TestItem::new(cx)
4824                .with_dirty(true)
4825                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4826        });
4827        workspace.update(cx, |w, cx| {
4828            w.add_item(Box::new(item2.clone()), cx);
4829            w.add_item(Box::new(item3.clone()), cx);
4830        });
4831        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4832        cx.executor().run_until_parked();
4833        cx.simulate_prompt_answer(2); // cancel save all
4834        cx.executor().run_until_parked();
4835        cx.simulate_prompt_answer(2); // cancel save all
4836        cx.executor().run_until_parked();
4837        assert!(!cx.has_pending_prompt());
4838        assert!(!task.await.unwrap());
4839    }
4840
4841    #[gpui::test]
4842    async fn test_close_pane_items(cx: &mut TestAppContext) {
4843        init_test(cx);
4844
4845        let fs = FakeFs::new(cx.executor());
4846
4847        let project = Project::test(fs, None, cx).await;
4848        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4849
4850        let item1 = cx.new_view(|cx| {
4851            TestItem::new(cx)
4852                .with_dirty(true)
4853                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4854        });
4855        let item2 = cx.new_view(|cx| {
4856            TestItem::new(cx)
4857                .with_dirty(true)
4858                .with_conflict(true)
4859                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4860        });
4861        let item3 = cx.new_view(|cx| {
4862            TestItem::new(cx)
4863                .with_dirty(true)
4864                .with_conflict(true)
4865                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4866        });
4867        let item4 = cx.new_view(|cx| {
4868            TestItem::new(cx)
4869                .with_dirty(true)
4870                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4871        });
4872        let pane = workspace.update(cx, |workspace, cx| {
4873            workspace.add_item(Box::new(item1.clone()), cx);
4874            workspace.add_item(Box::new(item2.clone()), cx);
4875            workspace.add_item(Box::new(item3.clone()), cx);
4876            workspace.add_item(Box::new(item4.clone()), cx);
4877            workspace.active_pane().clone()
4878        });
4879
4880        let close_items = pane.update(cx, |pane, cx| {
4881            pane.activate_item(1, true, true, cx);
4882            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4883            let item1_id = item1.item_id();
4884            let item3_id = item3.item_id();
4885            let item4_id = item4.item_id();
4886            pane.close_items(cx, SaveIntent::Close, move |id| {
4887                [item1_id, item3_id, item4_id].contains(&id)
4888            })
4889        });
4890        cx.executor().run_until_parked();
4891
4892        assert!(cx.has_pending_prompt());
4893        // Ignore "Save all" prompt
4894        cx.simulate_prompt_answer(2);
4895        cx.executor().run_until_parked();
4896        // There's a prompt to save item 1.
4897        pane.update(cx, |pane, _| {
4898            assert_eq!(pane.items_len(), 4);
4899            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4900        });
4901        // Confirm saving item 1.
4902        cx.simulate_prompt_answer(0);
4903        cx.executor().run_until_parked();
4904
4905        // Item 1 is saved. There's a prompt to save item 3.
4906        pane.update(cx, |pane, cx| {
4907            assert_eq!(item1.read(cx).save_count, 1);
4908            assert_eq!(item1.read(cx).save_as_count, 0);
4909            assert_eq!(item1.read(cx).reload_count, 0);
4910            assert_eq!(pane.items_len(), 3);
4911            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4912        });
4913        assert!(cx.has_pending_prompt());
4914
4915        // Cancel saving item 3.
4916        cx.simulate_prompt_answer(1);
4917        cx.executor().run_until_parked();
4918
4919        // Item 3 is reloaded. There's a prompt to save item 4.
4920        pane.update(cx, |pane, cx| {
4921            assert_eq!(item3.read(cx).save_count, 0);
4922            assert_eq!(item3.read(cx).save_as_count, 0);
4923            assert_eq!(item3.read(cx).reload_count, 1);
4924            assert_eq!(pane.items_len(), 2);
4925            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4926        });
4927        assert!(cx.has_pending_prompt());
4928
4929        // Confirm saving item 4.
4930        cx.simulate_prompt_answer(0);
4931        cx.executor().run_until_parked();
4932
4933        // There's a prompt for a path for item 4.
4934        cx.simulate_new_path_selection(|_| Some(Default::default()));
4935        close_items.await.unwrap();
4936
4937        // The requested items are closed.
4938        pane.update(cx, |pane, cx| {
4939            assert_eq!(item4.read(cx).save_count, 0);
4940            assert_eq!(item4.read(cx).save_as_count, 1);
4941            assert_eq!(item4.read(cx).reload_count, 0);
4942            assert_eq!(pane.items_len(), 1);
4943            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4944        });
4945    }
4946
4947    #[gpui::test]
4948    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4949        init_test(cx);
4950
4951        let fs = FakeFs::new(cx.executor());
4952        let project = Project::test(fs, [], cx).await;
4953        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4954
4955        // Create several workspace items with single project entries, and two
4956        // workspace items with multiple project entries.
4957        let single_entry_items = (0..=4)
4958            .map(|project_entry_id| {
4959                cx.new_view(|cx| {
4960                    TestItem::new(cx)
4961                        .with_dirty(true)
4962                        .with_project_items(&[TestProjectItem::new(
4963                            project_entry_id,
4964                            &format!("{project_entry_id}.txt"),
4965                            cx,
4966                        )])
4967                })
4968            })
4969            .collect::<Vec<_>>();
4970        let item_2_3 = cx.new_view(|cx| {
4971            TestItem::new(cx)
4972                .with_dirty(true)
4973                .with_singleton(false)
4974                .with_project_items(&[
4975                    single_entry_items[2].read(cx).project_items[0].clone(),
4976                    single_entry_items[3].read(cx).project_items[0].clone(),
4977                ])
4978        });
4979        let item_3_4 = cx.new_view(|cx| {
4980            TestItem::new(cx)
4981                .with_dirty(true)
4982                .with_singleton(false)
4983                .with_project_items(&[
4984                    single_entry_items[3].read(cx).project_items[0].clone(),
4985                    single_entry_items[4].read(cx).project_items[0].clone(),
4986                ])
4987        });
4988
4989        // Create two panes that contain the following project entries:
4990        //   left pane:
4991        //     multi-entry items:   (2, 3)
4992        //     single-entry items:  0, 1, 2, 3, 4
4993        //   right pane:
4994        //     single-entry items:  1
4995        //     multi-entry items:   (3, 4)
4996        let left_pane = workspace.update(cx, |workspace, cx| {
4997            let left_pane = workspace.active_pane().clone();
4998            workspace.add_item(Box::new(item_2_3.clone()), cx);
4999            for item in single_entry_items {
5000                workspace.add_item(Box::new(item), cx);
5001            }
5002            left_pane.update(cx, |pane, cx| {
5003                pane.activate_item(2, true, true, cx);
5004            });
5005
5006            let right_pane = workspace
5007                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5008                .unwrap();
5009
5010            right_pane.update(cx, |pane, cx| {
5011                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5012            });
5013
5014            left_pane
5015        });
5016
5017        cx.focus_view(&left_pane);
5018
5019        // When closing all of the items in the left pane, we should be prompted twice:
5020        // once for project entry 0, and once for project entry 2. Project entries 1,
5021        // 3, and 4 are all still open in the other paten. After those two
5022        // prompts, the task should complete.
5023
5024        let close = left_pane.update(cx, |pane, cx| {
5025            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5026        });
5027        cx.executor().run_until_parked();
5028
5029        // Discard "Save all" prompt
5030        cx.simulate_prompt_answer(2);
5031
5032        cx.executor().run_until_parked();
5033        left_pane.update(cx, |pane, cx| {
5034            assert_eq!(
5035                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5036                &[ProjectEntryId::from_proto(0)]
5037            );
5038        });
5039        cx.simulate_prompt_answer(0);
5040
5041        cx.executor().run_until_parked();
5042        left_pane.update(cx, |pane, cx| {
5043            assert_eq!(
5044                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5045                &[ProjectEntryId::from_proto(2)]
5046            );
5047        });
5048        cx.simulate_prompt_answer(0);
5049
5050        cx.executor().run_until_parked();
5051        close.await.unwrap();
5052        left_pane.update(cx, |pane, _| {
5053            assert_eq!(pane.items_len(), 0);
5054        });
5055    }
5056
5057    #[gpui::test]
5058    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5059        init_test(cx);
5060
5061        let fs = FakeFs::new(cx.executor());
5062        let project = Project::test(fs, [], cx).await;
5063        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5064        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5065
5066        let item = cx.new_view(|cx| {
5067            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5068        });
5069        let item_id = item.entity_id();
5070        workspace.update(cx, |workspace, cx| {
5071            workspace.add_item(Box::new(item.clone()), cx);
5072        });
5073
5074        // Autosave on window change.
5075        item.update(cx, |item, cx| {
5076            cx.update_global(|settings: &mut SettingsStore, cx| {
5077                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5078                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5079                })
5080            });
5081            item.is_dirty = true;
5082        });
5083
5084        // Deactivating the window saves the file.
5085        cx.deactivate_window();
5086        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5087
5088        // Autosave on focus change.
5089        item.update(cx, |item, cx| {
5090            cx.focus_self();
5091            cx.update_global(|settings: &mut SettingsStore, cx| {
5092                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5093                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5094                })
5095            });
5096            item.is_dirty = true;
5097        });
5098
5099        // Blurring the item saves the file.
5100        item.update(cx, |_, cx| cx.blur());
5101        cx.executor().run_until_parked();
5102        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5103
5104        // Deactivating the window still saves the file.
5105        cx.update(|cx| cx.activate_window());
5106        item.update(cx, |item, cx| {
5107            cx.focus_self();
5108            item.is_dirty = true;
5109        });
5110        cx.deactivate_window();
5111
5112        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5113
5114        // Autosave after delay.
5115        item.update(cx, |item, cx| {
5116            cx.update_global(|settings: &mut SettingsStore, cx| {
5117                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5118                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5119                })
5120            });
5121            item.is_dirty = true;
5122            cx.emit(ItemEvent::Edit);
5123        });
5124
5125        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5126        cx.executor().advance_clock(Duration::from_millis(250));
5127        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5128
5129        // After delay expires, the file is saved.
5130        cx.executor().advance_clock(Duration::from_millis(250));
5131        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5132
5133        // Autosave on focus change, ensuring closing the tab counts as such.
5134        item.update(cx, |item, cx| {
5135            cx.update_global(|settings: &mut SettingsStore, cx| {
5136                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5137                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5138                })
5139            });
5140            item.is_dirty = true;
5141        });
5142
5143        pane.update(cx, |pane, cx| {
5144            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5145        })
5146        .await
5147        .unwrap();
5148        assert!(!cx.has_pending_prompt());
5149        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5150
5151        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5152        workspace.update(cx, |workspace, cx| {
5153            workspace.add_item(Box::new(item.clone()), cx);
5154        });
5155        item.update(cx, |item, cx| {
5156            item.project_items[0].update(cx, |item, _| {
5157                item.entry_id = None;
5158            });
5159            item.is_dirty = true;
5160            cx.blur();
5161        });
5162        cx.run_until_parked();
5163        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5164
5165        // Ensure autosave is prevented for deleted files also when closing the buffer.
5166        let _close_items = pane.update(cx, |pane, cx| {
5167            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5168        });
5169        cx.run_until_parked();
5170        assert!(cx.has_pending_prompt());
5171        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5172    }
5173
5174    #[gpui::test]
5175    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5176        init_test(cx);
5177
5178        let fs = FakeFs::new(cx.executor());
5179
5180        let project = Project::test(fs, [], cx).await;
5181        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5182
5183        let item = cx.new_view(|cx| {
5184            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5185        });
5186        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5187        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5188        let toolbar_notify_count = Rc::new(RefCell::new(0));
5189
5190        workspace.update(cx, |workspace, cx| {
5191            workspace.add_item(Box::new(item.clone()), cx);
5192            let toolbar_notification_count = toolbar_notify_count.clone();
5193            cx.observe(&toolbar, move |_, _, _| {
5194                *toolbar_notification_count.borrow_mut() += 1
5195            })
5196            .detach();
5197        });
5198
5199        pane.update(cx, |pane, _| {
5200            assert!(!pane.can_navigate_backward());
5201            assert!(!pane.can_navigate_forward());
5202        });
5203
5204        item.update(cx, |item, cx| {
5205            item.set_state("one".to_string(), cx);
5206        });
5207
5208        // Toolbar must be notified to re-render the navigation buttons
5209        assert_eq!(*toolbar_notify_count.borrow(), 1);
5210
5211        pane.update(cx, |pane, _| {
5212            assert!(pane.can_navigate_backward());
5213            assert!(!pane.can_navigate_forward());
5214        });
5215
5216        workspace
5217            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5218            .await
5219            .unwrap();
5220
5221        assert_eq!(*toolbar_notify_count.borrow(), 2);
5222        pane.update(cx, |pane, _| {
5223            assert!(!pane.can_navigate_backward());
5224            assert!(pane.can_navigate_forward());
5225        });
5226    }
5227
5228    #[gpui::test]
5229    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5230        init_test(cx);
5231        let fs = FakeFs::new(cx.executor());
5232
5233        let project = Project::test(fs, [], cx).await;
5234        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5235
5236        let panel = workspace.update(cx, |workspace, cx| {
5237            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5238            workspace.add_panel(panel.clone(), cx);
5239
5240            workspace
5241                .right_dock()
5242                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5243
5244            panel
5245        });
5246
5247        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5248        pane.update(cx, |pane, cx| {
5249            let item = cx.new_view(|cx| TestItem::new(cx));
5250            pane.add_item(Box::new(item), true, true, None, cx);
5251        });
5252
5253        // Transfer focus from center to panel
5254        workspace.update(cx, |workspace, cx| {
5255            workspace.toggle_panel_focus::<TestPanel>(cx);
5256        });
5257
5258        workspace.update(cx, |workspace, cx| {
5259            assert!(workspace.right_dock().read(cx).is_open());
5260            assert!(!panel.is_zoomed(cx));
5261            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5262        });
5263
5264        // Transfer focus from panel to center
5265        workspace.update(cx, |workspace, cx| {
5266            workspace.toggle_panel_focus::<TestPanel>(cx);
5267        });
5268
5269        workspace.update(cx, |workspace, cx| {
5270            assert!(workspace.right_dock().read(cx).is_open());
5271            assert!(!panel.is_zoomed(cx));
5272            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5273        });
5274
5275        // Close the dock
5276        workspace.update(cx, |workspace, cx| {
5277            workspace.toggle_dock(DockPosition::Right, cx);
5278        });
5279
5280        workspace.update(cx, |workspace, cx| {
5281            assert!(!workspace.right_dock().read(cx).is_open());
5282            assert!(!panel.is_zoomed(cx));
5283            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5284        });
5285
5286        // Open the dock
5287        workspace.update(cx, |workspace, cx| {
5288            workspace.toggle_dock(DockPosition::Right, cx);
5289        });
5290
5291        workspace.update(cx, |workspace, cx| {
5292            assert!(workspace.right_dock().read(cx).is_open());
5293            assert!(!panel.is_zoomed(cx));
5294            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5295        });
5296
5297        // Focus and zoom panel
5298        panel.update(cx, |panel, cx| {
5299            cx.focus_self();
5300            panel.set_zoomed(true, cx)
5301        });
5302
5303        workspace.update(cx, |workspace, cx| {
5304            assert!(workspace.right_dock().read(cx).is_open());
5305            assert!(panel.is_zoomed(cx));
5306            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5307        });
5308
5309        // Transfer focus to the center closes the dock
5310        workspace.update(cx, |workspace, cx| {
5311            workspace.toggle_panel_focus::<TestPanel>(cx);
5312        });
5313
5314        workspace.update(cx, |workspace, cx| {
5315            assert!(!workspace.right_dock().read(cx).is_open());
5316            assert!(panel.is_zoomed(cx));
5317            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5318        });
5319
5320        // Transferring focus back to the panel keeps it zoomed
5321        workspace.update(cx, |workspace, cx| {
5322            workspace.toggle_panel_focus::<TestPanel>(cx);
5323        });
5324
5325        workspace.update(cx, |workspace, cx| {
5326            assert!(workspace.right_dock().read(cx).is_open());
5327            assert!(panel.is_zoomed(cx));
5328            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5329        });
5330
5331        // Close the dock while it is zoomed
5332        workspace.update(cx, |workspace, cx| {
5333            workspace.toggle_dock(DockPosition::Right, cx)
5334        });
5335
5336        workspace.update(cx, |workspace, cx| {
5337            assert!(!workspace.right_dock().read(cx).is_open());
5338            assert!(panel.is_zoomed(cx));
5339            assert!(workspace.zoomed.is_none());
5340            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5341        });
5342
5343        // Opening the dock, when it's zoomed, retains focus
5344        workspace.update(cx, |workspace, cx| {
5345            workspace.toggle_dock(DockPosition::Right, cx)
5346        });
5347
5348        workspace.update(cx, |workspace, cx| {
5349            assert!(workspace.right_dock().read(cx).is_open());
5350            assert!(panel.is_zoomed(cx));
5351            assert!(workspace.zoomed.is_some());
5352            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5353        });
5354
5355        // Unzoom and close the panel, zoom the active pane.
5356        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5357        workspace.update(cx, |workspace, cx| {
5358            workspace.toggle_dock(DockPosition::Right, cx)
5359        });
5360        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5361
5362        // Opening a dock unzooms the pane.
5363        workspace.update(cx, |workspace, cx| {
5364            workspace.toggle_dock(DockPosition::Right, cx)
5365        });
5366        workspace.update(cx, |workspace, cx| {
5367            let pane = pane.read(cx);
5368            assert!(!pane.is_zoomed());
5369            assert!(!pane.focus_handle(cx).is_focused(cx));
5370            assert!(workspace.right_dock().read(cx).is_open());
5371            assert!(workspace.zoomed.is_none());
5372        });
5373    }
5374
5375    struct TestModal(FocusHandle);
5376
5377    impl TestModal {
5378        fn new(cx: &mut ViewContext<Self>) -> Self {
5379            Self(cx.focus_handle())
5380        }
5381    }
5382
5383    impl EventEmitter<DismissEvent> for TestModal {}
5384
5385    impl FocusableView for TestModal {
5386        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5387            self.0.clone()
5388        }
5389    }
5390
5391    impl ModalView for TestModal {}
5392
5393    impl Render for TestModal {
5394        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5395            div().track_focus(&self.0)
5396        }
5397    }
5398
5399    #[gpui::test]
5400    async fn test_panels(cx: &mut gpui::TestAppContext) {
5401        init_test(cx);
5402        let fs = FakeFs::new(cx.executor());
5403
5404        let project = Project::test(fs, [], cx).await;
5405        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5406
5407        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5408            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5409            workspace.add_panel(panel_1.clone(), cx);
5410            workspace
5411                .left_dock()
5412                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5413            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5414            workspace.add_panel(panel_2.clone(), cx);
5415            workspace
5416                .right_dock()
5417                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5418
5419            let left_dock = workspace.left_dock();
5420            assert_eq!(
5421                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5422                panel_1.panel_id()
5423            );
5424            assert_eq!(
5425                left_dock.read(cx).active_panel_size(cx).unwrap(),
5426                panel_1.size(cx)
5427            );
5428
5429            left_dock.update(cx, |left_dock, cx| {
5430                left_dock.resize_active_panel(Some(px(1337.)), cx)
5431            });
5432            assert_eq!(
5433                workspace
5434                    .right_dock()
5435                    .read(cx)
5436                    .visible_panel()
5437                    .unwrap()
5438                    .panel_id(),
5439                panel_2.panel_id(),
5440            );
5441
5442            (panel_1, panel_2)
5443        });
5444
5445        // Move panel_1 to the right
5446        panel_1.update(cx, |panel_1, cx| {
5447            panel_1.set_position(DockPosition::Right, cx)
5448        });
5449
5450        workspace.update(cx, |workspace, cx| {
5451            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5452            // Since it was the only panel on the left, the left dock should now be closed.
5453            assert!(!workspace.left_dock().read(cx).is_open());
5454            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5455            let right_dock = workspace.right_dock();
5456            assert_eq!(
5457                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5458                panel_1.panel_id()
5459            );
5460            assert_eq!(
5461                right_dock.read(cx).active_panel_size(cx).unwrap(),
5462                px(1337.)
5463            );
5464
5465            // Now we move panel_2 to the left
5466            panel_2.set_position(DockPosition::Left, cx);
5467        });
5468
5469        workspace.update(cx, |workspace, cx| {
5470            // Since panel_2 was not visible on the right, we don't open the left dock.
5471            assert!(!workspace.left_dock().read(cx).is_open());
5472            // And the right dock is unaffected in it's displaying of panel_1
5473            assert!(workspace.right_dock().read(cx).is_open());
5474            assert_eq!(
5475                workspace
5476                    .right_dock()
5477                    .read(cx)
5478                    .visible_panel()
5479                    .unwrap()
5480                    .panel_id(),
5481                panel_1.panel_id(),
5482            );
5483        });
5484
5485        // Move panel_1 back to the left
5486        panel_1.update(cx, |panel_1, cx| {
5487            panel_1.set_position(DockPosition::Left, cx)
5488        });
5489
5490        workspace.update(cx, |workspace, cx| {
5491            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5492            let left_dock = workspace.left_dock();
5493            assert!(left_dock.read(cx).is_open());
5494            assert_eq!(
5495                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5496                panel_1.panel_id()
5497            );
5498            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5499            // And the right dock should be closed as it no longer has any panels.
5500            assert!(!workspace.right_dock().read(cx).is_open());
5501
5502            // Now we move panel_1 to the bottom
5503            panel_1.set_position(DockPosition::Bottom, cx);
5504        });
5505
5506        workspace.update(cx, |workspace, cx| {
5507            // Since panel_1 was visible on the left, we close the left dock.
5508            assert!(!workspace.left_dock().read(cx).is_open());
5509            // The bottom dock is sized based on the panel's default size,
5510            // since the panel orientation changed from vertical to horizontal.
5511            let bottom_dock = workspace.bottom_dock();
5512            assert_eq!(
5513                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5514                panel_1.size(cx),
5515            );
5516            // Close bottom dock and move panel_1 back to the left.
5517            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5518            panel_1.set_position(DockPosition::Left, cx);
5519        });
5520
5521        // Emit activated event on panel 1
5522        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5523
5524        // Now the left dock is open and panel_1 is active and focused.
5525        workspace.update(cx, |workspace, cx| {
5526            let left_dock = workspace.left_dock();
5527            assert!(left_dock.read(cx).is_open());
5528            assert_eq!(
5529                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5530                panel_1.panel_id(),
5531            );
5532            assert!(panel_1.focus_handle(cx).is_focused(cx));
5533        });
5534
5535        // Emit closed event on panel 2, which is not active
5536        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5537
5538        // Wo don't close the left dock, because panel_2 wasn't the active panel
5539        workspace.update(cx, |workspace, cx| {
5540            let left_dock = workspace.left_dock();
5541            assert!(left_dock.read(cx).is_open());
5542            assert_eq!(
5543                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5544                panel_1.panel_id(),
5545            );
5546        });
5547
5548        // Emitting a ZoomIn event shows the panel as zoomed.
5549        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5550        workspace.update(cx, |workspace, _| {
5551            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5552            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5553        });
5554
5555        // Move panel to another dock while it is zoomed
5556        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5557        workspace.update(cx, |workspace, _| {
5558            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5559
5560            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5561        });
5562
5563        // This is a helper for getting a:
5564        // - valid focus on an element,
5565        // - that isn't a part of the panes and panels system of the Workspace,
5566        // - and doesn't trigger the 'on_focus_lost' API.
5567        let focus_other_view = {
5568            let workspace = workspace.clone();
5569            move |cx: &mut VisualTestContext| {
5570                workspace.update(cx, |workspace, cx| {
5571                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5572                        workspace.toggle_modal(cx, TestModal::new);
5573                        workspace.toggle_modal(cx, TestModal::new);
5574                    } else {
5575                        workspace.toggle_modal(cx, TestModal::new);
5576                    }
5577                })
5578            }
5579        };
5580
5581        // If focus is transferred to another view that's not a panel or another pane, we still show
5582        // the panel as zoomed.
5583        focus_other_view(cx);
5584        workspace.update(cx, |workspace, _| {
5585            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5586            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5587        });
5588
5589        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5590        workspace.update(cx, |_, cx| cx.focus_self());
5591        workspace.update(cx, |workspace, _| {
5592            assert_eq!(workspace.zoomed, None);
5593            assert_eq!(workspace.zoomed_position, None);
5594        });
5595
5596        // If focus is transferred again to another view that's not a panel or a pane, we won't
5597        // show the panel as zoomed because it wasn't zoomed before.
5598        focus_other_view(cx);
5599        workspace.update(cx, |workspace, _| {
5600            assert_eq!(workspace.zoomed, None);
5601            assert_eq!(workspace.zoomed_position, None);
5602        });
5603
5604        // When the panel is activated, it is zoomed again.
5605        cx.dispatch_action(ToggleRightDock);
5606        workspace.update(cx, |workspace, _| {
5607            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5608            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5609        });
5610
5611        // Emitting a ZoomOut event unzooms the panel.
5612        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5613        workspace.update(cx, |workspace, _| {
5614            assert_eq!(workspace.zoomed, None);
5615            assert_eq!(workspace.zoomed_position, None);
5616        });
5617
5618        // Emit closed event on panel 1, which is active
5619        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5620
5621        // Now the left dock is closed, because panel_1 was the active panel
5622        workspace.update(cx, |workspace, cx| {
5623            let right_dock = workspace.right_dock();
5624            assert!(!right_dock.read(cx).is_open());
5625        });
5626    }
5627
5628    pub fn init_test(cx: &mut TestAppContext) {
5629        cx.update(|cx| {
5630            let settings_store = SettingsStore::test(cx);
5631            cx.set_global(settings_store);
5632            theme::init(theme::LoadThemes::JustBase, cx);
5633            language::init(cx);
5634            crate::init_settings(cx);
5635            Project::init_settings(cx);
5636        });
5637    }
5638}