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(&self, cx: &mut ViewContext<Self>) -> Option<proto::View> {
2785        let item = self.active_item(cx)?;
2786        let leader_id = self
2787            .pane_for(&*item)
2788            .and_then(|pane| self.leader_for_pane(&pane));
2789
2790        let item_handle = item.to_followable_item_handle(cx)?;
2791        let id = item_handle.remote_id(&self.app_state.client, cx)?;
2792        let variant = item_handle.to_state_proto(cx)?;
2793
2794        Some(proto::View {
2795            id: Some(id.to_proto()),
2796            leader_id,
2797            variant: Some(variant),
2798        })
2799    }
2800
2801    fn handle_follow(
2802        &mut self,
2803        follower_project_id: Option<u64>,
2804        cx: &mut ViewContext<Self>,
2805    ) -> proto::FollowResponse {
2806        let client = &self.app_state.client;
2807        let project_id = self.project.read(cx).remote_id();
2808
2809        let active_view = self.active_view_for_follower(cx);
2810        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
2811
2812        cx.notify();
2813
2814        proto::FollowResponse {
2815            active_view,
2816            // TODO: once v0.124.0 is retired we can stop sending these
2817            active_view_id,
2818            views: self
2819                .panes()
2820                .iter()
2821                .flat_map(|pane| {
2822                    let leader_id = self.leader_for_pane(pane);
2823                    pane.read(cx).items().filter_map({
2824                        let cx = &cx;
2825                        move |item| {
2826                            let item = item.to_followable_item_handle(cx)?;
2827
2828                            // If the item belongs to a particular project, then it should
2829                            // only be included if this project is shared, and the follower
2830                            // is in the project.
2831                            //
2832                            // Some items, like channel notes, do not belong to a particular
2833                            // project, so they should be included regardless of whether the
2834                            // current project is shared, or what project the follower is in.
2835                            if item.is_project_item(cx)
2836                                && (project_id.is_none() || project_id != follower_project_id)
2837                            {
2838                                return None;
2839                            }
2840
2841                            let id = item.remote_id(client, cx)?.to_proto();
2842                            let variant = item.to_state_proto(cx)?;
2843                            Some(proto::View {
2844                                id: Some(id),
2845                                leader_id,
2846                                variant: Some(variant),
2847                            })
2848                        }
2849                    })
2850                })
2851                .collect(),
2852        }
2853    }
2854
2855    fn handle_update_followers(
2856        &mut self,
2857        leader_id: PeerId,
2858        message: proto::UpdateFollowers,
2859        _cx: &mut ViewContext<Self>,
2860    ) {
2861        self.leader_updates_tx
2862            .unbounded_send((leader_id, message))
2863            .ok();
2864    }
2865
2866    async fn process_leader_update(
2867        this: &WeakView<Self>,
2868        leader_id: PeerId,
2869        update: proto::UpdateFollowers,
2870        cx: &mut AsyncWindowContext,
2871    ) -> Result<()> {
2872        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2873            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2874                let panes_missing_view = this.update(cx, |this, _| {
2875                    let mut panes = vec![];
2876                    for (pane, state) in &mut this.follower_states {
2877                        if state.leader_id != leader_id {
2878                            continue;
2879                        }
2880
2881                        state.active_view_id =
2882                            if let Some(active_view_id) = update_active_view.id.clone() {
2883                                Some(ViewId::from_proto(active_view_id)?)
2884                            } else {
2885                                None
2886                            };
2887
2888                        if state.active_view_id.is_some_and(|view_id| {
2889                            !state.items_by_leader_view_id.contains_key(&view_id)
2890                        }) {
2891                            panes.push(pane.clone())
2892                        }
2893                    }
2894                    anyhow::Ok(panes)
2895                })??;
2896
2897                if let Some(view) = update_active_view.view {
2898                    for pane in panes_missing_view {
2899                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
2900                            .await?
2901                    }
2902                }
2903            }
2904            proto::update_followers::Variant::UpdateView(update_view) => {
2905                let variant = update_view
2906                    .variant
2907                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2908                let id = update_view
2909                    .id
2910                    .ok_or_else(|| anyhow!("missing update view id"))?;
2911                let mut tasks = Vec::new();
2912                this.update(cx, |this, cx| {
2913                    let project = this.project.clone();
2914                    for (_, state) in &mut this.follower_states {
2915                        if state.leader_id == leader_id {
2916                            let view_id = ViewId::from_proto(id.clone())?;
2917                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2918                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2919                            }
2920                        }
2921                    }
2922                    anyhow::Ok(())
2923                })??;
2924                try_join_all(tasks).await.log_err();
2925            }
2926            proto::update_followers::Variant::CreateView(view) => {
2927                let panes = this.update(cx, |this, _| {
2928                    this.follower_states
2929                        .iter()
2930                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2931                        .cloned()
2932                        .collect()
2933                })?;
2934                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2935            }
2936        }
2937        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2938        Ok(())
2939    }
2940
2941    async fn add_view_from_leader(
2942        this: WeakView<Self>,
2943        leader_id: PeerId,
2944        pane: View<Pane>,
2945        view: &proto::View,
2946        cx: &mut AsyncWindowContext,
2947    ) -> Result<()> {
2948        let this = this.upgrade().context("workspace dropped")?;
2949
2950        let item_builders = cx.update(|cx| {
2951            cx.default_global::<FollowableItemBuilders>()
2952                .values()
2953                .map(|b| b.0)
2954                .collect::<Vec<_>>()
2955        })?;
2956
2957        let Some(id) = view.id.clone() else {
2958            return Err(anyhow!("no id for view")).into();
2959        };
2960        let id = ViewId::from_proto(id)?;
2961
2962        let mut variant = view.variant.clone();
2963        if variant.is_none() {
2964            Err(anyhow!("missing view variant"))?;
2965        }
2966
2967        let task = item_builders.iter().find_map(|build_item| {
2968            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
2969                .log_err()
2970                .flatten()
2971        });
2972        let Some(task) = task else {
2973            return Err(anyhow!(
2974                "failed to construct view from leader (maybe from a different version of zed?)"
2975            ));
2976        };
2977
2978        let item = task.await?;
2979
2980        this.update(cx, |this, cx| {
2981            let state = this.follower_states.get_mut(&pane)?;
2982            item.set_leader_peer_id(Some(leader_id), cx);
2983            state.items_by_leader_view_id.insert(id, item);
2984
2985            Some(())
2986        })?;
2987
2988        Ok(())
2989    }
2990
2991    async fn add_views_from_leader(
2992        this: WeakView<Self>,
2993        leader_id: PeerId,
2994        panes: Vec<View<Pane>>,
2995        views: Vec<proto::View>,
2996        cx: &mut AsyncWindowContext,
2997    ) -> Result<()> {
2998        let this = this.upgrade().context("workspace dropped")?;
2999
3000        let item_builders = cx.update(|cx| {
3001            cx.default_global::<FollowableItemBuilders>()
3002                .values()
3003                .map(|b| b.0)
3004                .collect::<Vec<_>>()
3005        })?;
3006
3007        let mut item_tasks_by_pane = HashMap::default();
3008        for pane in panes {
3009            let mut item_tasks = Vec::new();
3010            let mut leader_view_ids = Vec::new();
3011            for view in &views {
3012                let Some(id) = &view.id else {
3013                    continue;
3014                };
3015                let id = ViewId::from_proto(id.clone())?;
3016                let mut variant = view.variant.clone();
3017                if variant.is_none() {
3018                    Err(anyhow!("missing view variant"))?;
3019                }
3020                for build_item in &item_builders {
3021                    let task = cx.update(|cx| {
3022                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3023                    })?;
3024                    if let Some(task) = task {
3025                        item_tasks.push(task);
3026                        leader_view_ids.push(id);
3027                        break;
3028                    } else if variant.is_none() {
3029                        Err(anyhow!(
3030                            "failed to construct view from leader (maybe from a different version of zed?)"
3031                        ))?;
3032                    }
3033                }
3034            }
3035
3036            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3037        }
3038
3039        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3040            let items = futures::future::try_join_all(item_tasks).await?;
3041            this.update(cx, |this, cx| {
3042                let state = this.follower_states.get_mut(&pane)?;
3043                for (id, item) in leader_view_ids.into_iter().zip(items) {
3044                    item.set_leader_peer_id(Some(leader_id), cx);
3045                    state.items_by_leader_view_id.insert(id, item);
3046                }
3047
3048                Some(())
3049            })?;
3050        }
3051        Ok(())
3052    }
3053
3054    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3055        let mut is_project_item = true;
3056        let mut update = proto::UpdateActiveView::default();
3057        if cx.is_window_active() {
3058            if let Some(item) = self.active_item(cx) {
3059                if item.focus_handle(cx).contains_focused(cx) {
3060                    let leader_id = self
3061                        .pane_for(&*item)
3062                        .and_then(|pane| self.leader_for_pane(&pane));
3063
3064                    if let Some(item) = item.to_followable_item_handle(cx) {
3065                        let id = item
3066                            .remote_id(&self.app_state.client, cx)
3067                            .map(|id| id.to_proto());
3068
3069                        if let Some(id) = id.clone() {
3070                            if let Some(variant) = item.to_state_proto(cx) {
3071                                let view = Some(proto::View {
3072                                    id: Some(id.clone()),
3073                                    leader_id,
3074                                    variant: Some(variant),
3075                                });
3076
3077                                is_project_item = item.is_project_item(cx);
3078                                update = proto::UpdateActiveView {
3079                                    view,
3080                                    // TODO: once v0.124.0 is retired we can stop sending these
3081                                    id: Some(id),
3082                                    leader_id,
3083                                };
3084                            }
3085                        };
3086                    }
3087                }
3088            }
3089        }
3090
3091        if &update.id != &self.last_active_view_id {
3092            self.last_active_view_id = update.id.clone();
3093            self.update_followers(
3094                is_project_item,
3095                proto::update_followers::Variant::UpdateActiveView(update),
3096                cx,
3097            );
3098        }
3099    }
3100
3101    fn update_followers(
3102        &self,
3103        project_only: bool,
3104        update: proto::update_followers::Variant,
3105        cx: &mut WindowContext,
3106    ) -> Option<()> {
3107        // If this update only applies to for followers in the current project,
3108        // then skip it unless this project is shared. If it applies to all
3109        // followers, regardless of project, then set `project_id` to none,
3110        // indicating that it goes to all followers.
3111        let project_id = if project_only {
3112            Some(self.project.read(cx).remote_id()?)
3113        } else {
3114            None
3115        };
3116        self.app_state().workspace_store.update(cx, |store, cx| {
3117            store.update_followers(project_id, update, cx)
3118        })
3119    }
3120
3121    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3122        self.follower_states.get(pane).map(|state| state.leader_id)
3123    }
3124
3125    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3126        cx.notify();
3127
3128        let call = self.active_call()?;
3129        let room = call.read(cx).room()?.read(cx);
3130        let participant = room.remote_participant_for_peer_id(leader_id)?;
3131        let mut items_to_activate = Vec::new();
3132
3133        let leader_in_this_app;
3134        let leader_in_this_project;
3135        match participant.location {
3136            call::ParticipantLocation::SharedProject { project_id } => {
3137                leader_in_this_app = true;
3138                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3139            }
3140            call::ParticipantLocation::UnsharedProject => {
3141                leader_in_this_app = true;
3142                leader_in_this_project = false;
3143            }
3144            call::ParticipantLocation::External => {
3145                leader_in_this_app = false;
3146                leader_in_this_project = false;
3147            }
3148        };
3149
3150        for (pane, state) in &self.follower_states {
3151            if state.leader_id != leader_id {
3152                continue;
3153            }
3154            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3155                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3156                    if leader_in_this_project || !item.is_project_item(cx) {
3157                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3158                    }
3159                }
3160                continue;
3161            }
3162
3163            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3164                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3165            }
3166        }
3167
3168        for (pane, item) in items_to_activate {
3169            let pane_was_focused = pane.read(cx).has_focus(cx);
3170            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3171                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3172            } else {
3173                pane.update(cx, |pane, cx| {
3174                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3175                });
3176            }
3177
3178            if pane_was_focused {
3179                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3180            }
3181        }
3182
3183        None
3184    }
3185
3186    fn shared_screen_for_peer(
3187        &self,
3188        peer_id: PeerId,
3189        pane: &View<Pane>,
3190        cx: &mut WindowContext,
3191    ) -> Option<View<SharedScreen>> {
3192        let call = self.active_call()?;
3193        let room = call.read(cx).room()?.read(cx);
3194        let participant = room.remote_participant_for_peer_id(peer_id)?;
3195        let track = participant.video_tracks.values().next()?.clone();
3196        let user = participant.user.clone();
3197
3198        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3199            if item.read(cx).peer_id == peer_id {
3200                return Some(item);
3201            }
3202        }
3203
3204        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3205    }
3206
3207    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3208        if cx.is_window_active() {
3209            self.update_active_view_for_followers(cx);
3210            cx.background_executor()
3211                .spawn(persistence::DB.update_timestamp(self.database_id()))
3212                .detach();
3213        } else {
3214            for pane in &self.panes {
3215                pane.update(cx, |pane, cx| {
3216                    if let Some(item) = pane.active_item() {
3217                        item.workspace_deactivated(cx);
3218                    }
3219                    if matches!(
3220                        WorkspaceSettings::get_global(cx).autosave,
3221                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3222                    ) {
3223                        for item in pane.items() {
3224                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3225                                .detach_and_log_err(cx);
3226                        }
3227                    }
3228                });
3229            }
3230        }
3231    }
3232
3233    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3234        self.active_call.as_ref().map(|(call, _)| call)
3235    }
3236
3237    fn on_active_call_event(
3238        &mut self,
3239        _: Model<ActiveCall>,
3240        event: &call::room::Event,
3241        cx: &mut ViewContext<Self>,
3242    ) {
3243        match event {
3244            call::room::Event::ParticipantLocationChanged { participant_id }
3245            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3246                self.leader_updated(*participant_id, cx);
3247            }
3248            _ => {}
3249        }
3250    }
3251
3252    pub fn database_id(&self) -> WorkspaceId {
3253        self.database_id
3254    }
3255
3256    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3257        let project = self.project().read(cx);
3258
3259        if project.is_local() {
3260            Some(
3261                project
3262                    .visible_worktrees(cx)
3263                    .map(|worktree| worktree.read(cx).abs_path())
3264                    .collect::<Vec<_>>()
3265                    .into(),
3266            )
3267        } else {
3268            None
3269        }
3270    }
3271
3272    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3273        match member {
3274            Member::Axis(PaneAxis { members, .. }) => {
3275                for child in members.iter() {
3276                    self.remove_panes(child.clone(), cx)
3277                }
3278            }
3279            Member::Pane(pane) => {
3280                self.force_remove_pane(&pane, cx);
3281            }
3282        }
3283    }
3284
3285    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3286        self.panes.retain(|p| p != pane);
3287        self.panes
3288            .last()
3289            .unwrap()
3290            .update(cx, |pane, cx| pane.focus(cx));
3291        if self.last_active_center_pane == Some(pane.downgrade()) {
3292            self.last_active_center_pane = None;
3293        }
3294        cx.notify();
3295    }
3296
3297    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3298        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3299            cx.background_executor()
3300                .timer(Duration::from_millis(100))
3301                .await;
3302            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3303                .log_err();
3304        }));
3305    }
3306
3307    fn serialize_workspace(&self, cx: &mut WindowContext) {
3308        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3309            let (items, active) = {
3310                let pane = pane_handle.read(cx);
3311                let active_item_id = pane.active_item().map(|item| item.item_id());
3312                (
3313                    pane.items()
3314                        .filter_map(|item_handle| {
3315                            Some(SerializedItem {
3316                                kind: Arc::from(item_handle.serialized_item_kind()?),
3317                                item_id: item_handle.item_id().as_u64(),
3318                                active: Some(item_handle.item_id()) == active_item_id,
3319                            })
3320                        })
3321                        .collect::<Vec<_>>(),
3322                    pane.has_focus(cx),
3323                )
3324            };
3325
3326            SerializedPane::new(items, active)
3327        }
3328
3329        fn build_serialized_pane_group(
3330            pane_group: &Member,
3331            cx: &WindowContext,
3332        ) -> SerializedPaneGroup {
3333            match pane_group {
3334                Member::Axis(PaneAxis {
3335                    axis,
3336                    members,
3337                    flexes,
3338                    bounding_boxes: _,
3339                }) => SerializedPaneGroup::Group {
3340                    axis: SerializedAxis(*axis),
3341                    children: members
3342                        .iter()
3343                        .map(|member| build_serialized_pane_group(member, cx))
3344                        .collect::<Vec<_>>(),
3345                    flexes: Some(flexes.lock().clone()),
3346                },
3347                Member::Pane(pane_handle) => {
3348                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3349                }
3350            }
3351        }
3352
3353        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3354            let left_dock = this.left_dock.read(cx);
3355            let left_visible = left_dock.is_open();
3356            let left_active_panel = left_dock
3357                .visible_panel()
3358                .and_then(|panel| Some(panel.persistent_name().to_string()));
3359            let left_dock_zoom = left_dock
3360                .visible_panel()
3361                .map(|panel| panel.is_zoomed(cx))
3362                .unwrap_or(false);
3363
3364            let right_dock = this.right_dock.read(cx);
3365            let right_visible = right_dock.is_open();
3366            let right_active_panel = right_dock
3367                .visible_panel()
3368                .and_then(|panel| Some(panel.persistent_name().to_string()));
3369            let right_dock_zoom = right_dock
3370                .visible_panel()
3371                .map(|panel| panel.is_zoomed(cx))
3372                .unwrap_or(false);
3373
3374            let bottom_dock = this.bottom_dock.read(cx);
3375            let bottom_visible = bottom_dock.is_open();
3376            let bottom_active_panel = bottom_dock
3377                .visible_panel()
3378                .and_then(|panel| Some(panel.persistent_name().to_string()));
3379            let bottom_dock_zoom = bottom_dock
3380                .visible_panel()
3381                .map(|panel| panel.is_zoomed(cx))
3382                .unwrap_or(false);
3383
3384            DockStructure {
3385                left: DockData {
3386                    visible: left_visible,
3387                    active_panel: left_active_panel,
3388                    zoom: left_dock_zoom,
3389                },
3390                right: DockData {
3391                    visible: right_visible,
3392                    active_panel: right_active_panel,
3393                    zoom: right_dock_zoom,
3394                },
3395                bottom: DockData {
3396                    visible: bottom_visible,
3397                    active_panel: bottom_active_panel,
3398                    zoom: bottom_dock_zoom,
3399                },
3400            }
3401        }
3402
3403        if let Some(location) = self.location(cx) {
3404            // Load bearing special case:
3405            //  - with_local_workspace() relies on this to not have other stuff open
3406            //    when you open your log
3407            if !location.paths().is_empty() {
3408                let center_group = build_serialized_pane_group(&self.center.root, cx);
3409                let docks = build_serialized_docks(self, cx);
3410
3411                let serialized_workspace = SerializedWorkspace {
3412                    id: self.database_id,
3413                    location,
3414                    center_group,
3415                    bounds: Default::default(),
3416                    display: Default::default(),
3417                    docks,
3418                };
3419
3420                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3421                    .detach();
3422            }
3423        }
3424    }
3425
3426    pub(crate) fn load_workspace(
3427        serialized_workspace: SerializedWorkspace,
3428        paths_to_open: Vec<Option<ProjectPath>>,
3429        cx: &mut ViewContext<Workspace>,
3430    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3431        cx.spawn(|workspace, mut cx| async move {
3432            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3433
3434            let mut center_group = None;
3435            let mut center_items = None;
3436
3437            // Traverse the splits tree and add to things
3438            if let Some((group, active_pane, items)) = serialized_workspace
3439                .center_group
3440                .deserialize(
3441                    &project,
3442                    serialized_workspace.id,
3443                    workspace.clone(),
3444                    &mut cx,
3445                )
3446                .await
3447            {
3448                center_items = Some(items);
3449                center_group = Some((group, active_pane))
3450            }
3451
3452            let mut items_by_project_path = cx.update(|cx| {
3453                center_items
3454                    .unwrap_or_default()
3455                    .into_iter()
3456                    .filter_map(|item| {
3457                        let item = item?;
3458                        let project_path = item.project_path(cx)?;
3459                        Some((project_path, item))
3460                    })
3461                    .collect::<HashMap<_, _>>()
3462            })?;
3463
3464            let opened_items = paths_to_open
3465                .into_iter()
3466                .map(|path_to_open| {
3467                    path_to_open
3468                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3469                })
3470                .collect::<Vec<_>>();
3471
3472            // Remove old panes from workspace panes list
3473            workspace.update(&mut cx, |workspace, cx| {
3474                if let Some((center_group, active_pane)) = center_group {
3475                    workspace.remove_panes(workspace.center.root.clone(), cx);
3476
3477                    // Swap workspace center group
3478                    workspace.center = PaneGroup::with_root(center_group);
3479                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3480                    if let Some(active_pane) = active_pane {
3481                        workspace.active_pane = active_pane;
3482                        cx.focus_self();
3483                    } else {
3484                        workspace.active_pane = workspace.center.first_pane().clone();
3485                    }
3486                }
3487
3488                let docks = serialized_workspace.docks;
3489
3490                let right = docks.right.clone();
3491                workspace
3492                    .right_dock
3493                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3494                let left = docks.left.clone();
3495                workspace
3496                    .left_dock
3497                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3498                let bottom = docks.bottom.clone();
3499                workspace
3500                    .bottom_dock
3501                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3502
3503                cx.notify();
3504            })?;
3505
3506            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3507            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3508
3509            Ok(opened_items)
3510        })
3511    }
3512
3513    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3514        self.add_workspace_actions_listeners(div, cx)
3515            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3516            .on_action(cx.listener(Self::close_all_items_and_panes))
3517            .on_action(cx.listener(Self::save_all))
3518            .on_action(cx.listener(Self::send_keystrokes))
3519            .on_action(cx.listener(Self::add_folder_to_project))
3520            .on_action(cx.listener(Self::follow_next_collaborator))
3521            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3522                let pane = workspace.active_pane().clone();
3523                workspace.unfollow(&pane, cx);
3524            }))
3525            .on_action(cx.listener(|workspace, action: &Save, cx| {
3526                workspace
3527                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3528                    .detach_and_log_err(cx);
3529            }))
3530            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3531                workspace
3532                    .save_active_item(SaveIntent::SaveAs, cx)
3533                    .detach_and_log_err(cx);
3534            }))
3535            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3536                workspace.activate_previous_pane(cx)
3537            }))
3538            .on_action(
3539                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3540            )
3541            .on_action(
3542                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3543                    workspace.activate_pane_in_direction(action.0, cx)
3544                }),
3545            )
3546            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3547                workspace.swap_pane_in_direction(action.0, cx)
3548            }))
3549            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3550                this.toggle_dock(DockPosition::Left, cx);
3551            }))
3552            .on_action(
3553                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3554                    workspace.toggle_dock(DockPosition::Right, cx);
3555                }),
3556            )
3557            .on_action(
3558                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3559                    workspace.toggle_dock(DockPosition::Bottom, cx);
3560                }),
3561            )
3562            .on_action(
3563                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3564                    workspace.close_all_docks(cx);
3565                }),
3566            )
3567            .on_action(cx.listener(Workspace::open))
3568            .on_action(cx.listener(Workspace::close_window))
3569            .on_action(cx.listener(Workspace::activate_pane_at_index))
3570            .on_action(
3571                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3572                    workspace.reopen_closed_item(cx).detach();
3573                }),
3574            )
3575            .on_action(|_: &ToggleGraphicsProfiler, cx| cx.toggle_graphics_profiler())
3576    }
3577
3578    #[cfg(any(test, feature = "test-support"))]
3579    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3580        use node_runtime::FakeNodeRuntime;
3581
3582        let client = project.read(cx).client();
3583        let user_store = project.read(cx).user_store();
3584
3585        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3586        cx.activate_window();
3587        let app_state = Arc::new(AppState {
3588            languages: project.read(cx).languages().clone(),
3589            workspace_store,
3590            client,
3591            user_store,
3592            fs: project.read(cx).fs().clone(),
3593            build_window_options: |_, _, _| Default::default(),
3594            node_runtime: FakeNodeRuntime::new(),
3595        });
3596        let workspace = Self::new(0, project, app_state, cx);
3597        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3598        workspace
3599    }
3600
3601    pub fn register_action<A: Action>(
3602        &mut self,
3603        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3604    ) -> &mut Self {
3605        let callback = Arc::new(callback);
3606
3607        self.workspace_actions.push(Box::new(move |div, cx| {
3608            let callback = callback.clone();
3609            div.on_action(
3610                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3611            )
3612        }));
3613        self
3614    }
3615
3616    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3617        let mut div = div
3618            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3619            .on_action(cx.listener(Self::close_all_items_and_panes))
3620            .on_action(cx.listener(Self::add_folder_to_project))
3621            .on_action(cx.listener(Self::save_all))
3622            .on_action(cx.listener(Self::open));
3623        for action in self.workspace_actions.iter() {
3624            div = (action)(div, cx)
3625        }
3626        div
3627    }
3628
3629    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3630        self.modal_layer.read(cx).has_active_modal()
3631    }
3632
3633    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3634        self.modal_layer.read(cx).active_modal()
3635    }
3636
3637    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3638    where
3639        B: FnOnce(&mut ViewContext<V>) -> V,
3640    {
3641        self.modal_layer
3642            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3643    }
3644}
3645
3646fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3647    let display_origin = cx
3648        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3649        .ok()??;
3650    ZED_WINDOW_POSITION
3651        .zip(*ZED_WINDOW_SIZE)
3652        .map(|(position, size)| {
3653            WindowBounds::Fixed(Bounds {
3654                origin: display_origin + position,
3655                size,
3656            })
3657        })
3658}
3659
3660fn open_items(
3661    serialized_workspace: Option<SerializedWorkspace>,
3662    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3663    app_state: Arc<AppState>,
3664    cx: &mut ViewContext<Workspace>,
3665) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3666    let restored_items = serialized_workspace.map(|serialized_workspace| {
3667        Workspace::load_workspace(
3668            serialized_workspace,
3669            project_paths_to_open
3670                .iter()
3671                .map(|(_, project_path)| project_path)
3672                .cloned()
3673                .collect(),
3674            cx,
3675        )
3676    });
3677
3678    cx.spawn(|workspace, mut cx| async move {
3679        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3680
3681        if let Some(restored_items) = restored_items {
3682            let restored_items = restored_items.await?;
3683
3684            let restored_project_paths = restored_items
3685                .iter()
3686                .filter_map(|item| {
3687                    cx.update(|cx| item.as_ref()?.project_path(cx))
3688                        .ok()
3689                        .flatten()
3690                })
3691                .collect::<HashSet<_>>();
3692
3693            for restored_item in restored_items {
3694                opened_items.push(restored_item.map(Ok));
3695            }
3696
3697            project_paths_to_open
3698                .iter_mut()
3699                .for_each(|(_, project_path)| {
3700                    if let Some(project_path_to_open) = project_path {
3701                        if restored_project_paths.contains(project_path_to_open) {
3702                            *project_path = None;
3703                        }
3704                    }
3705                });
3706        } else {
3707            for _ in 0..project_paths_to_open.len() {
3708                opened_items.push(None);
3709            }
3710        }
3711        assert!(opened_items.len() == project_paths_to_open.len());
3712
3713        let tasks =
3714            project_paths_to_open
3715                .into_iter()
3716                .enumerate()
3717                .map(|(i, (abs_path, project_path))| {
3718                    let workspace = workspace.clone();
3719                    cx.spawn(|mut cx| {
3720                        let fs = app_state.fs.clone();
3721                        async move {
3722                            let file_project_path = project_path?;
3723                            if fs.is_file(&abs_path).await {
3724                                Some((
3725                                    i,
3726                                    workspace
3727                                        .update(&mut cx, |workspace, cx| {
3728                                            workspace.open_path(file_project_path, None, true, cx)
3729                                        })
3730                                        .log_err()?
3731                                        .await,
3732                                ))
3733                            } else {
3734                                None
3735                            }
3736                        }
3737                    })
3738                });
3739
3740        let tasks = tasks.collect::<Vec<_>>();
3741
3742        let tasks = futures::future::join_all(tasks.into_iter());
3743        for maybe_opened_path in tasks.await.into_iter() {
3744            if let Some((i, path_open_result)) = maybe_opened_path {
3745                opened_items[i] = Some(path_open_result);
3746            }
3747        }
3748
3749        Ok(opened_items)
3750    })
3751}
3752
3753enum ActivateInDirectionTarget {
3754    Pane(View<Pane>),
3755    Dock(View<Dock>),
3756}
3757
3758fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3759    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3760
3761    workspace
3762        .update(cx, |workspace, cx| {
3763            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3764                workspace.show_notification_once(0, cx, |cx| {
3765                    cx.new_view(|_| {
3766                        MessageNotification::new("Failed to load the database file.")
3767                            .with_click_message("Click to let us know about this error")
3768                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3769                    })
3770                });
3771            }
3772        })
3773        .log_err();
3774}
3775
3776impl FocusableView for Workspace {
3777    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3778        self.active_pane.focus_handle(cx)
3779    }
3780}
3781
3782#[derive(Clone, Render)]
3783struct DraggedDock(DockPosition);
3784
3785impl Render for Workspace {
3786    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3787        let mut context = KeyContext::default();
3788        context.add("Workspace");
3789
3790        let (ui_font, ui_font_size) = {
3791            let theme_settings = ThemeSettings::get_global(cx);
3792            (
3793                theme_settings.ui_font.family.clone(),
3794                theme_settings.ui_font_size.clone(),
3795            )
3796        };
3797
3798        let theme = cx.theme().clone();
3799        let colors = theme.colors();
3800        cx.set_rem_size(ui_font_size);
3801
3802        self.actions(div(), cx)
3803            .key_context(context)
3804            .relative()
3805            .size_full()
3806            .flex()
3807            .flex_col()
3808            .font(ui_font)
3809            .gap_0()
3810            .justify_start()
3811            .items_start()
3812            .text_color(colors.text)
3813            .bg(colors.background)
3814            .border()
3815            .border_color(colors.border)
3816            .children(self.titlebar_item.clone())
3817            .child(
3818                div()
3819                    .id("workspace")
3820                    .relative()
3821                    .flex_1()
3822                    .w_full()
3823                    .flex()
3824                    .flex_col()
3825                    .overflow_hidden()
3826                    .border_t()
3827                    .border_b()
3828                    .border_color(colors.border)
3829                    .child(
3830                        canvas({
3831                            let this = cx.view().clone();
3832                            move |bounds, cx| {
3833                                this.update(cx, |this, _cx| {
3834                                    this.bounds = *bounds;
3835                                })
3836                            }
3837                        })
3838                        .absolute()
3839                        .size_full(),
3840                    )
3841                    .on_drag_move(
3842                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3843                            match e.drag(cx).0 {
3844                                DockPosition::Left => {
3845                                    let size = workspace.bounds.left() + e.event.position.x;
3846                                    workspace.left_dock.update(cx, |left_dock, cx| {
3847                                        left_dock.resize_active_panel(Some(size), cx);
3848                                    });
3849                                }
3850                                DockPosition::Right => {
3851                                    let size = workspace.bounds.right() - e.event.position.x;
3852                                    workspace.right_dock.update(cx, |right_dock, cx| {
3853                                        right_dock.resize_active_panel(Some(size), cx);
3854                                    });
3855                                }
3856                                DockPosition::Bottom => {
3857                                    let size = workspace.bounds.bottom() - e.event.position.y;
3858                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3859                                        bottom_dock.resize_active_panel(Some(size), cx);
3860                                    });
3861                                }
3862                            }
3863                        }),
3864                    )
3865                    .child(self.modal_layer.clone())
3866                    .child(
3867                        div()
3868                            .flex()
3869                            .flex_row()
3870                            .h_full()
3871                            // Left Dock
3872                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3873                                || {
3874                                    div()
3875                                        .flex()
3876                                        .flex_none()
3877                                        .overflow_hidden()
3878                                        .child(self.left_dock.clone())
3879                                },
3880                            ))
3881                            // Panes
3882                            .child(
3883                                div()
3884                                    .flex()
3885                                    .flex_col()
3886                                    .flex_1()
3887                                    .overflow_hidden()
3888                                    .child(self.center.render(
3889                                        &self.project,
3890                                        &self.follower_states,
3891                                        self.active_call(),
3892                                        &self.active_pane,
3893                                        self.zoomed.as_ref(),
3894                                        &self.app_state,
3895                                        cx,
3896                                    ))
3897                                    .children(
3898                                        self.zoomed_position
3899                                            .ne(&Some(DockPosition::Bottom))
3900                                            .then(|| self.bottom_dock.clone()),
3901                                    ),
3902                            )
3903                            // Right Dock
3904                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3905                                || {
3906                                    div()
3907                                        .flex()
3908                                        .flex_none()
3909                                        .overflow_hidden()
3910                                        .child(self.right_dock.clone())
3911                                },
3912                            )),
3913                    )
3914                    .children(self.render_notifications(cx))
3915                    .children(self.zoomed.as_ref().and_then(|view| {
3916                        let zoomed_view = view.upgrade()?;
3917                        let div = div()
3918                            .z_index(1)
3919                            .absolute()
3920                            .overflow_hidden()
3921                            .border_color(colors.border)
3922                            .bg(colors.background)
3923                            .child(zoomed_view)
3924                            .inset_0()
3925                            .shadow_lg();
3926
3927                        Some(match self.zoomed_position {
3928                            Some(DockPosition::Left) => div.right_2().border_r(),
3929                            Some(DockPosition::Right) => div.left_2().border_l(),
3930                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3931                            None => div.top_2().bottom_2().left_2().right_2().border(),
3932                        })
3933                    })),
3934            )
3935            .child(self.status_bar.clone())
3936            .children(if self.project.read(cx).is_disconnected() {
3937                Some(DisconnectedOverlay)
3938            } else {
3939                None
3940            })
3941    }
3942}
3943
3944impl WorkspaceStore {
3945    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3946        Self {
3947            workspaces: Default::default(),
3948            _subscriptions: vec![
3949                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3950                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3951            ],
3952            client,
3953        }
3954    }
3955
3956    pub fn update_followers(
3957        &self,
3958        project_id: Option<u64>,
3959        update: proto::update_followers::Variant,
3960        cx: &AppContext,
3961    ) -> Option<()> {
3962        let active_call = ActiveCall::try_global(cx)?;
3963        let room_id = active_call.read(cx).room()?.read(cx).id();
3964        self.client
3965            .send(proto::UpdateFollowers {
3966                room_id,
3967                project_id,
3968                variant: Some(update),
3969            })
3970            .log_err()
3971    }
3972
3973    pub async fn handle_follow(
3974        this: Model<Self>,
3975        envelope: TypedEnvelope<proto::Follow>,
3976        _: Arc<Client>,
3977        mut cx: AsyncAppContext,
3978    ) -> Result<proto::FollowResponse> {
3979        this.update(&mut cx, |this, cx| {
3980            let follower = Follower {
3981                project_id: envelope.payload.project_id,
3982                peer_id: envelope.original_sender_id()?,
3983            };
3984            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3985
3986            let mut response = proto::FollowResponse::default();
3987            this.workspaces.retain(|workspace| {
3988                workspace
3989                    .update(cx, |workspace, cx| {
3990                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3991                        if response.views.is_empty() {
3992                            response.views = handler_response.views;
3993                        } else {
3994                            response.views.extend_from_slice(&handler_response.views);
3995                        }
3996
3997                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3998                            if response.active_view_id.is_none()
3999                                || Some(workspace.project.downgrade()) == active_project
4000                            {
4001                                response.active_view_id = Some(active_view_id);
4002                            }
4003                        }
4004
4005                        if let Some(active_view) = handler_response.active_view.clone() {
4006                            if workspace.project.read(cx).remote_id() == follower.project_id {
4007                                response.active_view = Some(active_view)
4008                            }
4009                        }
4010                    })
4011                    .is_ok()
4012            });
4013
4014            Ok(response)
4015        })?
4016    }
4017
4018    async fn handle_update_followers(
4019        this: Model<Self>,
4020        envelope: TypedEnvelope<proto::UpdateFollowers>,
4021        _: Arc<Client>,
4022        mut cx: AsyncAppContext,
4023    ) -> Result<()> {
4024        let leader_id = envelope.original_sender_id()?;
4025        let update = envelope.payload;
4026
4027        this.update(&mut cx, |this, cx| {
4028            this.workspaces.retain(|workspace| {
4029                workspace
4030                    .update(cx, |workspace, cx| {
4031                        let project_id = workspace.project.read(cx).remote_id();
4032                        if update.project_id != project_id && update.project_id.is_some() {
4033                            return;
4034                        }
4035                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4036                    })
4037                    .is_ok()
4038            });
4039            Ok(())
4040        })?
4041    }
4042}
4043
4044impl ViewId {
4045    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4046        Ok(Self {
4047            creator: message
4048                .creator
4049                .ok_or_else(|| anyhow!("creator is missing"))?,
4050            id: message.id,
4051        })
4052    }
4053
4054    pub(crate) fn to_proto(&self) -> proto::ViewId {
4055        proto::ViewId {
4056            creator: Some(self.creator),
4057            id: self.id,
4058        }
4059    }
4060}
4061
4062pub trait WorkspaceHandle {
4063    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4064}
4065
4066impl WorkspaceHandle for View<Workspace> {
4067    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4068        self.read(cx)
4069            .worktrees(cx)
4070            .flat_map(|worktree| {
4071                let worktree_id = worktree.read(cx).id();
4072                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4073                    worktree_id,
4074                    path: f.path.clone(),
4075                })
4076            })
4077            .collect::<Vec<_>>()
4078    }
4079}
4080
4081impl std::fmt::Debug for OpenPaths {
4082    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4083        f.debug_struct("OpenPaths")
4084            .field("paths", &self.paths)
4085            .finish()
4086    }
4087}
4088
4089pub fn activate_workspace_for_project(
4090    cx: &mut AppContext,
4091    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4092) -> Option<WindowHandle<Workspace>> {
4093    for window in cx.windows() {
4094        let Some(workspace) = window.downcast::<Workspace>() else {
4095            continue;
4096        };
4097
4098        let predicate = workspace
4099            .update(cx, |workspace, cx| {
4100                let project = workspace.project.read(cx);
4101                if predicate(project, cx) {
4102                    cx.activate_window();
4103                    true
4104                } else {
4105                    false
4106                }
4107            })
4108            .log_err()
4109            .unwrap_or(false);
4110
4111        if predicate {
4112            return Some(workspace);
4113        }
4114    }
4115
4116    None
4117}
4118
4119pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4120    DB.last_workspace().await.log_err().flatten()
4121}
4122
4123actions!(collab, [OpenChannelNotes]);
4124
4125async fn join_channel_internal(
4126    channel_id: ChannelId,
4127    app_state: &Arc<AppState>,
4128    requesting_window: Option<WindowHandle<Workspace>>,
4129    active_call: &Model<ActiveCall>,
4130    cx: &mut AsyncAppContext,
4131) -> Result<bool> {
4132    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4133        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4134            return (false, None);
4135        };
4136
4137        let already_in_channel = room.channel_id() == Some(channel_id);
4138        let should_prompt = room.is_sharing_project()
4139            && room.remote_participants().len() > 0
4140            && !already_in_channel;
4141        let open_room = if already_in_channel {
4142            active_call.room().cloned()
4143        } else {
4144            None
4145        };
4146        (should_prompt, open_room)
4147    })?;
4148
4149    if let Some(room) = open_room {
4150        let task = room.update(cx, |room, cx| {
4151            if let Some((project, host)) = room.most_active_project(cx) {
4152                return Some(join_remote_project(project, host, app_state.clone(), cx));
4153            }
4154
4155            None
4156        })?;
4157        if let Some(task) = task {
4158            task.await?;
4159        }
4160        return anyhow::Ok(true);
4161    }
4162
4163    if should_prompt {
4164        if let Some(workspace) = requesting_window {
4165            let answer = workspace
4166                .update(cx, |_, cx| {
4167                    cx.prompt(
4168                        PromptLevel::Warning,
4169                        "Do you want to switch channels?",
4170                        Some("Leaving this call will unshare your current project."),
4171                        &["Yes, Join Channel", "Cancel"],
4172                    )
4173                })?
4174                .await;
4175
4176            if answer == Ok(1) {
4177                return Ok(false);
4178            }
4179        } else {
4180            return Ok(false); // unreachable!() hopefully
4181        }
4182    }
4183
4184    let client = cx.update(|cx| active_call.read(cx).client())?;
4185
4186    let mut client_status = client.status();
4187
4188    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4189    'outer: loop {
4190        let Some(status) = client_status.recv().await else {
4191            return Err(anyhow!("error connecting"));
4192        };
4193
4194        match status {
4195            Status::Connecting
4196            | Status::Authenticating
4197            | Status::Reconnecting
4198            | Status::Reauthenticating => continue,
4199            Status::Connected { .. } => break 'outer,
4200            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4201            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4202            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4203                return Err(ErrorCode::Disconnected.into());
4204            }
4205        }
4206    }
4207
4208    let room = active_call
4209        .update(cx, |active_call, cx| {
4210            active_call.join_channel(channel_id, cx)
4211        })?
4212        .await?;
4213
4214    let Some(room) = room else {
4215        return anyhow::Ok(true);
4216    };
4217
4218    room.update(cx, |room, _| room.room_update_completed())?
4219        .await;
4220
4221    let task = room.update(cx, |room, cx| {
4222        if let Some((project, host)) = room.most_active_project(cx) {
4223            return Some(join_remote_project(project, host, app_state.clone(), cx));
4224        }
4225
4226        // if you are the first to join a channel, share your project
4227        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4228            if let Some(workspace) = requesting_window {
4229                let project = workspace.update(cx, |workspace, cx| {
4230                    if !CallSettings::get_global(cx).share_on_join {
4231                        return None;
4232                    }
4233                    let project = workspace.project.read(cx);
4234                    if project.is_local()
4235                        && project.visible_worktrees(cx).any(|tree| {
4236                            tree.read(cx)
4237                                .root_entry()
4238                                .map_or(false, |entry| entry.is_dir())
4239                        })
4240                    {
4241                        Some(workspace.project.clone())
4242                    } else {
4243                        None
4244                    }
4245                });
4246                if let Ok(Some(project)) = project {
4247                    return Some(cx.spawn(|room, mut cx| async move {
4248                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4249                            .await?;
4250                        Ok(())
4251                    }));
4252                }
4253            }
4254        }
4255
4256        None
4257    })?;
4258    if let Some(task) = task {
4259        task.await?;
4260        return anyhow::Ok(true);
4261    }
4262    anyhow::Ok(false)
4263}
4264
4265pub fn join_channel(
4266    channel_id: ChannelId,
4267    app_state: Arc<AppState>,
4268    requesting_window: Option<WindowHandle<Workspace>>,
4269    cx: &mut AppContext,
4270) -> Task<Result<()>> {
4271    let active_call = ActiveCall::global(cx);
4272    cx.spawn(|mut cx| async move {
4273        let result = join_channel_internal(
4274            channel_id,
4275            &app_state,
4276            requesting_window,
4277            &active_call,
4278            &mut cx,
4279        )
4280            .await;
4281
4282        // join channel succeeded, and opened a window
4283        if matches!(result, Ok(true)) {
4284            return anyhow::Ok(());
4285        }
4286
4287        // find an existing workspace to focus and show call controls
4288        let mut active_window =
4289            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4290        if active_window.is_none() {
4291            // no open workspaces, make one to show the error in (blergh)
4292            let (window_handle, _) = cx
4293                .update(|cx| {
4294                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4295                })?
4296                .await?;
4297
4298            if result.is_ok() {
4299                cx.update(|cx| {
4300                    cx.dispatch_action(&OpenChannelNotes);
4301                }).log_err();
4302            }
4303
4304            active_window = Some(window_handle);
4305        }
4306
4307        if let Err(err) = result {
4308            log::error!("failed to join channel: {}", err);
4309            if let Some(active_window) = active_window {
4310                active_window
4311                    .update(&mut cx, |_, cx| {
4312                        let detail: SharedString = match err.error_code() {
4313                            ErrorCode::SignedOut => {
4314                                "Please sign in to continue.".into()
4315                            }
4316                            ErrorCode::UpgradeRequired => {
4317                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4318                            }
4319                            ErrorCode::NoSuchChannel => {
4320                                "No matching channel was found. Please check the link and try again.".into()
4321                            }
4322                            ErrorCode::Forbidden => {
4323                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4324                            }
4325                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4326                            _ => format!("{}\n\nPlease try again.", err).into(),
4327                        };
4328                        cx.prompt(
4329                            PromptLevel::Critical,
4330                            "Failed to join channel",
4331                            Some(&detail),
4332                            &["Ok"],
4333                        )
4334                    })?
4335                    .await
4336                    .ok();
4337            }
4338        }
4339
4340        // return ok, we showed the error to the user.
4341        return anyhow::Ok(());
4342    })
4343}
4344
4345pub async fn get_any_active_workspace(
4346    app_state: Arc<AppState>,
4347    mut cx: AsyncAppContext,
4348) -> anyhow::Result<WindowHandle<Workspace>> {
4349    // find an existing workspace to focus and show call controls
4350    let active_window = activate_any_workspace_window(&mut cx);
4351    if active_window.is_none() {
4352        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4353            .await?;
4354    }
4355    activate_any_workspace_window(&mut cx).context("could not open zed")
4356}
4357
4358fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4359    cx.update(|cx| {
4360        for window in cx.windows() {
4361            if let Some(workspace_window) = window.downcast::<Workspace>() {
4362                workspace_window
4363                    .update(cx, |_, cx| cx.activate_window())
4364                    .ok();
4365                return Some(workspace_window);
4366            }
4367        }
4368        None
4369    })
4370    .ok()
4371    .flatten()
4372}
4373
4374#[allow(clippy::type_complexity)]
4375pub fn open_paths(
4376    abs_paths: &[PathBuf],
4377    app_state: &Arc<AppState>,
4378    requesting_window: Option<WindowHandle<Workspace>>,
4379    cx: &mut AppContext,
4380) -> Task<
4381    anyhow::Result<(
4382        WindowHandle<Workspace>,
4383        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4384    )>,
4385> {
4386    let app_state = app_state.clone();
4387    let abs_paths = abs_paths.to_vec();
4388    // Open paths in existing workspace if possible
4389    let existing = activate_workspace_for_project(cx, {
4390        let abs_paths = abs_paths.clone();
4391        move |project, cx| project.contains_paths(&abs_paths, cx)
4392    });
4393    cx.spawn(move |mut cx| async move {
4394        if let Some(existing) = existing {
4395            Ok((
4396                existing.clone(),
4397                existing
4398                    .update(&mut cx, |workspace, cx| {
4399                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4400                    })?
4401                    .await,
4402            ))
4403        } else {
4404            cx.update(move |cx| {
4405                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4406            })?
4407            .await
4408        }
4409    })
4410}
4411
4412pub fn open_new(
4413    app_state: &Arc<AppState>,
4414    cx: &mut AppContext,
4415    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4416) -> Task<()> {
4417    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4418    cx.spawn(|mut cx| async move {
4419        if let Some((workspace, opened_paths)) = task.await.log_err() {
4420            workspace
4421                .update(&mut cx, |workspace, cx| {
4422                    if opened_paths.is_empty() {
4423                        init(workspace, cx)
4424                    }
4425                })
4426                .log_err();
4427        }
4428    })
4429}
4430
4431pub fn create_and_open_local_file(
4432    path: &'static Path,
4433    cx: &mut ViewContext<Workspace>,
4434    default_content: impl 'static + Send + FnOnce() -> Rope,
4435) -> Task<Result<Box<dyn ItemHandle>>> {
4436    cx.spawn(|workspace, mut cx| async move {
4437        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4438        if !fs.is_file(path).await {
4439            fs.create_file(path, Default::default()).await?;
4440            fs.save(path, &default_content(), Default::default())
4441                .await?;
4442        }
4443
4444        let mut items = workspace
4445            .update(&mut cx, |workspace, cx| {
4446                workspace.with_local_workspace(cx, |workspace, cx| {
4447                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4448                })
4449            })?
4450            .await?
4451            .await;
4452
4453        let item = items.pop().flatten();
4454        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4455    })
4456}
4457
4458pub fn join_remote_project(
4459    project_id: u64,
4460    follow_user_id: u64,
4461    app_state: Arc<AppState>,
4462    cx: &mut AppContext,
4463) -> Task<Result<()>> {
4464    let windows = cx.windows();
4465    cx.spawn(|mut cx| async move {
4466        let existing_workspace = windows.into_iter().find_map(|window| {
4467            window.downcast::<Workspace>().and_then(|window| {
4468                window
4469                    .update(&mut cx, |workspace, cx| {
4470                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4471                            Some(window)
4472                        } else {
4473                            None
4474                        }
4475                    })
4476                    .unwrap_or(None)
4477            })
4478        });
4479
4480        let workspace = if let Some(existing_workspace) = existing_workspace {
4481            existing_workspace
4482        } else {
4483            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4484            let room = active_call
4485                .read_with(&cx, |call, _| call.room().cloned())?
4486                .ok_or_else(|| anyhow!("not in a call"))?;
4487            let project = room
4488                .update(&mut cx, |room, cx| {
4489                    room.join_project(
4490                        project_id,
4491                        app_state.languages.clone(),
4492                        app_state.fs.clone(),
4493                        cx,
4494                    )
4495                })?
4496                .await?;
4497
4498            let window_bounds_override = window_bounds_env_override(&cx);
4499            cx.update(|cx| {
4500                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4501                cx.open_window(options, |cx| {
4502                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4503                })
4504            })?
4505        };
4506
4507        workspace.update(&mut cx, |workspace, cx| {
4508            cx.activate(true);
4509            cx.activate_window();
4510
4511            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4512                let follow_peer_id = room
4513                    .read(cx)
4514                    .remote_participants()
4515                    .iter()
4516                    .find(|(_, participant)| participant.user.id == follow_user_id)
4517                    .map(|(_, p)| p.peer_id)
4518                    .or_else(|| {
4519                        // If we couldn't follow the given user, follow the host instead.
4520                        let collaborator = workspace
4521                            .project()
4522                            .read(cx)
4523                            .collaborators()
4524                            .values()
4525                            .find(|collaborator| collaborator.replica_id == 0)?;
4526                        Some(collaborator.peer_id)
4527                    });
4528
4529                if let Some(follow_peer_id) = follow_peer_id {
4530                    workspace.follow(follow_peer_id, cx);
4531                }
4532            }
4533        })?;
4534
4535        anyhow::Ok(())
4536    })
4537}
4538
4539pub fn restart(_: &Restart, cx: &mut AppContext) {
4540    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4541    let mut workspace_windows = cx
4542        .windows()
4543        .into_iter()
4544        .filter_map(|window| window.downcast::<Workspace>())
4545        .collect::<Vec<_>>();
4546
4547    // If multiple windows have unsaved changes, and need a save prompt,
4548    // prompt in the active window before switching to a different window.
4549    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4550
4551    let mut prompt = None;
4552    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4553        prompt = window
4554            .update(cx, |_, cx| {
4555                cx.prompt(
4556                    PromptLevel::Info,
4557                    "Are you sure you want to restart?",
4558                    None,
4559                    &["Restart", "Cancel"],
4560                )
4561            })
4562            .ok();
4563    }
4564
4565    cx.spawn(|mut cx| async move {
4566        if let Some(prompt) = prompt {
4567            let answer = prompt.await?;
4568            if answer != 0 {
4569                return Ok(());
4570            }
4571        }
4572
4573        // If the user cancels any save prompt, then keep the app open.
4574        for window in workspace_windows {
4575            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4576                workspace.prepare_to_close(true, cx)
4577            }) {
4578                if !should_close.await? {
4579                    return Ok(());
4580                }
4581            }
4582        }
4583
4584        cx.update(|cx| cx.restart())
4585    })
4586    .detach_and_log_err(cx);
4587}
4588
4589fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4590    let mut parts = value.split(',');
4591    let x: usize = parts.next()?.parse().ok()?;
4592    let y: usize = parts.next()?.parse().ok()?;
4593    Some(point((x as f64).into(), (y as f64).into()))
4594}
4595
4596fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4597    let mut parts = value.split(',');
4598    let width: usize = parts.next()?.parse().ok()?;
4599    let height: usize = parts.next()?.parse().ok()?;
4600    Some(size((width as f64).into(), (height as f64).into()))
4601}
4602
4603pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4604    (1.75 * cx.rem_size()).max(px(32.))
4605}
4606
4607struct DisconnectedOverlay;
4608
4609impl Element for DisconnectedOverlay {
4610    type State = AnyElement;
4611
4612    fn request_layout(
4613        &mut self,
4614        _: Option<Self::State>,
4615        cx: &mut ElementContext,
4616    ) -> (LayoutId, Self::State) {
4617        let mut background = cx.theme().colors().elevated_surface_background;
4618        background.fade_out(0.2);
4619        let mut overlay = div()
4620            .bg(background)
4621            .absolute()
4622            .left_0()
4623            .top(titlebar_height(cx))
4624            .size_full()
4625            .flex()
4626            .items_center()
4627            .justify_center()
4628            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4629            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4630            .child(Label::new(
4631                "Your connection to the remote project has been lost.",
4632            ))
4633            .into_any();
4634        (overlay.request_layout(cx), overlay)
4635    }
4636
4637    fn paint(
4638        &mut self,
4639        bounds: Bounds<Pixels>,
4640        overlay: &mut Self::State,
4641        cx: &mut ElementContext,
4642    ) {
4643        cx.with_z_index(u16::MAX, |cx| {
4644            cx.add_opaque_layer(bounds);
4645            overlay.paint(cx);
4646        })
4647    }
4648}
4649
4650impl IntoElement for DisconnectedOverlay {
4651    type Element = Self;
4652
4653    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4654        None
4655    }
4656
4657    fn into_element(self) -> Self::Element {
4658        self
4659    }
4660}
4661
4662#[cfg(test)]
4663mod tests {
4664    use std::{cell::RefCell, rc::Rc};
4665
4666    use super::*;
4667    use crate::{
4668        dock::{test::TestPanel, PanelEvent},
4669        item::{
4670            test::{TestItem, TestProjectItem},
4671            ItemEvent,
4672        },
4673    };
4674    use fs::FakeFs;
4675    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4676    use project::{Project, ProjectEntryId};
4677    use serde_json::json;
4678    use settings::SettingsStore;
4679
4680    #[gpui::test]
4681    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4682        init_test(cx);
4683
4684        let fs = FakeFs::new(cx.executor());
4685        let project = Project::test(fs, [], cx).await;
4686        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4687
4688        // Adding an item with no ambiguity renders the tab without detail.
4689        let item1 = cx.new_view(|cx| {
4690            let mut item = TestItem::new(cx);
4691            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4692            item
4693        });
4694        workspace.update(cx, |workspace, cx| {
4695            workspace.add_item(Box::new(item1.clone()), cx);
4696        });
4697        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4698
4699        // Adding an item that creates ambiguity increases the level of detail on
4700        // both tabs.
4701        let item2 = cx.new_view(|cx| {
4702            let mut item = TestItem::new(cx);
4703            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4704            item
4705        });
4706        workspace.update(cx, |workspace, cx| {
4707            workspace.add_item(Box::new(item2.clone()), cx);
4708        });
4709        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4710        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4711
4712        // Adding an item that creates ambiguity increases the level of detail only
4713        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4714        // we stop at the highest detail available.
4715        let item3 = cx.new_view(|cx| {
4716            let mut item = TestItem::new(cx);
4717            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4718            item
4719        });
4720        workspace.update(cx, |workspace, cx| {
4721            workspace.add_item(Box::new(item3.clone()), cx);
4722        });
4723        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4724        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4725        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4726    }
4727
4728    #[gpui::test]
4729    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4730        init_test(cx);
4731
4732        let fs = FakeFs::new(cx.executor());
4733        fs.insert_tree(
4734            "/root1",
4735            json!({
4736                "one.txt": "",
4737                "two.txt": "",
4738            }),
4739        )
4740        .await;
4741        fs.insert_tree(
4742            "/root2",
4743            json!({
4744                "three.txt": "",
4745            }),
4746        )
4747        .await;
4748
4749        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4750        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4751        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4752        let worktree_id = project.update(cx, |project, cx| {
4753            project.worktrees().next().unwrap().read(cx).id()
4754        });
4755
4756        let item1 = cx.new_view(|cx| {
4757            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4758        });
4759        let item2 = cx.new_view(|cx| {
4760            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4761        });
4762
4763        // Add an item to an empty pane
4764        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4765        project.update(cx, |project, cx| {
4766            assert_eq!(
4767                project.active_entry(),
4768                project
4769                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4770                    .map(|e| e.id)
4771            );
4772        });
4773        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4774
4775        // Add a second item to a non-empty pane
4776        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4777        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4778        project.update(cx, |project, cx| {
4779            assert_eq!(
4780                project.active_entry(),
4781                project
4782                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4783                    .map(|e| e.id)
4784            );
4785        });
4786
4787        // Close the active item
4788        pane.update(cx, |pane, cx| {
4789            pane.close_active_item(&Default::default(), cx).unwrap()
4790        })
4791        .await
4792        .unwrap();
4793        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4794        project.update(cx, |project, cx| {
4795            assert_eq!(
4796                project.active_entry(),
4797                project
4798                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4799                    .map(|e| e.id)
4800            );
4801        });
4802
4803        // Add a project folder
4804        project
4805            .update(cx, |project, cx| {
4806                project.find_or_create_local_worktree("/root2", true, cx)
4807            })
4808            .await
4809            .unwrap();
4810        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4811
4812        // Remove a project folder
4813        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4814        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4815    }
4816
4817    #[gpui::test]
4818    async fn test_close_window(cx: &mut TestAppContext) {
4819        init_test(cx);
4820
4821        let fs = FakeFs::new(cx.executor());
4822        fs.insert_tree("/root", json!({ "one": "" })).await;
4823
4824        let project = Project::test(fs, ["root".as_ref()], cx).await;
4825        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4826
4827        // When there are no dirty items, there's nothing to do.
4828        let item1 = cx.new_view(|cx| TestItem::new(cx));
4829        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4830        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4831        assert!(task.await.unwrap());
4832
4833        // When there are dirty untitled items, prompt to save each one. If the user
4834        // cancels any prompt, then abort.
4835        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4836        let item3 = cx.new_view(|cx| {
4837            TestItem::new(cx)
4838                .with_dirty(true)
4839                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4840        });
4841        workspace.update(cx, |w, cx| {
4842            w.add_item(Box::new(item2.clone()), cx);
4843            w.add_item(Box::new(item3.clone()), cx);
4844        });
4845        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4846        cx.executor().run_until_parked();
4847        cx.simulate_prompt_answer(2); // cancel save all
4848        cx.executor().run_until_parked();
4849        cx.simulate_prompt_answer(2); // cancel save all
4850        cx.executor().run_until_parked();
4851        assert!(!cx.has_pending_prompt());
4852        assert!(!task.await.unwrap());
4853    }
4854
4855    #[gpui::test]
4856    async fn test_close_pane_items(cx: &mut TestAppContext) {
4857        init_test(cx);
4858
4859        let fs = FakeFs::new(cx.executor());
4860
4861        let project = Project::test(fs, None, cx).await;
4862        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4863
4864        let item1 = cx.new_view(|cx| {
4865            TestItem::new(cx)
4866                .with_dirty(true)
4867                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4868        });
4869        let item2 = cx.new_view(|cx| {
4870            TestItem::new(cx)
4871                .with_dirty(true)
4872                .with_conflict(true)
4873                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4874        });
4875        let item3 = cx.new_view(|cx| {
4876            TestItem::new(cx)
4877                .with_dirty(true)
4878                .with_conflict(true)
4879                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4880        });
4881        let item4 = cx.new_view(|cx| {
4882            TestItem::new(cx)
4883                .with_dirty(true)
4884                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4885        });
4886        let pane = workspace.update(cx, |workspace, cx| {
4887            workspace.add_item(Box::new(item1.clone()), cx);
4888            workspace.add_item(Box::new(item2.clone()), cx);
4889            workspace.add_item(Box::new(item3.clone()), cx);
4890            workspace.add_item(Box::new(item4.clone()), cx);
4891            workspace.active_pane().clone()
4892        });
4893
4894        let close_items = pane.update(cx, |pane, cx| {
4895            pane.activate_item(1, true, true, cx);
4896            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4897            let item1_id = item1.item_id();
4898            let item3_id = item3.item_id();
4899            let item4_id = item4.item_id();
4900            pane.close_items(cx, SaveIntent::Close, move |id| {
4901                [item1_id, item3_id, item4_id].contains(&id)
4902            })
4903        });
4904        cx.executor().run_until_parked();
4905
4906        assert!(cx.has_pending_prompt());
4907        // Ignore "Save all" prompt
4908        cx.simulate_prompt_answer(2);
4909        cx.executor().run_until_parked();
4910        // There's a prompt to save item 1.
4911        pane.update(cx, |pane, _| {
4912            assert_eq!(pane.items_len(), 4);
4913            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4914        });
4915        // Confirm saving item 1.
4916        cx.simulate_prompt_answer(0);
4917        cx.executor().run_until_parked();
4918
4919        // Item 1 is saved. There's a prompt to save item 3.
4920        pane.update(cx, |pane, cx| {
4921            assert_eq!(item1.read(cx).save_count, 1);
4922            assert_eq!(item1.read(cx).save_as_count, 0);
4923            assert_eq!(item1.read(cx).reload_count, 0);
4924            assert_eq!(pane.items_len(), 3);
4925            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4926        });
4927        assert!(cx.has_pending_prompt());
4928
4929        // Cancel saving item 3.
4930        cx.simulate_prompt_answer(1);
4931        cx.executor().run_until_parked();
4932
4933        // Item 3 is reloaded. There's a prompt to save item 4.
4934        pane.update(cx, |pane, cx| {
4935            assert_eq!(item3.read(cx).save_count, 0);
4936            assert_eq!(item3.read(cx).save_as_count, 0);
4937            assert_eq!(item3.read(cx).reload_count, 1);
4938            assert_eq!(pane.items_len(), 2);
4939            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4940        });
4941        assert!(cx.has_pending_prompt());
4942
4943        // Confirm saving item 4.
4944        cx.simulate_prompt_answer(0);
4945        cx.executor().run_until_parked();
4946
4947        // There's a prompt for a path for item 4.
4948        cx.simulate_new_path_selection(|_| Some(Default::default()));
4949        close_items.await.unwrap();
4950
4951        // The requested items are closed.
4952        pane.update(cx, |pane, cx| {
4953            assert_eq!(item4.read(cx).save_count, 0);
4954            assert_eq!(item4.read(cx).save_as_count, 1);
4955            assert_eq!(item4.read(cx).reload_count, 0);
4956            assert_eq!(pane.items_len(), 1);
4957            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4958        });
4959    }
4960
4961    #[gpui::test]
4962    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4963        init_test(cx);
4964
4965        let fs = FakeFs::new(cx.executor());
4966        let project = Project::test(fs, [], cx).await;
4967        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4968
4969        // Create several workspace items with single project entries, and two
4970        // workspace items with multiple project entries.
4971        let single_entry_items = (0..=4)
4972            .map(|project_entry_id| {
4973                cx.new_view(|cx| {
4974                    TestItem::new(cx)
4975                        .with_dirty(true)
4976                        .with_project_items(&[TestProjectItem::new(
4977                            project_entry_id,
4978                            &format!("{project_entry_id}.txt"),
4979                            cx,
4980                        )])
4981                })
4982            })
4983            .collect::<Vec<_>>();
4984        let item_2_3 = cx.new_view(|cx| {
4985            TestItem::new(cx)
4986                .with_dirty(true)
4987                .with_singleton(false)
4988                .with_project_items(&[
4989                    single_entry_items[2].read(cx).project_items[0].clone(),
4990                    single_entry_items[3].read(cx).project_items[0].clone(),
4991                ])
4992        });
4993        let item_3_4 = cx.new_view(|cx| {
4994            TestItem::new(cx)
4995                .with_dirty(true)
4996                .with_singleton(false)
4997                .with_project_items(&[
4998                    single_entry_items[3].read(cx).project_items[0].clone(),
4999                    single_entry_items[4].read(cx).project_items[0].clone(),
5000                ])
5001        });
5002
5003        // Create two panes that contain the following project entries:
5004        //   left pane:
5005        //     multi-entry items:   (2, 3)
5006        //     single-entry items:  0, 1, 2, 3, 4
5007        //   right pane:
5008        //     single-entry items:  1
5009        //     multi-entry items:   (3, 4)
5010        let left_pane = workspace.update(cx, |workspace, cx| {
5011            let left_pane = workspace.active_pane().clone();
5012            workspace.add_item(Box::new(item_2_3.clone()), cx);
5013            for item in single_entry_items {
5014                workspace.add_item(Box::new(item), cx);
5015            }
5016            left_pane.update(cx, |pane, cx| {
5017                pane.activate_item(2, true, true, cx);
5018            });
5019
5020            let right_pane = workspace
5021                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5022                .unwrap();
5023
5024            right_pane.update(cx, |pane, cx| {
5025                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5026            });
5027
5028            left_pane
5029        });
5030
5031        cx.focus_view(&left_pane);
5032
5033        // When closing all of the items in the left pane, we should be prompted twice:
5034        // once for project entry 0, and once for project entry 2. Project entries 1,
5035        // 3, and 4 are all still open in the other paten. After those two
5036        // prompts, the task should complete.
5037
5038        let close = left_pane.update(cx, |pane, cx| {
5039            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5040        });
5041        cx.executor().run_until_parked();
5042
5043        // Discard "Save all" prompt
5044        cx.simulate_prompt_answer(2);
5045
5046        cx.executor().run_until_parked();
5047        left_pane.update(cx, |pane, cx| {
5048            assert_eq!(
5049                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5050                &[ProjectEntryId::from_proto(0)]
5051            );
5052        });
5053        cx.simulate_prompt_answer(0);
5054
5055        cx.executor().run_until_parked();
5056        left_pane.update(cx, |pane, cx| {
5057            assert_eq!(
5058                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5059                &[ProjectEntryId::from_proto(2)]
5060            );
5061        });
5062        cx.simulate_prompt_answer(0);
5063
5064        cx.executor().run_until_parked();
5065        close.await.unwrap();
5066        left_pane.update(cx, |pane, _| {
5067            assert_eq!(pane.items_len(), 0);
5068        });
5069    }
5070
5071    #[gpui::test]
5072    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5073        init_test(cx);
5074
5075        let fs = FakeFs::new(cx.executor());
5076        let project = Project::test(fs, [], cx).await;
5077        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5078        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5079
5080        let item = cx.new_view(|cx| {
5081            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5082        });
5083        let item_id = item.entity_id();
5084        workspace.update(cx, |workspace, cx| {
5085            workspace.add_item(Box::new(item.clone()), cx);
5086        });
5087
5088        // Autosave on window change.
5089        item.update(cx, |item, cx| {
5090            cx.update_global(|settings: &mut SettingsStore, cx| {
5091                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5092                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5093                })
5094            });
5095            item.is_dirty = true;
5096        });
5097
5098        // Deactivating the window saves the file.
5099        cx.deactivate_window();
5100        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5101
5102        // Autosave on focus change.
5103        item.update(cx, |item, cx| {
5104            cx.focus_self();
5105            cx.update_global(|settings: &mut SettingsStore, cx| {
5106                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5107                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5108                })
5109            });
5110            item.is_dirty = true;
5111        });
5112
5113        // Blurring the item saves the file.
5114        item.update(cx, |_, cx| cx.blur());
5115        cx.executor().run_until_parked();
5116        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5117
5118        // Deactivating the window still saves the file.
5119        cx.update(|cx| cx.activate_window());
5120        item.update(cx, |item, cx| {
5121            cx.focus_self();
5122            item.is_dirty = true;
5123        });
5124        cx.deactivate_window();
5125
5126        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5127
5128        // Autosave after delay.
5129        item.update(cx, |item, cx| {
5130            cx.update_global(|settings: &mut SettingsStore, cx| {
5131                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5132                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5133                })
5134            });
5135            item.is_dirty = true;
5136            cx.emit(ItemEvent::Edit);
5137        });
5138
5139        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5140        cx.executor().advance_clock(Duration::from_millis(250));
5141        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5142
5143        // After delay expires, the file is saved.
5144        cx.executor().advance_clock(Duration::from_millis(250));
5145        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5146
5147        // Autosave on focus change, ensuring closing the tab counts as such.
5148        item.update(cx, |item, cx| {
5149            cx.update_global(|settings: &mut SettingsStore, cx| {
5150                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5151                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5152                })
5153            });
5154            item.is_dirty = true;
5155        });
5156
5157        pane.update(cx, |pane, cx| {
5158            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5159        })
5160        .await
5161        .unwrap();
5162        assert!(!cx.has_pending_prompt());
5163        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5164
5165        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5166        workspace.update(cx, |workspace, cx| {
5167            workspace.add_item(Box::new(item.clone()), cx);
5168        });
5169        item.update(cx, |item, cx| {
5170            item.project_items[0].update(cx, |item, _| {
5171                item.entry_id = None;
5172            });
5173            item.is_dirty = true;
5174            cx.blur();
5175        });
5176        cx.run_until_parked();
5177        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5178
5179        // Ensure autosave is prevented for deleted files also when closing the buffer.
5180        let _close_items = pane.update(cx, |pane, cx| {
5181            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5182        });
5183        cx.run_until_parked();
5184        assert!(cx.has_pending_prompt());
5185        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5186    }
5187
5188    #[gpui::test]
5189    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5190        init_test(cx);
5191
5192        let fs = FakeFs::new(cx.executor());
5193
5194        let project = Project::test(fs, [], cx).await;
5195        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5196
5197        let item = cx.new_view(|cx| {
5198            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5199        });
5200        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5201        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5202        let toolbar_notify_count = Rc::new(RefCell::new(0));
5203
5204        workspace.update(cx, |workspace, cx| {
5205            workspace.add_item(Box::new(item.clone()), cx);
5206            let toolbar_notification_count = toolbar_notify_count.clone();
5207            cx.observe(&toolbar, move |_, _, _| {
5208                *toolbar_notification_count.borrow_mut() += 1
5209            })
5210            .detach();
5211        });
5212
5213        pane.update(cx, |pane, _| {
5214            assert!(!pane.can_navigate_backward());
5215            assert!(!pane.can_navigate_forward());
5216        });
5217
5218        item.update(cx, |item, cx| {
5219            item.set_state("one".to_string(), cx);
5220        });
5221
5222        // Toolbar must be notified to re-render the navigation buttons
5223        assert_eq!(*toolbar_notify_count.borrow(), 1);
5224
5225        pane.update(cx, |pane, _| {
5226            assert!(pane.can_navigate_backward());
5227            assert!(!pane.can_navigate_forward());
5228        });
5229
5230        workspace
5231            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5232            .await
5233            .unwrap();
5234
5235        assert_eq!(*toolbar_notify_count.borrow(), 2);
5236        pane.update(cx, |pane, _| {
5237            assert!(!pane.can_navigate_backward());
5238            assert!(pane.can_navigate_forward());
5239        });
5240    }
5241
5242    #[gpui::test]
5243    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5244        init_test(cx);
5245        let fs = FakeFs::new(cx.executor());
5246
5247        let project = Project::test(fs, [], cx).await;
5248        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5249
5250        let panel = workspace.update(cx, |workspace, cx| {
5251            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5252            workspace.add_panel(panel.clone(), cx);
5253
5254            workspace
5255                .right_dock()
5256                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5257
5258            panel
5259        });
5260
5261        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5262        pane.update(cx, |pane, cx| {
5263            let item = cx.new_view(|cx| TestItem::new(cx));
5264            pane.add_item(Box::new(item), true, true, None, cx);
5265        });
5266
5267        // Transfer focus from center to panel
5268        workspace.update(cx, |workspace, cx| {
5269            workspace.toggle_panel_focus::<TestPanel>(cx);
5270        });
5271
5272        workspace.update(cx, |workspace, cx| {
5273            assert!(workspace.right_dock().read(cx).is_open());
5274            assert!(!panel.is_zoomed(cx));
5275            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5276        });
5277
5278        // Transfer focus from panel to center
5279        workspace.update(cx, |workspace, cx| {
5280            workspace.toggle_panel_focus::<TestPanel>(cx);
5281        });
5282
5283        workspace.update(cx, |workspace, cx| {
5284            assert!(workspace.right_dock().read(cx).is_open());
5285            assert!(!panel.is_zoomed(cx));
5286            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5287        });
5288
5289        // Close the dock
5290        workspace.update(cx, |workspace, cx| {
5291            workspace.toggle_dock(DockPosition::Right, cx);
5292        });
5293
5294        workspace.update(cx, |workspace, cx| {
5295            assert!(!workspace.right_dock().read(cx).is_open());
5296            assert!(!panel.is_zoomed(cx));
5297            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5298        });
5299
5300        // Open the dock
5301        workspace.update(cx, |workspace, cx| {
5302            workspace.toggle_dock(DockPosition::Right, cx);
5303        });
5304
5305        workspace.update(cx, |workspace, cx| {
5306            assert!(workspace.right_dock().read(cx).is_open());
5307            assert!(!panel.is_zoomed(cx));
5308            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5309        });
5310
5311        // Focus and zoom panel
5312        panel.update(cx, |panel, cx| {
5313            cx.focus_self();
5314            panel.set_zoomed(true, 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        // Transfer focus to the center closes the dock
5324        workspace.update(cx, |workspace, cx| {
5325            workspace.toggle_panel_focus::<TestPanel>(cx);
5326        });
5327
5328        workspace.update(cx, |workspace, cx| {
5329            assert!(!workspace.right_dock().read(cx).is_open());
5330            assert!(panel.is_zoomed(cx));
5331            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5332        });
5333
5334        // Transferring focus back to the panel keeps it zoomed
5335        workspace.update(cx, |workspace, cx| {
5336            workspace.toggle_panel_focus::<TestPanel>(cx);
5337        });
5338
5339        workspace.update(cx, |workspace, cx| {
5340            assert!(workspace.right_dock().read(cx).is_open());
5341            assert!(panel.is_zoomed(cx));
5342            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5343        });
5344
5345        // Close the dock while it is zoomed
5346        workspace.update(cx, |workspace, cx| {
5347            workspace.toggle_dock(DockPosition::Right, cx)
5348        });
5349
5350        workspace.update(cx, |workspace, cx| {
5351            assert!(!workspace.right_dock().read(cx).is_open());
5352            assert!(panel.is_zoomed(cx));
5353            assert!(workspace.zoomed.is_none());
5354            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5355        });
5356
5357        // Opening the dock, when it's zoomed, retains focus
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_some());
5366            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5367        });
5368
5369        // Unzoom and close the panel, zoom the active pane.
5370        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5371        workspace.update(cx, |workspace, cx| {
5372            workspace.toggle_dock(DockPosition::Right, cx)
5373        });
5374        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5375
5376        // Opening a dock unzooms the pane.
5377        workspace.update(cx, |workspace, cx| {
5378            workspace.toggle_dock(DockPosition::Right, cx)
5379        });
5380        workspace.update(cx, |workspace, cx| {
5381            let pane = pane.read(cx);
5382            assert!(!pane.is_zoomed());
5383            assert!(!pane.focus_handle(cx).is_focused(cx));
5384            assert!(workspace.right_dock().read(cx).is_open());
5385            assert!(workspace.zoomed.is_none());
5386        });
5387    }
5388
5389    struct TestModal(FocusHandle);
5390
5391    impl TestModal {
5392        fn new(cx: &mut ViewContext<Self>) -> Self {
5393            Self(cx.focus_handle())
5394        }
5395    }
5396
5397    impl EventEmitter<DismissEvent> for TestModal {}
5398
5399    impl FocusableView for TestModal {
5400        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5401            self.0.clone()
5402        }
5403    }
5404
5405    impl ModalView for TestModal {}
5406
5407    impl Render for TestModal {
5408        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5409            div().track_focus(&self.0)
5410        }
5411    }
5412
5413    #[gpui::test]
5414    async fn test_panels(cx: &mut gpui::TestAppContext) {
5415        init_test(cx);
5416        let fs = FakeFs::new(cx.executor());
5417
5418        let project = Project::test(fs, [], cx).await;
5419        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5420
5421        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5422            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5423            workspace.add_panel(panel_1.clone(), cx);
5424            workspace
5425                .left_dock()
5426                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5427            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5428            workspace.add_panel(panel_2.clone(), cx);
5429            workspace
5430                .right_dock()
5431                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5432
5433            let left_dock = workspace.left_dock();
5434            assert_eq!(
5435                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5436                panel_1.panel_id()
5437            );
5438            assert_eq!(
5439                left_dock.read(cx).active_panel_size(cx).unwrap(),
5440                panel_1.size(cx)
5441            );
5442
5443            left_dock.update(cx, |left_dock, cx| {
5444                left_dock.resize_active_panel(Some(px(1337.)), cx)
5445            });
5446            assert_eq!(
5447                workspace
5448                    .right_dock()
5449                    .read(cx)
5450                    .visible_panel()
5451                    .unwrap()
5452                    .panel_id(),
5453                panel_2.panel_id(),
5454            );
5455
5456            (panel_1, panel_2)
5457        });
5458
5459        // Move panel_1 to the right
5460        panel_1.update(cx, |panel_1, cx| {
5461            panel_1.set_position(DockPosition::Right, cx)
5462        });
5463
5464        workspace.update(cx, |workspace, cx| {
5465            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5466            // Since it was the only panel on the left, the left dock should now be closed.
5467            assert!(!workspace.left_dock().read(cx).is_open());
5468            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5469            let right_dock = workspace.right_dock();
5470            assert_eq!(
5471                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5472                panel_1.panel_id()
5473            );
5474            assert_eq!(
5475                right_dock.read(cx).active_panel_size(cx).unwrap(),
5476                px(1337.)
5477            );
5478
5479            // Now we move panel_2 to the left
5480            panel_2.set_position(DockPosition::Left, cx);
5481        });
5482
5483        workspace.update(cx, |workspace, cx| {
5484            // Since panel_2 was not visible on the right, we don't open the left dock.
5485            assert!(!workspace.left_dock().read(cx).is_open());
5486            // And the right dock is unaffected in it's displaying of panel_1
5487            assert!(workspace.right_dock().read(cx).is_open());
5488            assert_eq!(
5489                workspace
5490                    .right_dock()
5491                    .read(cx)
5492                    .visible_panel()
5493                    .unwrap()
5494                    .panel_id(),
5495                panel_1.panel_id(),
5496            );
5497        });
5498
5499        // Move panel_1 back to the left
5500        panel_1.update(cx, |panel_1, cx| {
5501            panel_1.set_position(DockPosition::Left, cx)
5502        });
5503
5504        workspace.update(cx, |workspace, cx| {
5505            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5506            let left_dock = workspace.left_dock();
5507            assert!(left_dock.read(cx).is_open());
5508            assert_eq!(
5509                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5510                panel_1.panel_id()
5511            );
5512            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5513            // And the right dock should be closed as it no longer has any panels.
5514            assert!(!workspace.right_dock().read(cx).is_open());
5515
5516            // Now we move panel_1 to the bottom
5517            panel_1.set_position(DockPosition::Bottom, cx);
5518        });
5519
5520        workspace.update(cx, |workspace, cx| {
5521            // Since panel_1 was visible on the left, we close the left dock.
5522            assert!(!workspace.left_dock().read(cx).is_open());
5523            // The bottom dock is sized based on the panel's default size,
5524            // since the panel orientation changed from vertical to horizontal.
5525            let bottom_dock = workspace.bottom_dock();
5526            assert_eq!(
5527                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5528                panel_1.size(cx),
5529            );
5530            // Close bottom dock and move panel_1 back to the left.
5531            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5532            panel_1.set_position(DockPosition::Left, cx);
5533        });
5534
5535        // Emit activated event on panel 1
5536        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5537
5538        // Now the left dock is open and panel_1 is active and focused.
5539        workspace.update(cx, |workspace, cx| {
5540            let left_dock = workspace.left_dock();
5541            assert!(left_dock.read(cx).is_open());
5542            assert_eq!(
5543                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5544                panel_1.panel_id(),
5545            );
5546            assert!(panel_1.focus_handle(cx).is_focused(cx));
5547        });
5548
5549        // Emit closed event on panel 2, which is not active
5550        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5551
5552        // Wo don't close the left dock, because panel_2 wasn't the active panel
5553        workspace.update(cx, |workspace, cx| {
5554            let left_dock = workspace.left_dock();
5555            assert!(left_dock.read(cx).is_open());
5556            assert_eq!(
5557                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5558                panel_1.panel_id(),
5559            );
5560        });
5561
5562        // Emitting a ZoomIn event shows the panel as zoomed.
5563        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5564        workspace.update(cx, |workspace, _| {
5565            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5566            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5567        });
5568
5569        // Move panel to another dock while it is zoomed
5570        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5571        workspace.update(cx, |workspace, _| {
5572            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5573
5574            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5575        });
5576
5577        // This is a helper for getting a:
5578        // - valid focus on an element,
5579        // - that isn't a part of the panes and panels system of the Workspace,
5580        // - and doesn't trigger the 'on_focus_lost' API.
5581        let focus_other_view = {
5582            let workspace = workspace.clone();
5583            move |cx: &mut VisualTestContext| {
5584                workspace.update(cx, |workspace, cx| {
5585                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5586                        workspace.toggle_modal(cx, TestModal::new);
5587                        workspace.toggle_modal(cx, TestModal::new);
5588                    } else {
5589                        workspace.toggle_modal(cx, TestModal::new);
5590                    }
5591                })
5592            }
5593        };
5594
5595        // If focus is transferred to another view that's not a panel or another pane, we still show
5596        // the panel as zoomed.
5597        focus_other_view(cx);
5598        workspace.update(cx, |workspace, _| {
5599            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5600            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5601        });
5602
5603        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5604        workspace.update(cx, |_, cx| cx.focus_self());
5605        workspace.update(cx, |workspace, _| {
5606            assert_eq!(workspace.zoomed, None);
5607            assert_eq!(workspace.zoomed_position, None);
5608        });
5609
5610        // If focus is transferred again to another view that's not a panel or a pane, we won't
5611        // show the panel as zoomed because it wasn't zoomed before.
5612        focus_other_view(cx);
5613        workspace.update(cx, |workspace, _| {
5614            assert_eq!(workspace.zoomed, None);
5615            assert_eq!(workspace.zoomed_position, None);
5616        });
5617
5618        // When the panel is activated, it is zoomed again.
5619        cx.dispatch_action(ToggleRightDock);
5620        workspace.update(cx, |workspace, _| {
5621            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5622            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5623        });
5624
5625        // Emitting a ZoomOut event unzooms the panel.
5626        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5627        workspace.update(cx, |workspace, _| {
5628            assert_eq!(workspace.zoomed, None);
5629            assert_eq!(workspace.zoomed_position, None);
5630        });
5631
5632        // Emit closed event on panel 1, which is active
5633        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5634
5635        // Now the left dock is closed, because panel_1 was the active panel
5636        workspace.update(cx, |workspace, cx| {
5637            let right_dock = workspace.right_dock();
5638            assert!(!right_dock.read(cx).is_open());
5639        });
5640    }
5641
5642    pub fn init_test(cx: &mut TestAppContext) {
5643        cx.update(|cx| {
5644            let settings_store = SettingsStore::test(cx);
5645            cx.set_global(settings_store);
5646            theme::init(theme::LoadThemes::JustBase, cx);
5647            language::init(cx);
5648            crate::init_settings(cx);
5649            Project::init_settings(cx);
5650        });
5651    }
5652}