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