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