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