workspace.rs

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