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