workspace.rs

   1pub mod dock;
   2/// NOTE: Focus only 'takes' after an update has flushed_effects.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod item;
   7pub mod notifications;
   8pub mod pane;
   9pub mod pane_group;
  10mod persistence;
  11pub mod searchable;
  12pub mod shared_screen;
  13mod status_bar;
  14mod toolbar;
  15mod workspace_settings;
  16
  17use anyhow::{anyhow, Context, Result};
  18use assets::Assets;
  19use call::ActiveCall;
  20use client::{
  21    proto::{self, PeerId},
  22    Client, TypedEnvelope, UserStore,
  23};
  24use collections::{hash_map, HashMap, HashSet};
  25use drag_and_drop::DragAndDrop;
  26use futures::{
  27    channel::{mpsc, oneshot},
  28    future::try_join_all,
  29    FutureExt, StreamExt,
  30};
  31use gpui::{
  32    actions,
  33    elements::*,
  34    geometry::{
  35        rect::RectF,
  36        vector::{vec2f, Vector2F},
  37    },
  38    impl_actions,
  39    platform::{
  40        CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
  41        WindowOptions,
  42    },
  43    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  44    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  45    WeakViewHandle, WindowContext,
  46};
  47use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  48use itertools::Itertools;
  49use language::{LanguageRegistry, Rope};
  50use std::{
  51    any::TypeId,
  52    borrow::Cow,
  53    cmp, env,
  54    future::Future,
  55    path::{Path, PathBuf},
  56    str,
  57    sync::{atomic::AtomicUsize, Arc},
  58    time::Duration, rc::Rc,
  59};
  60
  61use crate::{
  62    notifications::simple_message_notification::MessageNotification,
  63    persistence::model::{
  64        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  65    },
  66};
  67use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  68use lazy_static::lazy_static;
  69use notifications::{NotificationHandle, NotifyResultExt};
  70pub use pane::*;
  71pub use pane_group::*;
  72use persistence::{model::SerializedItem, DB};
  73pub use persistence::{
  74    model::{ItemId, WorkspaceLocation},
  75    WorkspaceDb, DB as WORKSPACE_DB,
  76};
  77use postage::prelude::Stream;
  78use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  79use serde::Deserialize;
  80use shared_screen::SharedScreen;
  81use status_bar::StatusBar;
  82pub use status_bar::StatusItemView;
  83use theme::Theme;
  84pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  85use util::{async_iife, paths, ResultExt};
  86pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  87
  88lazy_static! {
  89    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  90        .ok()
  91        .as_deref()
  92        .and_then(parse_pixel_position_env_var);
  93    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  94        .ok()
  95        .as_deref()
  96        .and_then(parse_pixel_position_env_var);
  97}
  98
  99pub trait Modal: View {
 100    fn dismiss_on_event(event: &Self::Event) -> bool;
 101}
 102
 103#[derive(Clone, PartialEq)]
 104pub struct RemoveWorktreeFromProject(pub WorktreeId);
 105
 106actions!(
 107    workspace,
 108    [
 109        Open,
 110        NewFile,
 111        NewWindow,
 112        CloseWindow,
 113        AddFolderToProject,
 114        Unfollow,
 115        Save,
 116        SaveAs,
 117        SaveAll,
 118        ActivatePreviousPane,
 119        ActivateNextPane,
 120        FollowNextCollaborator,
 121        NewTerminal,
 122        NewCenterTerminal,
 123        ToggleTerminalFocus,
 124        NewSearch,
 125        Feedback,
 126        Restart,
 127        Welcome,
 128        ToggleZoom,
 129        ToggleLeftDock,
 130        ToggleRightDock,
 131        ToggleBottomDock,
 132    ]
 133);
 134
 135actions!(zed, [OpenSettings]);
 136
 137#[derive(Clone, PartialEq)]
 138pub struct OpenPaths {
 139    pub paths: Vec<PathBuf>,
 140}
 141
 142#[derive(Clone, Deserialize, PartialEq)]
 143pub struct ActivatePane(pub usize);
 144
 145pub struct Toast {
 146    id: usize,
 147    msg: Cow<'static, str>,
 148    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 149}
 150
 151impl Toast {
 152    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 153        Toast {
 154            id,
 155            msg: msg.into(),
 156            on_click: None,
 157        }
 158    }
 159
 160    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 161    where
 162        M: Into<Cow<'static, str>>,
 163        F: Fn(&mut WindowContext) + 'static,
 164    {
 165        self.on_click = Some((message.into(), Arc::new(on_click)));
 166        self
 167    }
 168}
 169
 170impl PartialEq for Toast {
 171    fn eq(&self, other: &Self) -> bool {
 172        self.id == other.id
 173            && self.msg == other.msg
 174            && self.on_click.is_some() == other.on_click.is_some()
 175    }
 176}
 177
 178impl Clone for Toast {
 179    fn clone(&self) -> Self {
 180        Toast {
 181            id: self.id,
 182            msg: self.msg.to_owned(),
 183            on_click: self.on_click.clone(),
 184        }
 185    }
 186}
 187
 188pub type WorkspaceId = i64;
 189
 190impl_actions!(workspace, [ActivatePane]);
 191
 192pub fn init_settings(cx: &mut AppContext) {
 193    settings::register::<WorkspaceSettings>(cx);
 194}
 195
 196pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 197    init_settings(cx);
 198    pane::init(cx);
 199    notifications::init(cx);
 200
 201    cx.add_global_action({
 202        let app_state = Arc::downgrade(&app_state);
 203        move |_: &Open, cx: &mut AppContext| {
 204            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 205                files: true,
 206                directories: true,
 207                multiple: true,
 208            });
 209
 210            if let Some(app_state) = app_state.upgrade() {
 211                cx.spawn(move |mut cx| async move {
 212                    if let Some(paths) = paths.recv().await.flatten() {
 213                        cx.update(|cx| {
 214                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 215                        });
 216                    }
 217                })
 218                .detach();
 219            }
 220        }
 221    });
 222    cx.add_async_action(Workspace::open);
 223
 224    cx.add_async_action(Workspace::follow_next_collaborator);
 225    cx.add_async_action(Workspace::close);
 226    cx.add_global_action(Workspace::close_global);
 227    cx.add_global_action(restart);
 228    cx.add_async_action(Workspace::save_all);
 229    cx.add_action(Workspace::add_folder_to_project);
 230    cx.add_action(
 231        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 232            let pane = workspace.active_pane().clone();
 233            workspace.unfollow(&pane, cx);
 234        },
 235    );
 236    cx.add_action(
 237        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 238            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 239        },
 240    );
 241    cx.add_action(
 242        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 243            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 244        },
 245    );
 246    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 247        workspace.activate_previous_pane(cx)
 248    });
 249    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 250        workspace.activate_next_pane(cx)
 251    });
 252    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 253        workspace.toggle_dock(DockPosition::Left, cx);
 254    });
 255    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 256        workspace.toggle_dock(DockPosition::Right, cx);
 257    });
 258    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 259        workspace.toggle_dock(DockPosition::Bottom, cx);
 260    });
 261    cx.add_action(Workspace::activate_pane_at_index);
 262    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 263        workspace.reopen_closed_item(cx).detach();
 264    });
 265    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 266        workspace
 267            .go_back(workspace.active_pane().downgrade(), cx)
 268            .detach();
 269    });
 270    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 271        workspace
 272            .go_forward(workspace.active_pane().downgrade(), cx)
 273            .detach();
 274    });
 275
 276    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 277        cx.spawn(|workspace, mut cx| async move {
 278            let err = install_cli::install_cli(&cx)
 279                .await
 280                .context("Failed to create CLI symlink");
 281
 282            workspace.update(&mut cx, |workspace, cx| {
 283                if matches!(err, Err(_)) {
 284                    err.notify_err(workspace, cx);
 285                } else {
 286                    workspace.show_notification(1, cx, |cx| {
 287                        cx.add_view(|_| {
 288                            MessageNotification::new("Successfully installed the `zed` binary")
 289                        })
 290                    });
 291                }
 292            })
 293        })
 294        .detach();
 295    });
 296
 297    cx.add_action(
 298        move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 299            create_and_open_local_file(&paths::SETTINGS, cx, || {
 300                settings::initial_user_settings_content(&Assets)
 301                    .as_ref()
 302                    .into()
 303            })
 304            .detach_and_log_err(cx);
 305        },
 306    );
 307
 308    let client = &app_state.client;
 309    client.add_view_request_handler(Workspace::handle_follow);
 310    client.add_view_message_handler(Workspace::handle_unfollow);
 311    client.add_view_message_handler(Workspace::handle_update_followers);
 312}
 313
 314type ProjectItemBuilders = HashMap<
 315    TypeId,
 316    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 317>;
 318pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 319    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 320        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 321            let item = model.downcast::<I::Item>().unwrap();
 322            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 323        });
 324    });
 325}
 326
 327type FollowableItemBuilder = fn(
 328    ViewHandle<Pane>,
 329    ModelHandle<Project>,
 330    ViewId,
 331    &mut Option<proto::view::Variant>,
 332    &mut AppContext,
 333) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 334type FollowableItemBuilders = HashMap<
 335    TypeId,
 336    (
 337        FollowableItemBuilder,
 338        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 339    ),
 340>;
 341pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 342    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 343        builders.insert(
 344            TypeId::of::<I>(),
 345            (
 346                |pane, project, id, state, cx| {
 347                    I::from_state_proto(pane, project, id, state, cx).map(|task| {
 348                        cx.foreground()
 349                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 350                    })
 351                },
 352                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 353            ),
 354        );
 355    });
 356}
 357
 358type ItemDeserializers = HashMap<
 359    Arc<str>,
 360    fn(
 361        ModelHandle<Project>,
 362        WeakViewHandle<Workspace>,
 363        WorkspaceId,
 364        ItemId,
 365        &mut ViewContext<Pane>,
 366    ) -> Task<Result<Box<dyn ItemHandle>>>,
 367>;
 368pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 369    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 370        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 371            deserializers.insert(
 372                Arc::from(serialized_item_kind),
 373                |project, workspace, workspace_id, item_id, cx| {
 374                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 375                    cx.foreground()
 376                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 377                },
 378            );
 379        }
 380    });
 381}
 382
 383pub struct AppState {
 384    pub languages: Arc<LanguageRegistry>,
 385    pub client: Arc<client::Client>,
 386    pub user_store: ModelHandle<client::UserStore>,
 387    pub fs: Arc<dyn fs::Fs>,
 388    pub build_window_options:
 389        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 390    pub initialize_workspace:
 391        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 392    pub background_actions: BackgroundActions,
 393}
 394
 395impl AppState {
 396    #[cfg(any(test, feature = "test-support"))]
 397    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 398        use settings::SettingsStore;
 399
 400        if !cx.has_global::<SettingsStore>() {
 401            cx.set_global(SettingsStore::test(cx));
 402        }
 403
 404        let fs = fs::FakeFs::new(cx.background().clone());
 405        let languages = Arc::new(LanguageRegistry::test());
 406        let http_client = util::http::FakeHttpClient::with_404_response();
 407        let client = Client::new(http_client.clone(), cx);
 408        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 409
 410        theme::init((), cx);
 411        client::init(&client, cx);
 412        crate::init_settings(cx);
 413
 414        Arc::new(Self {
 415            client,
 416            fs,
 417            languages,
 418            user_store,
 419            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 420            build_window_options: |_, _, _| Default::default(),
 421            background_actions: || &[],
 422        })
 423    }
 424}
 425
 426struct DelayedDebouncedEditAction {
 427    task: Option<Task<()>>,
 428    cancel_channel: Option<oneshot::Sender<()>>,
 429}
 430
 431impl DelayedDebouncedEditAction {
 432    fn new() -> DelayedDebouncedEditAction {
 433        DelayedDebouncedEditAction {
 434            task: None,
 435            cancel_channel: None,
 436        }
 437    }
 438
 439    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 440    where
 441        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 442    {
 443        if let Some(channel) = self.cancel_channel.take() {
 444            _ = channel.send(());
 445        }
 446
 447        let (sender, mut receiver) = oneshot::channel::<()>();
 448        self.cancel_channel = Some(sender);
 449
 450        let previous_task = self.task.take();
 451        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 452            let mut timer = cx.background().timer(delay).fuse();
 453            if let Some(previous_task) = previous_task {
 454                previous_task.await;
 455            }
 456
 457            futures::select_biased! {
 458                _ = receiver => return,
 459                    _ = timer => {}
 460            }
 461
 462            if let Some(result) = workspace
 463                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 464                .log_err()
 465            {
 466                result.await.log_err();
 467            }
 468        }));
 469    }
 470}
 471
 472pub enum Event {
 473    PaneAdded(ViewHandle<Pane>),
 474    ContactRequestedJoin(u64),
 475}
 476
 477pub struct Workspace {
 478    weak_self: WeakViewHandle<Self>,
 479    remote_entity_subscription: Option<client::Subscription>,
 480    modal: Option<AnyViewHandle>,
 481    zoomed: Option<AnyWeakViewHandle>,
 482    zoomed_position: Option<DockPosition>,
 483    center: PaneGroup,
 484    left_dock: ViewHandle<Dock>,
 485    bottom_dock: ViewHandle<Dock>,
 486    right_dock: ViewHandle<Dock>,
 487    panes: Vec<ViewHandle<Pane>>,
 488    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 489    active_pane: ViewHandle<Pane>,
 490    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 491    status_bar: ViewHandle<StatusBar>,
 492    titlebar_item: Option<AnyViewHandle>,
 493    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 494    project: ModelHandle<Project>,
 495    leader_state: LeaderState,
 496    follower_states_by_leader: FollowerStatesByLeader,
 497    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 498    window_edited: bool,
 499    active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
 500    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 501    database_id: WorkspaceId,
 502    app_state: Arc<AppState>,
 503    subscriptions: Vec<Subscription>,
 504    _apply_leader_updates: Task<Result<()>>,
 505    _observe_current_user: Task<Result<()>>,
 506    pane_history_timestamp: Arc<AtomicUsize>,
 507}
 508
 509#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 510pub struct ViewId {
 511    pub creator: PeerId,
 512    pub id: u64,
 513}
 514
 515#[derive(Default)]
 516struct LeaderState {
 517    followers: HashSet<PeerId>,
 518}
 519
 520type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 521
 522#[derive(Default)]
 523struct FollowerState {
 524    active_view_id: Option<ViewId>,
 525    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 526}
 527
 528impl Workspace {
 529    pub fn new(
 530        workspace_id: WorkspaceId,
 531        project: ModelHandle<Project>,
 532        app_state: Arc<AppState>,
 533        cx: &mut ViewContext<Self>,
 534    ) -> Self {
 535        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 536        cx.subscribe(&project, move |this, _, event, cx| {
 537            match event {
 538                project::Event::RemoteIdChanged(remote_id) => {
 539                    this.update_window_title(cx);
 540                    this.project_remote_id_changed(*remote_id, cx);
 541                }
 542
 543                project::Event::CollaboratorLeft(peer_id) => {
 544                    this.collaborator_left(*peer_id, cx);
 545                }
 546
 547                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 548                    this.update_window_title(cx);
 549                    this.serialize_workspace(cx);
 550                }
 551
 552                project::Event::DisconnectedFromHost => {
 553                    this.update_window_edited(cx);
 554                    cx.blur();
 555                }
 556
 557                project::Event::Closed => {
 558                    cx.remove_window();
 559                }
 560
 561                project::Event::DeletedEntry(entry_id) => {
 562                    for pane in this.panes.iter() {
 563                        pane.update(cx, |pane, cx| {
 564                            pane.handle_deleted_project_item(*entry_id, cx)
 565                        });
 566                    }
 567                }
 568
 569                _ => {}
 570            }
 571            cx.notify()
 572        })
 573        .detach();
 574
 575        let weak_handle = cx.weak_handle();
 576        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 577
 578        let center_pane = cx.add_view(|cx| {
 579            Pane::new(
 580                weak_handle.clone(),
 581                project.clone(),
 582                app_state.background_actions,
 583                pane_history_timestamp.clone(),
 584                cx,
 585            )
 586        });
 587        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 588        cx.focus(&center_pane);
 589        cx.emit(Event::PaneAdded(center_pane.clone()));
 590
 591        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 592        let mut connection_status = app_state.client.status();
 593        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 594            current_user.recv().await;
 595            connection_status.recv().await;
 596            let mut stream =
 597                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 598
 599            while stream.recv().await.is_some() {
 600                this.update(&mut cx, |_, cx| cx.notify())?;
 601            }
 602            anyhow::Ok(())
 603        });
 604
 605        // All leader updates are enqueued and then processed in a single task, so
 606        // that each asynchronous operation can be run in order.
 607        let (leader_updates_tx, mut leader_updates_rx) =
 608            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 609        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 610            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 611                Self::process_leader_update(&this, leader_id, update, &mut cx)
 612                    .await
 613                    .log_err();
 614            }
 615
 616            Ok(())
 617        });
 618
 619        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 620
 621        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 622        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 623        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 624        let left_dock_buttons =
 625            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 626        let bottom_dock_buttons =
 627            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 628        let right_dock_buttons =
 629            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 630        let status_bar = cx.add_view(|cx| {
 631            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 632            status_bar.add_left_item(left_dock_buttons, cx);
 633            status_bar.add_right_item(right_dock_buttons, cx);
 634            status_bar.add_right_item(bottom_dock_buttons, cx);
 635            status_bar
 636        });
 637
 638        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 639            drag_and_drop.register_container(weak_handle.clone());
 640        });
 641
 642        let mut active_call = None;
 643        if cx.has_global::<ModelHandle<ActiveCall>>() {
 644            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 645            let mut subscriptions = Vec::new();
 646            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 647            active_call = Some((call, subscriptions));
 648        }
 649
 650        let subscriptions = vec![
 651            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 652            cx.observe_window_activation(Self::on_window_activation_changed),
 653            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 654                // Transform fixed bounds to be stored in terms of the containing display
 655                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 656                    if let Some(screen) = cx.platform().screen_by_id(display) {
 657                        let screen_bounds = screen.bounds();
 658                        window_bounds
 659                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 660                        window_bounds
 661                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 662                        bounds = WindowBounds::Fixed(window_bounds);
 663                    }
 664                }
 665
 666                cx.background()
 667                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 668                    .detach_and_log_err(cx);
 669            }),
 670            cx.observe(&left_dock, |this, _, cx| {
 671                this.serialize_workspace(cx);
 672                cx.notify();
 673            }),
 674            cx.observe(&bottom_dock, |this, _, cx| {
 675                this.serialize_workspace(cx);
 676                cx.notify();
 677            }),
 678            cx.observe(&right_dock, |this, _, cx| {
 679                this.serialize_workspace(cx);
 680                cx.notify();
 681            }),
 682        ];
 683
 684        let mut this = Workspace {
 685            weak_self: weak_handle.clone(),
 686            modal: None,
 687            zoomed: None,
 688            zoomed_position: None,
 689            center: PaneGroup::new(center_pane.clone()),
 690            panes: vec![center_pane.clone()],
 691            panes_by_item: Default::default(),
 692            active_pane: center_pane.clone(),
 693            last_active_center_pane: Some(center_pane.downgrade()),
 694            status_bar,
 695            titlebar_item: None,
 696            notifications: Default::default(),
 697            remote_entity_subscription: None,
 698            left_dock,
 699            bottom_dock,
 700            right_dock,
 701            project: project.clone(),
 702            leader_state: Default::default(),
 703            follower_states_by_leader: Default::default(),
 704            last_leaders_by_pane: Default::default(),
 705            window_edited: false,
 706            active_call,
 707            database_id: workspace_id,
 708            app_state,
 709            _observe_current_user,
 710            _apply_leader_updates,
 711            leader_updates_tx,
 712            subscriptions,
 713            pane_history_timestamp,
 714        };
 715        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 716        cx.defer(|this, cx| this.update_window_title(cx));
 717        this
 718    }
 719
 720    fn new_local(
 721        abs_paths: Vec<PathBuf>,
 722        app_state: Arc<AppState>,
 723        requesting_window_id: Option<usize>,
 724        cx: &mut AppContext,
 725    ) -> Task<(
 726        WeakViewHandle<Workspace>,
 727        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 728    )> {
 729        let project_handle = Project::local(
 730            app_state.client.clone(),
 731            app_state.user_store.clone(),
 732            app_state.languages.clone(),
 733            app_state.fs.clone(),
 734            cx,
 735        );
 736
 737        cx.spawn(|mut cx| async move {
 738            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 739
 740            let paths_to_open = Arc::new(abs_paths);
 741
 742            // Get project paths for all of the abs_paths
 743            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 744            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 745                Vec::with_capacity(paths_to_open.len());
 746            for path in paths_to_open.iter().cloned() {
 747                if let Some((worktree, project_entry)) = cx
 748                    .update(|cx| {
 749                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 750                    })
 751                    .await
 752                    .log_err()
 753                {
 754                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 755                    project_paths.push((path, Some(project_entry)));
 756                } else {
 757                    project_paths.push((path, None));
 758                }
 759            }
 760
 761            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 762                serialized_workspace.id
 763            } else {
 764                DB.next_id().await.unwrap_or(0)
 765            };
 766
 767            let window_bounds_override =
 768                ZED_WINDOW_POSITION
 769                    .zip(*ZED_WINDOW_SIZE)
 770                    .map(|(position, size)| {
 771                        WindowBounds::Fixed(RectF::new(
 772                            cx.platform().screens()[0].bounds().origin() + position,
 773                            size,
 774                        ))
 775                    });
 776
 777            let build_workspace = |cx: &mut ViewContext<Workspace>| {
 778                Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 779            };
 780
 781            let workspace = requesting_window_id
 782                .and_then(|window_id| {
 783                    cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
 784                })
 785                .unwrap_or_else(|| {
 786                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 787                        (Some(bounds), None)
 788                    } else {
 789                        serialized_workspace
 790                            .as_ref()
 791                            .and_then(|serialized_workspace| {
 792                                let display = serialized_workspace.display?;
 793                                let mut bounds = serialized_workspace.bounds?;
 794
 795                                // Stored bounds are relative to the containing display.
 796                                // So convert back to global coordinates if that screen still exists
 797                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 798                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 799                                        let screen_bounds = screen.bounds();
 800                                        window_bounds.set_origin_x(
 801                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 802                                        );
 803                                        window_bounds.set_origin_y(
 804                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 805                                        );
 806                                        bounds = WindowBounds::Fixed(window_bounds);
 807                                    } else {
 808                                        // Screen no longer exists. Return none here.
 809                                        return None;
 810                                    }
 811                                }
 812
 813                                Some((bounds, display))
 814                            })
 815                            .unzip()
 816                    };
 817
 818                    // Use the serialized workspace to construct the new window
 819                    cx.add_window(
 820                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 821                        |cx| build_workspace(cx),
 822                    )
 823                    .1
 824                });
 825
 826            (app_state.initialize_workspace)(
 827                workspace.downgrade(),
 828                serialized_workspace.is_some(),
 829                app_state.clone(),
 830                cx.clone(),
 831            )
 832            .await
 833            .log_err();
 834
 835            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
 836
 837            let workspace = workspace.downgrade();
 838            notify_if_database_failed(&workspace, &mut cx);
 839            let opened_items = open_items(
 840                serialized_workspace,
 841                &workspace,
 842                project_paths,
 843                app_state,
 844                cx,
 845            )
 846            .await;
 847
 848            (workspace, opened_items)
 849        })
 850    }
 851
 852    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 853        self.weak_self.clone()
 854    }
 855
 856    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 857        &self.left_dock
 858    }
 859
 860    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 861        &self.bottom_dock
 862    }
 863
 864    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 865        &self.right_dock
 866    }
 867
 868    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
 869        let dock = match panel.position(cx) {
 870            DockPosition::Left => &self.left_dock,
 871            DockPosition::Bottom => &self.bottom_dock,
 872            DockPosition::Right => &self.right_dock,
 873        };
 874
 875        self.subscriptions.push(cx.subscribe(&panel, {
 876            let mut dock = dock.clone();
 877            let mut prev_position = panel.position(cx);
 878            move |this, panel, event, cx| {
 879                if T::should_change_position_on_event(event) {
 880                    let new_position = panel.read(cx).position(cx);
 881                    let mut was_visible = false;
 882                    dock.update(cx, |dock, cx| {
 883                        prev_position = new_position;
 884
 885                        was_visible = dock.is_open()
 886                            && dock
 887                                .visible_panel()
 888                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 889                        dock.remove_panel(&panel, cx);
 890                    });
 891
 892                    if panel.is_zoomed(cx) {
 893                        this.zoomed_position = Some(new_position);
 894                    }
 895
 896                    dock = match panel.read(cx).position(cx) {
 897                        DockPosition::Left => &this.left_dock,
 898                        DockPosition::Bottom => &this.bottom_dock,
 899                        DockPosition::Right => &this.right_dock,
 900                    }
 901                    .clone();
 902                    dock.update(cx, |dock, cx| {
 903                        dock.add_panel(panel.clone(), cx);
 904                        if was_visible {
 905                            dock.set_open(true, cx);
 906                            dock.activate_panel(dock.panels_len() - 1, cx);
 907                        }
 908                    });
 909                } else if T::should_zoom_in_on_event(event) {
 910                    this.zoom_out(cx);
 911                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
 912                    if panel.has_focus(cx) {
 913                        this.zoomed = Some(panel.downgrade().into_any());
 914                        this.zoomed_position = Some(panel.read(cx).position(cx));
 915                    }
 916                } else if T::should_zoom_out_on_event(event) {
 917                    this.zoom_out(cx);
 918                } else if T::is_focus_event(event) {
 919                    if panel.is_zoomed(cx) {
 920                        this.zoomed = Some(panel.downgrade().into_any());
 921                        this.zoomed_position = Some(panel.read(cx).position(cx));
 922                    } else {
 923                        this.zoomed = None;
 924                        this.zoomed_position = None;
 925                    }
 926                    cx.notify();
 927                }
 928            }
 929        }));
 930
 931        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 932    }
 933
 934    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 935        &self.status_bar
 936    }
 937
 938    pub fn app_state(&self) -> &Arc<AppState> {
 939        &self.app_state
 940    }
 941
 942    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 943        &self.app_state.user_store
 944    }
 945
 946    pub fn project(&self) -> &ModelHandle<Project> {
 947        &self.project
 948    }
 949
 950    pub fn recent_navigation_history(
 951        &self,
 952        limit: Option<usize>,
 953        cx: &AppContext,
 954    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 955        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 956        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 957        for pane in &self.panes {
 958            let pane = pane.read(cx);
 959            pane.nav_history()
 960                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 961                    if let Some(fs_path) = &fs_path {
 962                        abs_paths_opened
 963                            .entry(fs_path.clone())
 964                            .or_default()
 965                            .insert(project_path.clone());
 966                    }
 967                    let timestamp = entry.timestamp;
 968                    match history.entry(project_path) {
 969                        hash_map::Entry::Occupied(mut entry) => {
 970                            let (old_fs_path, old_timestamp) = entry.get();
 971                            if &timestamp > old_timestamp {
 972                                assert_eq!(&fs_path, old_fs_path, "Inconsistent nav history");
 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: WeakViewHandle<Pane>,
1010        mode: NavigationMode,
1011        cx: &mut ViewContext<Workspace>,
1012    ) -> Task<Result<()>> {
1013        let to_load = if let Some(pane) = pane.upgrade(cx) {
1014            cx.focus(&pane);
1015
1016            pane.update(cx, |pane, cx| {
1017                loop {
1018                    // Retrieve the weak item handle from the history.
1019                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1020
1021                    // If the item is still present in this pane, then activate it.
1022                    if let Some(index) = entry
1023                        .item
1024                        .upgrade(cx)
1025                        .and_then(|v| pane.index_for_item(v.as_ref()))
1026                    {
1027                        let prev_active_item_index = pane.active_item_index();
1028                        pane.nav_history_mut().set_mode(mode);
1029                        pane.activate_item(index, true, true, cx);
1030                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1031
1032                        let mut navigated = prev_active_item_index != pane.active_item_index();
1033                        if let Some(data) = entry.data {
1034                            navigated |= pane.active_item()?.navigate(data, cx);
1035                        }
1036
1037                        if navigated {
1038                            break None;
1039                        }
1040                    }
1041                    // If the item is no longer present in this pane, then retrieve its
1042                    // project path in order to reopen it.
1043                    else {
1044                        break pane
1045                            .nav_history()
1046                            .path_for_item(entry.item.id())
1047                            .map(|(project_path, _)| (project_path, entry));
1048                    }
1049                }
1050            })
1051        } else {
1052            None
1053        };
1054
1055        if let Some((project_path, entry)) = to_load {
1056            // If the item was no longer present, then load it again from its previous path.
1057            let task = self.load_path(project_path, cx);
1058            cx.spawn(|workspace, mut cx| async move {
1059                let task = task.await;
1060                let mut navigated = false;
1061                if let Some((project_entry_id, build_item)) = task.log_err() {
1062                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1063                        pane.nav_history_mut().set_mode(mode);
1064                        pane.active_item().map(|p| p.id())
1065                    })?;
1066
1067                    pane.update(&mut cx, |pane, cx| {
1068                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1069                        navigated |= Some(item.id()) != prev_active_item_id;
1070                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1071                        if let Some(data) = entry.data {
1072                            navigated |= item.navigate(data, cx);
1073                        }
1074                    })?;
1075                }
1076
1077                if !navigated {
1078                    workspace
1079                        .update(&mut cx, |workspace, cx| {
1080                            Self::navigate_history(workspace, pane, mode, cx)
1081                        })?
1082                        .await?;
1083                }
1084
1085                Ok(())
1086            })
1087        } else {
1088            Task::ready(Ok(()))
1089        }
1090    }
1091
1092    pub fn go_back(
1093        &mut self,
1094        pane: WeakViewHandle<Pane>,
1095        cx: &mut ViewContext<Workspace>,
1096    ) -> Task<Result<()>> {
1097        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1098    }
1099
1100    pub fn go_forward(
1101        &mut self,
1102        pane: WeakViewHandle<Pane>,
1103        cx: &mut ViewContext<Workspace>,
1104    ) -> Task<Result<()>> {
1105        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1106    }
1107
1108    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1109        self.navigate_history(
1110            self.active_pane().downgrade(),
1111            NavigationMode::ReopeningClosedItem,
1112            cx,
1113        )
1114    }
1115
1116    pub fn client(&self) -> &Client {
1117        &self.app_state.client
1118    }
1119
1120    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1121        self.titlebar_item = Some(item);
1122        cx.notify();
1123    }
1124
1125    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1126        self.titlebar_item.clone()
1127    }
1128
1129    /// Call the given callback with a workspace whose project is local.
1130    ///
1131    /// If the given workspace has a local project, then it will be passed
1132    /// to the callback. Otherwise, a new empty window will be created.
1133    pub fn with_local_workspace<T, F>(
1134        &mut self,
1135        cx: &mut ViewContext<Self>,
1136        callback: F,
1137    ) -> Task<Result<T>>
1138    where
1139        T: 'static,
1140        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1141    {
1142        if self.project.read(cx).is_local() {
1143            Task::Ready(Some(Ok(callback(self, cx))))
1144        } else {
1145            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1146            cx.spawn(|_vh, mut cx| async move {
1147                let (workspace, _) = task.await;
1148                workspace.update(&mut cx, callback)
1149            })
1150        }
1151    }
1152
1153    pub fn worktrees<'a>(
1154        &self,
1155        cx: &'a AppContext,
1156    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1157        self.project.read(cx).worktrees(cx)
1158    }
1159
1160    pub fn visible_worktrees<'a>(
1161        &self,
1162        cx: &'a AppContext,
1163    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1164        self.project.read(cx).visible_worktrees(cx)
1165    }
1166
1167    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1168        let futures = self
1169            .worktrees(cx)
1170            .filter_map(|worktree| worktree.read(cx).as_local())
1171            .map(|worktree| worktree.scan_complete())
1172            .collect::<Vec<_>>();
1173        async move {
1174            for future in futures {
1175                future.await;
1176            }
1177        }
1178    }
1179
1180    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1181        cx.spawn(|mut cx| async move {
1182            let id = cx
1183                .window_ids()
1184                .into_iter()
1185                .find(|&id| cx.window_is_active(id));
1186            if let Some(id) = id {
1187                //This can only get called when the window's project connection has been lost
1188                //so we don't need to prompt the user for anything and instead just close the window
1189                cx.remove_window(id);
1190            }
1191        })
1192        .detach();
1193    }
1194
1195    pub fn close(
1196        &mut self,
1197        _: &CloseWindow,
1198        cx: &mut ViewContext<Self>,
1199    ) -> Option<Task<Result<()>>> {
1200        let window_id = cx.window_id();
1201        let prepare = self.prepare_to_close(false, cx);
1202        Some(cx.spawn(|_, mut cx| async move {
1203            if prepare.await? {
1204                cx.remove_window(window_id);
1205            }
1206            Ok(())
1207        }))
1208    }
1209
1210    pub fn prepare_to_close(
1211        &mut self,
1212        quitting: bool,
1213        cx: &mut ViewContext<Self>,
1214    ) -> Task<Result<bool>> {
1215        let active_call = self.active_call().cloned();
1216        let window_id = cx.window_id();
1217
1218        cx.spawn(|this, mut cx| async move {
1219            let workspace_count = cx
1220                .window_ids()
1221                .into_iter()
1222                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1223                .count();
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 = cx.prompt(
1231                        window_id,
1232                        PromptLevel::Warning,
1233                        "Do you want to leave the current call?",
1234                        &["Close window and hang up", "Cancel"],
1235                    );
1236
1237                    if let Some(mut answer) = answer {
1238                        if answer.next().await == Some(1) {
1239                            return anyhow::Ok(false);
1240                        } else {
1241                            active_call
1242                                .update(&mut cx, |call, cx| call.hang_up(cx))
1243                                .await
1244                                .log_err();
1245                        }
1246                    }
1247                }
1248            }
1249
1250            Ok(this
1251                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1252                .await?)
1253        })
1254    }
1255
1256    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1257        let save_all = self.save_all_internal(false, cx);
1258        Some(cx.foreground().spawn(async move {
1259            save_all.await?;
1260            Ok(())
1261        }))
1262    }
1263
1264    fn save_all_internal(
1265        &mut self,
1266        should_prompt_to_save: bool,
1267        cx: &mut ViewContext<Self>,
1268    ) -> Task<Result<bool>> {
1269        if self.project.read(cx).is_read_only() {
1270            return Task::ready(Ok(true));
1271        }
1272
1273        let dirty_items = self
1274            .panes
1275            .iter()
1276            .flat_map(|pane| {
1277                pane.read(cx).items().filter_map(|item| {
1278                    if item.is_dirty(cx) {
1279                        Some((pane.downgrade(), item.boxed_clone()))
1280                    } else {
1281                        None
1282                    }
1283                })
1284            })
1285            .collect::<Vec<_>>();
1286
1287        let project = self.project.clone();
1288        cx.spawn(|_, mut cx| async move {
1289            for (pane, item) in dirty_items {
1290                let (singleton, project_entry_ids) =
1291                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1292                if singleton || !project_entry_ids.is_empty() {
1293                    if let Some(ix) =
1294                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1295                    {
1296                        if !Pane::save_item(
1297                            project.clone(),
1298                            &pane,
1299                            ix,
1300                            &*item,
1301                            should_prompt_to_save,
1302                            &mut cx,
1303                        )
1304                        .await?
1305                        {
1306                            return Ok(false);
1307                        }
1308                    }
1309                }
1310            }
1311            Ok(true)
1312        })
1313    }
1314
1315    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1316        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1317            files: true,
1318            directories: true,
1319            multiple: true,
1320        });
1321
1322        Some(cx.spawn(|this, mut cx| async move {
1323            if let Some(paths) = paths.recv().await.flatten() {
1324                if let Some(task) = this
1325                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1326                    .log_err()
1327                {
1328                    task.await?
1329                }
1330            }
1331            Ok(())
1332        }))
1333    }
1334
1335    pub fn open_workspace_for_paths(
1336        &mut self,
1337        paths: Vec<PathBuf>,
1338        cx: &mut ViewContext<Self>,
1339    ) -> Task<Result<()>> {
1340        let window_id = cx.window_id();
1341        let is_remote = self.project.read(cx).is_remote();
1342        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1343        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1344        let close_task = if is_remote || has_worktree || has_dirty_items {
1345            None
1346        } else {
1347            Some(self.prepare_to_close(false, cx))
1348        };
1349        let app_state = self.app_state.clone();
1350
1351        cx.spawn(|_, mut cx| async move {
1352            let window_id_to_replace = if let Some(close_task) = close_task {
1353                if !close_task.await? {
1354                    return Ok(());
1355                }
1356                Some(window_id)
1357            } else {
1358                None
1359            };
1360            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1361                .await?;
1362            Ok(())
1363        })
1364    }
1365
1366    #[allow(clippy::type_complexity)]
1367    pub fn open_paths(
1368        &mut self,
1369        mut abs_paths: Vec<PathBuf>,
1370        visible: bool,
1371        cx: &mut ViewContext<Self>,
1372    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1373        log::info!("open paths {:?}", abs_paths);
1374
1375        let fs = self.app_state.fs.clone();
1376
1377        // Sort the paths to ensure we add worktrees for parents before their children.
1378        abs_paths.sort_unstable();
1379        cx.spawn(|this, mut cx| async move {
1380            let mut project_paths = Vec::new();
1381            for path in &abs_paths {
1382                if let Some(project_path) = this
1383                    .update(&mut cx, |this, cx| {
1384                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1385                    })
1386                    .log_err()
1387                {
1388                    project_paths.push(project_path.await.log_err());
1389                } else {
1390                    project_paths.push(None);
1391                }
1392            }
1393
1394            let tasks = abs_paths
1395                .iter()
1396                .cloned()
1397                .zip(project_paths.into_iter())
1398                .map(|(abs_path, project_path)| {
1399                    let this = this.clone();
1400                    cx.spawn(|mut cx| {
1401                        let fs = fs.clone();
1402                        async move {
1403                            let (_worktree, project_path) = project_path?;
1404                            if fs.is_file(&abs_path).await {
1405                                Some(
1406                                    this.update(&mut cx, |this, cx| {
1407                                        this.open_path(project_path, None, true, cx)
1408                                    })
1409                                    .log_err()?
1410                                    .await,
1411                                )
1412                            } else {
1413                                None
1414                            }
1415                        }
1416                    })
1417                })
1418                .collect::<Vec<_>>();
1419
1420            futures::future::join_all(tasks).await
1421        })
1422    }
1423
1424    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1425        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1426            files: false,
1427            directories: true,
1428            multiple: true,
1429        });
1430        cx.spawn(|this, mut cx| async move {
1431            if let Some(paths) = paths.recv().await.flatten() {
1432                let results = this
1433                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1434                    .await;
1435                for result in results.into_iter().flatten() {
1436                    result.log_err();
1437                }
1438            }
1439            anyhow::Ok(())
1440        })
1441        .detach_and_log_err(cx);
1442    }
1443
1444    fn project_path_for_path(
1445        project: ModelHandle<Project>,
1446        abs_path: &Path,
1447        visible: bool,
1448        cx: &mut AppContext,
1449    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1450        let entry = project.update(cx, |project, cx| {
1451            project.find_or_create_local_worktree(abs_path, visible, cx)
1452        });
1453        cx.spawn(|cx| async move {
1454            let (worktree, path) = entry.await?;
1455            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1456            Ok((
1457                worktree,
1458                ProjectPath {
1459                    worktree_id,
1460                    path: path.into(),
1461                },
1462            ))
1463        })
1464    }
1465
1466    /// Returns the modal that was toggled closed if it was open.
1467    pub fn toggle_modal<V, F>(
1468        &mut self,
1469        cx: &mut ViewContext<Self>,
1470        add_view: F,
1471    ) -> Option<ViewHandle<V>>
1472    where
1473        V: 'static + Modal,
1474        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1475    {
1476        cx.notify();
1477        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1478        // it. Otherwise, create a new modal and set it as active.
1479        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1480        if let Some(already_open_modal) = already_open_modal {
1481            cx.focus_self();
1482            Some(already_open_modal)
1483        } else {
1484            let modal = add_view(self, cx);
1485            cx.subscribe(&modal, |this, _, event, cx| {
1486                if V::dismiss_on_event(event) {
1487                    this.dismiss_modal(cx);
1488                }
1489            })
1490            .detach();
1491            cx.focus(&modal);
1492            self.modal = Some(modal.into_any());
1493            None
1494        }
1495    }
1496
1497    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1498        self.modal
1499            .as_ref()
1500            .and_then(|modal| modal.clone().downcast::<V>())
1501    }
1502
1503    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1504        if self.modal.take().is_some() {
1505            cx.focus(&self.active_pane);
1506            cx.notify();
1507        }
1508    }
1509
1510    pub fn items<'a>(
1511        &'a self,
1512        cx: &'a AppContext,
1513    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1514        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1515    }
1516
1517    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1518        self.items_of_type(cx).max_by_key(|item| item.id())
1519    }
1520
1521    pub fn items_of_type<'a, T: Item>(
1522        &'a self,
1523        cx: &'a AppContext,
1524    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1525        self.panes
1526            .iter()
1527            .flat_map(|pane| pane.read(cx).items_of_type())
1528    }
1529
1530    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1531        self.active_pane().read(cx).active_item()
1532    }
1533
1534    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1535        self.active_item(cx).and_then(|item| item.project_path(cx))
1536    }
1537
1538    pub fn save_active_item(
1539        &mut self,
1540        force_name_change: bool,
1541        cx: &mut ViewContext<Self>,
1542    ) -> Task<Result<()>> {
1543        let project = self.project.clone();
1544        if let Some(item) = self.active_item(cx) {
1545            if !force_name_change && item.can_save(cx) {
1546                if item.has_conflict(cx) {
1547                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1548
1549                    let mut answer = cx.prompt(
1550                        PromptLevel::Warning,
1551                        CONFLICT_MESSAGE,
1552                        &["Overwrite", "Cancel"],
1553                    );
1554                    cx.spawn(|this, mut cx| async move {
1555                        let answer = answer.recv().await;
1556                        if answer == Some(0) {
1557                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1558                                .await?;
1559                        }
1560                        Ok(())
1561                    })
1562                } else {
1563                    item.save(self.project.clone(), cx)
1564                }
1565            } else if item.is_singleton(cx) {
1566                let worktree = self.worktrees(cx).next();
1567                let start_abs_path = worktree
1568                    .and_then(|w| w.read(cx).as_local())
1569                    .map_or(Path::new(""), |w| w.abs_path())
1570                    .to_path_buf();
1571                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1572                cx.spawn(|this, mut cx| async move {
1573                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1574                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1575                            .await?;
1576                    }
1577                    Ok(())
1578                })
1579            } else {
1580                Task::ready(Ok(()))
1581            }
1582        } else {
1583            Task::ready(Ok(()))
1584        }
1585    }
1586
1587    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1588        let dock = match dock_side {
1589            DockPosition::Left => &self.left_dock,
1590            DockPosition::Bottom => &self.bottom_dock,
1591            DockPosition::Right => &self.right_dock,
1592        };
1593        let mut focus_center = false;
1594        let mut zoom_out = false;
1595        dock.update(cx, |dock, cx| {
1596            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1597            let was_visible = dock.is_open() && !other_is_zoomed;
1598            dock.set_open(!was_visible, cx);
1599
1600            if let Some(active_panel) = dock.active_panel() {
1601                if was_visible {
1602                    if active_panel.has_focus(cx) {
1603                        focus_center = true;
1604                    }
1605                } else {
1606                    if active_panel.is_zoomed(cx) {
1607                        cx.focus(active_panel.as_any());
1608                    }
1609                    zoom_out = true;
1610                }
1611            }
1612        });
1613
1614        if zoom_out {
1615            self.zoom_out_everything_except(dock_side, cx);
1616        }
1617        if focus_center {
1618            cx.focus_self();
1619        }
1620
1621        cx.notify();
1622        self.serialize_workspace(cx);
1623    }
1624
1625    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1626        self.show_or_hide_panel::<T>(cx, |_, _| true)?.as_any().clone().downcast()
1627    }
1628
1629    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1630        self.show_or_hide_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1631    }
1632
1633    fn show_or_hide_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>, show: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool) -> Option<Rc<dyn PanelHandle>> {
1634        for (dock, position) in [
1635            self.left_dock.clone(),
1636            self.bottom_dock.clone(),
1637            self.right_dock.clone(),
1638        ]
1639        .into_iter()
1640        .zip(
1641            [
1642                DockPosition::Left,
1643                DockPosition::Bottom,
1644                DockPosition::Right,
1645            ]
1646            .into_iter(),
1647        ) {
1648            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1649                let mut focus_center = false;
1650                let mut zoom_out = false;
1651                let panel = dock.update(cx, |dock, cx| {
1652                    dock.activate_panel(panel_index, cx);
1653
1654                    let panel = dock.active_panel().cloned();
1655                    if let Some(panel) = panel.as_ref() {
1656                        let should_show = show(&**panel, cx);
1657                        if should_show {
1658                            dock.set_open(true, cx);
1659                            cx.focus(panel.as_any());
1660                            zoom_out = true;
1661                        } else {
1662                            if panel.is_zoomed(cx) {
1663                                dock.set_open(false, cx);
1664                            }
1665                            focus_center = true;
1666                        }
1667                    }
1668                    panel
1669                });
1670
1671                if zoom_out {
1672                    self.zoom_out_everything_except(position, cx);
1673                }
1674                if focus_center {
1675                    cx.focus_self();
1676                }
1677
1678                self.serialize_workspace(cx);
1679                cx.notify();
1680                return panel;
1681            }
1682        }
1683        None
1684    }
1685
1686    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1687        for pane in &self.panes {
1688            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1689        }
1690
1691        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1692        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1693        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1694        self.zoomed = None;
1695        self.zoomed_position = None;
1696
1697        cx.notify();
1698    }
1699
1700    fn zoom_out_everything_except(
1701        &mut self,
1702        except_position: DockPosition,
1703        cx: &mut ViewContext<Self>,
1704    ) {
1705        for pane in &self.panes {
1706            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1707        }
1708
1709        if except_position != DockPosition::Left {
1710            self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1711        }
1712
1713        if except_position != DockPosition::Bottom {
1714            self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1715        }
1716
1717        if except_position != DockPosition::Right {
1718            self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1719        }
1720
1721        if self.zoomed_position != Some(except_position) {
1722            self.zoomed = None;
1723            self.zoomed_position = None;
1724        }
1725
1726        cx.notify();
1727    }
1728
1729    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1730        let pane = cx.add_view(|cx| {
1731            Pane::new(
1732                self.weak_handle(),
1733                self.project.clone(),
1734                self.app_state.background_actions,
1735                self.pane_history_timestamp.clone(),
1736                cx,
1737            )
1738        });
1739        cx.subscribe(&pane, Self::handle_pane_event).detach();
1740        self.panes.push(pane.clone());
1741        cx.focus(&pane);
1742        cx.emit(Event::PaneAdded(pane.clone()));
1743        pane
1744    }
1745
1746    pub fn add_item_to_center(
1747        &mut self,
1748        item: Box<dyn ItemHandle>,
1749        cx: &mut ViewContext<Self>,
1750    ) -> bool {
1751        if let Some(center_pane) = self.last_active_center_pane.clone() {
1752            if let Some(center_pane) = center_pane.upgrade(cx) {
1753                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1754                true
1755            } else {
1756                false
1757            }
1758        } else {
1759            false
1760        }
1761    }
1762
1763    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1764        self.active_pane
1765            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1766    }
1767
1768    pub fn open_abs_path(
1769        &mut self,
1770        abs_path: PathBuf,
1771        visible: bool,
1772        cx: &mut ViewContext<Self>,
1773    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1774        cx.spawn(|workspace, mut cx| async move {
1775            let open_paths_task_result = workspace
1776                .update(&mut cx, |workspace, cx| {
1777                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1778                })
1779                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1780                .await;
1781            anyhow::ensure!(
1782                open_paths_task_result.len() == 1,
1783                "open abs path {abs_path:?} task returned incorrect number of results"
1784            );
1785            match open_paths_task_result
1786                .into_iter()
1787                .next()
1788                .expect("ensured single task result")
1789            {
1790                Some(open_result) => {
1791                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1792                }
1793                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1794            }
1795        })
1796    }
1797
1798    pub fn open_path(
1799        &mut self,
1800        path: impl Into<ProjectPath>,
1801        pane: Option<WeakViewHandle<Pane>>,
1802        focus_item: bool,
1803        cx: &mut ViewContext<Self>,
1804    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1805        let pane = pane.unwrap_or_else(|| {
1806            self.last_active_center_pane.clone().unwrap_or_else(|| {
1807                self.panes
1808                    .first()
1809                    .expect("There must be an active pane")
1810                    .downgrade()
1811            })
1812        });
1813
1814        let task = self.load_path(path.into(), cx);
1815        cx.spawn(|_, mut cx| async move {
1816            let (project_entry_id, build_item) = task.await?;
1817            pane.update(&mut cx, |pane, cx| {
1818                pane.open_item(project_entry_id, focus_item, cx, build_item)
1819            })
1820        })
1821    }
1822
1823    pub(crate) fn load_path(
1824        &mut self,
1825        path: ProjectPath,
1826        cx: &mut ViewContext<Self>,
1827    ) -> Task<
1828        Result<(
1829            ProjectEntryId,
1830            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1831        )>,
1832    > {
1833        let project = self.project().clone();
1834        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1835        cx.spawn(|_, mut cx| async move {
1836            let (project_entry_id, project_item) = project_item.await?;
1837            let build_item = cx.update(|cx| {
1838                cx.default_global::<ProjectItemBuilders>()
1839                    .get(&project_item.model_type())
1840                    .ok_or_else(|| anyhow!("no item builder for project item"))
1841                    .cloned()
1842            })?;
1843            let build_item =
1844                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1845            Ok((project_entry_id, build_item))
1846        })
1847    }
1848
1849    pub fn open_project_item<T>(
1850        &mut self,
1851        project_item: ModelHandle<T::Item>,
1852        cx: &mut ViewContext<Self>,
1853    ) -> ViewHandle<T>
1854    where
1855        T: ProjectItem,
1856    {
1857        use project::Item as _;
1858
1859        let entry_id = project_item.read(cx).entry_id(cx);
1860        if let Some(item) = entry_id
1861            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1862            .and_then(|item| item.downcast())
1863        {
1864            self.activate_item(&item, cx);
1865            return item;
1866        }
1867
1868        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1869        self.add_item(Box::new(item.clone()), cx);
1870        item
1871    }
1872
1873    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1874        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1875            self.active_pane.update(cx, |pane, cx| {
1876                pane.add_item(Box::new(shared_screen), false, true, None, cx)
1877            });
1878        }
1879    }
1880
1881    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1882        let result = self.panes.iter().find_map(|pane| {
1883            pane.read(cx)
1884                .index_for_item(item)
1885                .map(|ix| (pane.clone(), ix))
1886        });
1887        if let Some((pane, ix)) = result {
1888            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1889            true
1890        } else {
1891            false
1892        }
1893    }
1894
1895    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1896        let panes = self.center.panes();
1897        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1898            cx.focus(&pane);
1899        } else {
1900            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1901        }
1902    }
1903
1904    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1905        let panes = self.center.panes();
1906        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1907            let next_ix = (ix + 1) % panes.len();
1908            let next_pane = panes[next_ix].clone();
1909            cx.focus(&next_pane);
1910        }
1911    }
1912
1913    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1914        let panes = self.center.panes();
1915        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1916            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1917            let prev_pane = panes[prev_ix].clone();
1918            cx.focus(&prev_pane);
1919        }
1920    }
1921
1922    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1923        if self.active_pane != pane {
1924            self.active_pane = pane.clone();
1925            self.status_bar.update(cx, |status_bar, cx| {
1926                status_bar.set_active_pane(&self.active_pane, cx);
1927            });
1928            self.active_item_path_changed(cx);
1929            self.last_active_center_pane = Some(pane.downgrade());
1930        }
1931
1932        if pane.read(cx).is_zoomed() {
1933            self.zoomed = Some(pane.downgrade().into_any());
1934        } else {
1935            self.zoomed = None;
1936        }
1937        self.zoomed_position = None;
1938
1939        self.update_followers(
1940            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1941                id: self.active_item(cx).and_then(|item| {
1942                    item.to_followable_item_handle(cx)?
1943                        .remote_id(&self.app_state.client, cx)
1944                        .map(|id| id.to_proto())
1945                }),
1946                leader_id: self.leader_for_pane(&pane),
1947            }),
1948            cx,
1949        );
1950
1951        cx.notify();
1952    }
1953
1954    fn handle_pane_event(
1955        &mut self,
1956        pane: ViewHandle<Pane>,
1957        event: &pane::Event,
1958        cx: &mut ViewContext<Self>,
1959    ) {
1960        match event {
1961            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
1962            pane::Event::Split(direction) => {
1963                self.split_pane(pane, *direction, cx);
1964            }
1965            pane::Event::Remove => self.remove_pane(pane, cx),
1966            pane::Event::ActivateItem { local } => {
1967                if *local {
1968                    self.unfollow(&pane, cx);
1969                }
1970                if &pane == self.active_pane() {
1971                    self.active_item_path_changed(cx);
1972                }
1973            }
1974            pane::Event::ChangeItemTitle => {
1975                if pane == self.active_pane {
1976                    self.active_item_path_changed(cx);
1977                }
1978                self.update_window_edited(cx);
1979            }
1980            pane::Event::RemoveItem { item_id } => {
1981                self.update_window_edited(cx);
1982                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1983                    if entry.get().id() == pane.id() {
1984                        entry.remove();
1985                    }
1986                }
1987            }
1988            pane::Event::Focus => {
1989                self.handle_pane_focused(pane.clone(), cx);
1990            }
1991            pane::Event::ZoomIn => {
1992                if pane == self.active_pane {
1993                    self.zoom_out(cx);
1994                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
1995                    if pane.read(cx).has_focus() {
1996                        self.zoomed = Some(pane.downgrade().into_any());
1997                        self.zoomed_position = None;
1998                    }
1999                    cx.notify();
2000                }
2001            }
2002            pane::Event::ZoomOut => self.zoom_out(cx),
2003        }
2004
2005        self.serialize_workspace(cx);
2006    }
2007
2008    pub fn split_pane(
2009        &mut self,
2010        pane: ViewHandle<Pane>,
2011        direction: SplitDirection,
2012        cx: &mut ViewContext<Self>,
2013    ) -> Option<ViewHandle<Pane>> {
2014        let item = pane.read(cx).active_item()?;
2015        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2016            let new_pane = self.add_pane(cx);
2017            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2018            self.center.split(&pane, &new_pane, direction).unwrap();
2019            Some(new_pane)
2020        } else {
2021            None
2022        };
2023        cx.notify();
2024        maybe_pane_handle
2025    }
2026
2027    pub fn split_pane_with_item(
2028        &mut self,
2029        pane_to_split: WeakViewHandle<Pane>,
2030        split_direction: SplitDirection,
2031        from: WeakViewHandle<Pane>,
2032        item_id_to_move: usize,
2033        cx: &mut ViewContext<Self>,
2034    ) {
2035        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
2036        let Some(from) = from.upgrade(cx) else { return; };
2037
2038        let new_pane = self.add_pane(cx);
2039        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2040        self.center
2041            .split(&pane_to_split, &new_pane, split_direction)
2042            .unwrap();
2043        cx.notify();
2044    }
2045
2046    pub fn split_pane_with_project_entry(
2047        &mut self,
2048        pane_to_split: WeakViewHandle<Pane>,
2049        split_direction: SplitDirection,
2050        project_entry: ProjectEntryId,
2051        cx: &mut ViewContext<Self>,
2052    ) -> Option<Task<Result<()>>> {
2053        let pane_to_split = pane_to_split.upgrade(cx)?;
2054        let new_pane = self.add_pane(cx);
2055        self.center
2056            .split(&pane_to_split, &new_pane, split_direction)
2057            .unwrap();
2058
2059        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2060        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2061        Some(cx.foreground().spawn(async move {
2062            task.await?;
2063            Ok(())
2064        }))
2065    }
2066
2067    pub fn move_item(
2068        &mut self,
2069        source: ViewHandle<Pane>,
2070        destination: ViewHandle<Pane>,
2071        item_id_to_move: usize,
2072        destination_index: usize,
2073        cx: &mut ViewContext<Self>,
2074    ) {
2075        let item_to_move = source
2076            .read(cx)
2077            .items()
2078            .enumerate()
2079            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2080
2081        if item_to_move.is_none() {
2082            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2083            return;
2084        }
2085        let (item_ix, item_handle) = item_to_move.unwrap();
2086        let item_handle = item_handle.clone();
2087
2088        if source != destination {
2089            // Close item from previous pane
2090            source.update(cx, |source, cx| {
2091                source.remove_item(item_ix, false, cx);
2092            });
2093        }
2094
2095        // This automatically removes duplicate items in the pane
2096        destination.update(cx, |destination, cx| {
2097            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2098            cx.focus_self();
2099        });
2100    }
2101
2102    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2103        if self.center.remove(&pane).unwrap() {
2104            self.force_remove_pane(&pane, cx);
2105            self.unfollow(&pane, cx);
2106            self.last_leaders_by_pane.remove(&pane.downgrade());
2107            for removed_item in pane.read(cx).items() {
2108                self.panes_by_item.remove(&removed_item.id());
2109            }
2110
2111            cx.notify();
2112        } else {
2113            self.active_item_path_changed(cx);
2114        }
2115    }
2116
2117    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2118        &self.panes
2119    }
2120
2121    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2122        &self.active_pane
2123    }
2124
2125    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2126        if let Some(remote_id) = remote_id {
2127            self.remote_entity_subscription = Some(
2128                self.app_state
2129                    .client
2130                    .add_view_for_remote_entity(remote_id, cx),
2131            );
2132        } else {
2133            self.remote_entity_subscription.take();
2134        }
2135    }
2136
2137    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2138        self.leader_state.followers.remove(&peer_id);
2139        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2140            for state in states_by_pane.into_values() {
2141                for item in state.items_by_leader_view_id.into_values() {
2142                    item.set_leader_replica_id(None, cx);
2143                }
2144            }
2145        }
2146        cx.notify();
2147    }
2148
2149    pub fn toggle_follow(
2150        &mut self,
2151        leader_id: PeerId,
2152        cx: &mut ViewContext<Self>,
2153    ) -> Option<Task<Result<()>>> {
2154        let pane = self.active_pane().clone();
2155
2156        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2157            if leader_id == prev_leader_id {
2158                return None;
2159            }
2160        }
2161
2162        self.last_leaders_by_pane
2163            .insert(pane.downgrade(), leader_id);
2164        self.follower_states_by_leader
2165            .entry(leader_id)
2166            .or_default()
2167            .insert(pane.clone(), Default::default());
2168        cx.notify();
2169
2170        let project_id = self.project.read(cx).remote_id()?;
2171        let request = self.app_state.client.request(proto::Follow {
2172            project_id,
2173            leader_id: Some(leader_id),
2174        });
2175
2176        Some(cx.spawn(|this, mut cx| async move {
2177            let response = request.await?;
2178            this.update(&mut cx, |this, _| {
2179                let state = this
2180                    .follower_states_by_leader
2181                    .get_mut(&leader_id)
2182                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2183                    .ok_or_else(|| anyhow!("following interrupted"))?;
2184                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2185                    Some(ViewId::from_proto(active_view_id)?)
2186                } else {
2187                    None
2188                };
2189                Ok::<_, anyhow::Error>(())
2190            })??;
2191            Self::add_views_from_leader(
2192                this.clone(),
2193                leader_id,
2194                vec![pane],
2195                response.views,
2196                &mut cx,
2197            )
2198            .await?;
2199            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2200            Ok(())
2201        }))
2202    }
2203
2204    pub fn follow_next_collaborator(
2205        &mut self,
2206        _: &FollowNextCollaborator,
2207        cx: &mut ViewContext<Self>,
2208    ) -> Option<Task<Result<()>>> {
2209        let collaborators = self.project.read(cx).collaborators();
2210        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2211            let mut collaborators = collaborators.keys().copied();
2212            for peer_id in collaborators.by_ref() {
2213                if peer_id == leader_id {
2214                    break;
2215                }
2216            }
2217            collaborators.next()
2218        } else if let Some(last_leader_id) =
2219            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2220        {
2221            if collaborators.contains_key(last_leader_id) {
2222                Some(*last_leader_id)
2223            } else {
2224                None
2225            }
2226        } else {
2227            None
2228        };
2229
2230        next_leader_id
2231            .or_else(|| collaborators.keys().copied().next())
2232            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2233    }
2234
2235    pub fn unfollow(
2236        &mut self,
2237        pane: &ViewHandle<Pane>,
2238        cx: &mut ViewContext<Self>,
2239    ) -> Option<PeerId> {
2240        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2241            let leader_id = *leader_id;
2242            if let Some(state) = states_by_pane.remove(pane) {
2243                for (_, item) in state.items_by_leader_view_id {
2244                    item.set_leader_replica_id(None, cx);
2245                }
2246
2247                if states_by_pane.is_empty() {
2248                    self.follower_states_by_leader.remove(&leader_id);
2249                    if let Some(project_id) = self.project.read(cx).remote_id() {
2250                        self.app_state
2251                            .client
2252                            .send(proto::Unfollow {
2253                                project_id,
2254                                leader_id: Some(leader_id),
2255                            })
2256                            .log_err();
2257                    }
2258                }
2259
2260                cx.notify();
2261                return Some(leader_id);
2262            }
2263        }
2264        None
2265    }
2266
2267    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2268        self.follower_states_by_leader.contains_key(&peer_id)
2269    }
2270
2271    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2272        self.leader_state.followers.contains(&peer_id)
2273    }
2274
2275    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2276        // TODO: There should be a better system in place for this
2277        // (https://github.com/zed-industries/zed/issues/1290)
2278        let is_fullscreen = cx.window_is_fullscreen();
2279        let container_theme = if is_fullscreen {
2280            let mut container_theme = theme.workspace.titlebar.container;
2281            container_theme.padding.left = container_theme.padding.right;
2282            container_theme
2283        } else {
2284            theme.workspace.titlebar.container
2285        };
2286
2287        enum TitleBar {}
2288        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2289            Stack::new()
2290                .with_children(
2291                    self.titlebar_item
2292                        .as_ref()
2293                        .map(|item| ChildView::new(item, cx)),
2294                )
2295                .contained()
2296                .with_style(container_theme)
2297        })
2298        .on_click(MouseButton::Left, |event, _, cx| {
2299            if event.click_count == 2 {
2300                cx.zoom_window();
2301            }
2302        })
2303        .constrained()
2304        .with_height(theme.workspace.titlebar.height)
2305        .into_any_named("titlebar")
2306    }
2307
2308    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2309        let active_entry = self.active_project_path(cx);
2310        self.project
2311            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2312        self.update_window_title(cx);
2313    }
2314
2315    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2316        let project = self.project().read(cx);
2317        let mut title = String::new();
2318
2319        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2320            let filename = path
2321                .path
2322                .file_name()
2323                .map(|s| s.to_string_lossy())
2324                .or_else(|| {
2325                    Some(Cow::Borrowed(
2326                        project
2327                            .worktree_for_id(path.worktree_id, cx)?
2328                            .read(cx)
2329                            .root_name(),
2330                    ))
2331                });
2332
2333            if let Some(filename) = filename {
2334                title.push_str(filename.as_ref());
2335                title.push_str(" β€” ");
2336            }
2337        }
2338
2339        for (i, name) in project.worktree_root_names(cx).enumerate() {
2340            if i > 0 {
2341                title.push_str(", ");
2342            }
2343            title.push_str(name);
2344        }
2345
2346        if title.is_empty() {
2347            title = "empty project".to_string();
2348        }
2349
2350        if project.is_remote() {
2351            title.push_str(" ↙");
2352        } else if project.is_shared() {
2353            title.push_str(" β†—");
2354        }
2355
2356        cx.set_window_title(&title);
2357    }
2358
2359    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2360        let is_edited = !self.project.read(cx).is_read_only()
2361            && self
2362                .items(cx)
2363                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2364        if is_edited != self.window_edited {
2365            self.window_edited = is_edited;
2366            cx.set_window_edited(self.window_edited)
2367        }
2368    }
2369
2370    fn render_disconnected_overlay(
2371        &self,
2372        cx: &mut ViewContext<Workspace>,
2373    ) -> Option<AnyElement<Workspace>> {
2374        if self.project.read(cx).is_read_only() {
2375            enum DisconnectedOverlay {}
2376            Some(
2377                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2378                    let theme = &theme::current(cx);
2379                    Label::new(
2380                        "Your connection to the remote project has been lost.",
2381                        theme.workspace.disconnected_overlay.text.clone(),
2382                    )
2383                    .aligned()
2384                    .contained()
2385                    .with_style(theme.workspace.disconnected_overlay.container)
2386                })
2387                .with_cursor_style(CursorStyle::Arrow)
2388                .capture_all()
2389                .into_any_named("disconnected overlay"),
2390            )
2391        } else {
2392            None
2393        }
2394    }
2395
2396    fn render_notifications(
2397        &self,
2398        theme: &theme::Workspace,
2399        cx: &AppContext,
2400    ) -> Option<AnyElement<Workspace>> {
2401        if self.notifications.is_empty() {
2402            None
2403        } else {
2404            Some(
2405                Flex::column()
2406                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2407                        ChildView::new(notification.as_any(), cx)
2408                            .contained()
2409                            .with_style(theme.notification)
2410                    }))
2411                    .constrained()
2412                    .with_width(theme.notifications.width)
2413                    .contained()
2414                    .with_style(theme.notifications.container)
2415                    .aligned()
2416                    .bottom()
2417                    .right()
2418                    .into_any(),
2419            )
2420        }
2421    }
2422
2423    // RPC handlers
2424
2425    async fn handle_follow(
2426        this: WeakViewHandle<Self>,
2427        envelope: TypedEnvelope<proto::Follow>,
2428        _: Arc<Client>,
2429        mut cx: AsyncAppContext,
2430    ) -> Result<proto::FollowResponse> {
2431        this.update(&mut cx, |this, cx| {
2432            let client = &this.app_state.client;
2433            this.leader_state
2434                .followers
2435                .insert(envelope.original_sender_id()?);
2436
2437            let active_view_id = this.active_item(cx).and_then(|i| {
2438                Some(
2439                    i.to_followable_item_handle(cx)?
2440                        .remote_id(client, cx)?
2441                        .to_proto(),
2442                )
2443            });
2444
2445            cx.notify();
2446
2447            Ok(proto::FollowResponse {
2448                active_view_id,
2449                views: this
2450                    .panes()
2451                    .iter()
2452                    .flat_map(|pane| {
2453                        let leader_id = this.leader_for_pane(pane);
2454                        pane.read(cx).items().filter_map({
2455                            let cx = &cx;
2456                            move |item| {
2457                                let item = item.to_followable_item_handle(cx)?;
2458                                let id = item.remote_id(client, cx)?.to_proto();
2459                                let variant = item.to_state_proto(cx)?;
2460                                Some(proto::View {
2461                                    id: Some(id),
2462                                    leader_id,
2463                                    variant: Some(variant),
2464                                })
2465                            }
2466                        })
2467                    })
2468                    .collect(),
2469            })
2470        })?
2471    }
2472
2473    async fn handle_unfollow(
2474        this: WeakViewHandle<Self>,
2475        envelope: TypedEnvelope<proto::Unfollow>,
2476        _: Arc<Client>,
2477        mut cx: AsyncAppContext,
2478    ) -> Result<()> {
2479        this.update(&mut cx, |this, cx| {
2480            this.leader_state
2481                .followers
2482                .remove(&envelope.original_sender_id()?);
2483            cx.notify();
2484            Ok(())
2485        })?
2486    }
2487
2488    async fn handle_update_followers(
2489        this: WeakViewHandle<Self>,
2490        envelope: TypedEnvelope<proto::UpdateFollowers>,
2491        _: Arc<Client>,
2492        cx: AsyncAppContext,
2493    ) -> Result<()> {
2494        let leader_id = envelope.original_sender_id()?;
2495        this.read_with(&cx, |this, _| {
2496            this.leader_updates_tx
2497                .unbounded_send((leader_id, envelope.payload))
2498        })??;
2499        Ok(())
2500    }
2501
2502    async fn process_leader_update(
2503        this: &WeakViewHandle<Self>,
2504        leader_id: PeerId,
2505        update: proto::UpdateFollowers,
2506        cx: &mut AsyncAppContext,
2507    ) -> Result<()> {
2508        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2509            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2510                this.update(cx, |this, _| {
2511                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2512                        for state in state.values_mut() {
2513                            state.active_view_id =
2514                                if let Some(active_view_id) = update_active_view.id.clone() {
2515                                    Some(ViewId::from_proto(active_view_id)?)
2516                                } else {
2517                                    None
2518                                };
2519                        }
2520                    }
2521                    anyhow::Ok(())
2522                })??;
2523            }
2524            proto::update_followers::Variant::UpdateView(update_view) => {
2525                let variant = update_view
2526                    .variant
2527                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2528                let id = update_view
2529                    .id
2530                    .ok_or_else(|| anyhow!("missing update view id"))?;
2531                let mut tasks = Vec::new();
2532                this.update(cx, |this, cx| {
2533                    let project = this.project.clone();
2534                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2535                        for state in state.values_mut() {
2536                            let view_id = ViewId::from_proto(id.clone())?;
2537                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2538                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2539                            }
2540                        }
2541                    }
2542                    anyhow::Ok(())
2543                })??;
2544                try_join_all(tasks).await.log_err();
2545            }
2546            proto::update_followers::Variant::CreateView(view) => {
2547                let panes = this.read_with(cx, |this, _| {
2548                    this.follower_states_by_leader
2549                        .get(&leader_id)
2550                        .into_iter()
2551                        .flat_map(|states_by_pane| states_by_pane.keys())
2552                        .cloned()
2553                        .collect()
2554                })?;
2555                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2556            }
2557        }
2558        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2559        Ok(())
2560    }
2561
2562    async fn add_views_from_leader(
2563        this: WeakViewHandle<Self>,
2564        leader_id: PeerId,
2565        panes: Vec<ViewHandle<Pane>>,
2566        views: Vec<proto::View>,
2567        cx: &mut AsyncAppContext,
2568    ) -> Result<()> {
2569        let project = this.read_with(cx, |this, _| this.project.clone())?;
2570        let replica_id = project
2571            .read_with(cx, |project, _| {
2572                project
2573                    .collaborators()
2574                    .get(&leader_id)
2575                    .map(|c| c.replica_id)
2576            })
2577            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2578
2579        let item_builders = cx.update(|cx| {
2580            cx.default_global::<FollowableItemBuilders>()
2581                .values()
2582                .map(|b| b.0)
2583                .collect::<Vec<_>>()
2584        });
2585
2586        let mut item_tasks_by_pane = HashMap::default();
2587        for pane in panes {
2588            let mut item_tasks = Vec::new();
2589            let mut leader_view_ids = Vec::new();
2590            for view in &views {
2591                let Some(id) = &view.id else { continue };
2592                let id = ViewId::from_proto(id.clone())?;
2593                let mut variant = view.variant.clone();
2594                if variant.is_none() {
2595                    Err(anyhow!("missing variant"))?;
2596                }
2597                for build_item in &item_builders {
2598                    let task = cx.update(|cx| {
2599                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2600                    });
2601                    if let Some(task) = task {
2602                        item_tasks.push(task);
2603                        leader_view_ids.push(id);
2604                        break;
2605                    } else {
2606                        assert!(variant.is_some());
2607                    }
2608                }
2609            }
2610
2611            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2612        }
2613
2614        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2615            let items = futures::future::try_join_all(item_tasks).await?;
2616            this.update(cx, |this, cx| {
2617                let state = this
2618                    .follower_states_by_leader
2619                    .get_mut(&leader_id)?
2620                    .get_mut(&pane)?;
2621
2622                for (id, item) in leader_view_ids.into_iter().zip(items) {
2623                    item.set_leader_replica_id(Some(replica_id), cx);
2624                    state.items_by_leader_view_id.insert(id, item);
2625                }
2626
2627                Some(())
2628            })?;
2629        }
2630        Ok(())
2631    }
2632
2633    fn update_followers(
2634        &self,
2635        update: proto::update_followers::Variant,
2636        cx: &AppContext,
2637    ) -> Option<()> {
2638        let project_id = self.project.read(cx).remote_id()?;
2639        if !self.leader_state.followers.is_empty() {
2640            self.app_state
2641                .client
2642                .send(proto::UpdateFollowers {
2643                    project_id,
2644                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2645                    variant: Some(update),
2646                })
2647                .log_err();
2648        }
2649        None
2650    }
2651
2652    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2653        self.follower_states_by_leader
2654            .iter()
2655            .find_map(|(leader_id, state)| {
2656                if state.contains_key(pane) {
2657                    Some(*leader_id)
2658                } else {
2659                    None
2660                }
2661            })
2662    }
2663
2664    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2665        cx.notify();
2666
2667        let call = self.active_call()?;
2668        let room = call.read(cx).room()?.read(cx);
2669        let participant = room.remote_participant_for_peer_id(leader_id)?;
2670        let mut items_to_activate = Vec::new();
2671        match participant.location {
2672            call::ParticipantLocation::SharedProject { project_id } => {
2673                if Some(project_id) == self.project.read(cx).remote_id() {
2674                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2675                        if let Some(item) = state
2676                            .active_view_id
2677                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2678                        {
2679                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2680                        } else {
2681                            if let Some(shared_screen) =
2682                                self.shared_screen_for_peer(leader_id, pane, cx)
2683                            {
2684                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2685                            }
2686                        }
2687                    }
2688                }
2689            }
2690            call::ParticipantLocation::UnsharedProject => {}
2691            call::ParticipantLocation::External => {
2692                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2693                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2694                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2695                    }
2696                }
2697            }
2698        }
2699
2700        for (pane, item) in items_to_activate {
2701            let pane_was_focused = pane.read(cx).has_focus();
2702            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2703                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2704            } else {
2705                pane.update(cx, |pane, cx| {
2706                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2707                });
2708            }
2709
2710            if pane_was_focused {
2711                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2712            }
2713        }
2714
2715        None
2716    }
2717
2718    fn shared_screen_for_peer(
2719        &self,
2720        peer_id: PeerId,
2721        pane: &ViewHandle<Pane>,
2722        cx: &mut ViewContext<Self>,
2723    ) -> Option<ViewHandle<SharedScreen>> {
2724        let call = self.active_call()?;
2725        let room = call.read(cx).room()?.read(cx);
2726        let participant = room.remote_participant_for_peer_id(peer_id)?;
2727        let track = participant.tracks.values().next()?.clone();
2728        let user = participant.user.clone();
2729
2730        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2731            if item.read(cx).peer_id == peer_id {
2732                return Some(item);
2733            }
2734        }
2735
2736        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2737    }
2738
2739    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2740        if active {
2741            cx.background()
2742                .spawn(persistence::DB.update_timestamp(self.database_id()))
2743                .detach();
2744        } else {
2745            for pane in &self.panes {
2746                pane.update(cx, |pane, cx| {
2747                    if let Some(item) = pane.active_item() {
2748                        item.workspace_deactivated(cx);
2749                    }
2750                    if matches!(
2751                        settings::get::<WorkspaceSettings>(cx).autosave,
2752                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2753                    ) {
2754                        for item in pane.items() {
2755                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2756                                .detach_and_log_err(cx);
2757                        }
2758                    }
2759                });
2760            }
2761        }
2762    }
2763
2764    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2765        self.active_call.as_ref().map(|(call, _)| call)
2766    }
2767
2768    fn on_active_call_event(
2769        &mut self,
2770        _: ModelHandle<ActiveCall>,
2771        event: &call::room::Event,
2772        cx: &mut ViewContext<Self>,
2773    ) {
2774        match event {
2775            call::room::Event::ParticipantLocationChanged { participant_id }
2776            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2777                self.leader_updated(*participant_id, cx);
2778            }
2779            _ => {}
2780        }
2781    }
2782
2783    pub fn database_id(&self) -> WorkspaceId {
2784        self.database_id
2785    }
2786
2787    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2788        let project = self.project().read(cx);
2789
2790        if project.is_local() {
2791            Some(
2792                project
2793                    .visible_worktrees(cx)
2794                    .map(|worktree| worktree.read(cx).abs_path())
2795                    .collect::<Vec<_>>()
2796                    .into(),
2797            )
2798        } else {
2799            None
2800        }
2801    }
2802
2803    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2804        match member {
2805            Member::Axis(PaneAxis { members, .. }) => {
2806                for child in members.iter() {
2807                    self.remove_panes(child.clone(), cx)
2808                }
2809            }
2810            Member::Pane(pane) => {
2811                self.force_remove_pane(&pane, cx);
2812            }
2813        }
2814    }
2815
2816    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2817        self.panes.retain(|p| p != pane);
2818        cx.focus(self.panes.last().unwrap());
2819        if self.last_active_center_pane == Some(pane.downgrade()) {
2820            self.last_active_center_pane = None;
2821        }
2822        cx.notify();
2823    }
2824
2825    fn serialize_workspace(&self, cx: &AppContext) {
2826        fn serialize_pane_handle(
2827            pane_handle: &ViewHandle<Pane>,
2828            cx: &AppContext,
2829        ) -> SerializedPane {
2830            let (items, active) = {
2831                let pane = pane_handle.read(cx);
2832                let active_item_id = pane.active_item().map(|item| item.id());
2833                (
2834                    pane.items()
2835                        .filter_map(|item_handle| {
2836                            Some(SerializedItem {
2837                                kind: Arc::from(item_handle.serialized_item_kind()?),
2838                                item_id: item_handle.id(),
2839                                active: Some(item_handle.id()) == active_item_id,
2840                            })
2841                        })
2842                        .collect::<Vec<_>>(),
2843                    pane.has_focus(),
2844                )
2845            };
2846
2847            SerializedPane::new(items, active)
2848        }
2849
2850        fn build_serialized_pane_group(
2851            pane_group: &Member,
2852            cx: &AppContext,
2853        ) -> SerializedPaneGroup {
2854            match pane_group {
2855                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2856                    axis: *axis,
2857                    children: members
2858                        .iter()
2859                        .map(|member| build_serialized_pane_group(member, cx))
2860                        .collect::<Vec<_>>(),
2861                },
2862                Member::Pane(pane_handle) => {
2863                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2864                }
2865            }
2866        }
2867
2868        fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
2869            let left_dock = this.left_dock.read(cx);
2870            let left_visible = left_dock.is_open();
2871            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
2872                Some(
2873                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2874                        .to_string(),
2875                )
2876            });
2877
2878            let right_dock = this.right_dock.read(cx);
2879            let right_visible = right_dock.is_open();
2880            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
2881                Some(
2882                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2883                        .to_string(),
2884                )
2885            });
2886
2887            let bottom_dock = this.bottom_dock.read(cx);
2888            let bottom_visible = bottom_dock.is_open();
2889            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
2890                Some(
2891                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2892                        .to_string(),
2893                )
2894            });
2895
2896            DockStructure {
2897                left: DockData {
2898                    visible: left_visible,
2899                    active_panel: left_active_panel,
2900                },
2901                right: DockData {
2902                    visible: right_visible,
2903                    active_panel: right_active_panel,
2904                },
2905                bottom: DockData {
2906                    visible: bottom_visible,
2907                    active_panel: bottom_active_panel,
2908                },
2909            }
2910        }
2911
2912        if let Some(location) = self.location(cx) {
2913            // Load bearing special case:
2914            //  - with_local_workspace() relies on this to not have other stuff open
2915            //    when you open your log
2916            if !location.paths().is_empty() {
2917                let center_group = build_serialized_pane_group(&self.center.root, cx);
2918                let docks = build_serialized_docks(self, cx);
2919
2920                let serialized_workspace = SerializedWorkspace {
2921                    id: self.database_id,
2922                    location,
2923                    center_group,
2924                    bounds: Default::default(),
2925                    display: Default::default(),
2926                    docks,
2927                };
2928
2929                cx.background()
2930                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2931                    .detach();
2932            }
2933        }
2934    }
2935
2936    pub(crate) fn load_workspace(
2937        workspace: WeakViewHandle<Workspace>,
2938        serialized_workspace: SerializedWorkspace,
2939        paths_to_open: Vec<Option<ProjectPath>>,
2940        cx: &mut AppContext,
2941    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2942        cx.spawn(|mut cx| async move {
2943            let result = async_iife! {{
2944                let (project, old_center_pane) =
2945                workspace.read_with(&cx, |workspace, _| {
2946                    (
2947                        workspace.project().clone(),
2948                        workspace.last_active_center_pane.clone(),
2949                    )
2950                })?;
2951
2952                let mut center_items = None;
2953                let mut center_group = None;
2954                // Traverse the splits tree and add to things
2955                if let Some((group, active_pane, items)) = serialized_workspace
2956                        .center_group
2957                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2958                        .await {
2959                    center_items = Some(items);
2960                    center_group = Some((group, active_pane))
2961                }
2962
2963                let resulting_list = cx.read(|cx| {
2964                    let mut opened_items = center_items
2965                        .unwrap_or_default()
2966                        .into_iter()
2967                        .filter_map(|item| {
2968                            let item = item?;
2969                            let project_path = item.project_path(cx)?;
2970                            Some((project_path, item))
2971                        })
2972                        .collect::<HashMap<_, _>>();
2973
2974                    paths_to_open
2975                        .into_iter()
2976                        .map(|path_to_open| {
2977                            path_to_open.map(|path_to_open| {
2978                                Ok(opened_items.remove(&path_to_open))
2979                            })
2980                            .transpose()
2981                            .map(|item| item.flatten())
2982                            .transpose()
2983                        })
2984                        .collect::<Vec<_>>()
2985                });
2986
2987                // Remove old panes from workspace panes list
2988                workspace.update(&mut cx, |workspace, cx| {
2989                    if let Some((center_group, active_pane)) = center_group {
2990                        workspace.remove_panes(workspace.center.root.clone(), cx);
2991
2992                        // Swap workspace center group
2993                        workspace.center = PaneGroup::with_root(center_group);
2994
2995                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2996                        cx.focus_self();
2997
2998                        if let Some(active_pane) = active_pane {
2999                            cx.focus(&active_pane);
3000                        } else {
3001                            cx.focus(workspace.panes.last().unwrap());
3002                        }
3003                    } else {
3004                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3005                        if let Some(old_center_handle) = old_center_handle {
3006                            cx.focus(&old_center_handle)
3007                        } else {
3008                            cx.focus_self()
3009                        }
3010                    }
3011
3012                    let docks = serialized_workspace.docks;
3013                    workspace.left_dock.update(cx, |dock, cx| {
3014                        dock.set_open(docks.left.visible, cx);
3015                        if let Some(active_panel) = docks.left.active_panel {
3016                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3017                                dock.activate_panel(ix, cx);
3018                            }
3019                        }
3020                    });
3021                    workspace.right_dock.update(cx, |dock, cx| {
3022                        dock.set_open(docks.right.visible, cx);
3023                        if let Some(active_panel) = docks.right.active_panel {
3024                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3025                                dock.activate_panel(ix, cx);
3026                            }
3027                        }
3028                    });
3029                    workspace.bottom_dock.update(cx, |dock, cx| {
3030                        dock.set_open(docks.bottom.visible, cx);
3031                        if let Some(active_panel) = docks.bottom.active_panel {
3032                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3033                                dock.activate_panel(ix, cx);
3034                            }
3035                        }
3036                    });
3037
3038                    cx.notify();
3039                })?;
3040
3041                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3042                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3043
3044                Ok::<_, anyhow::Error>(resulting_list)
3045            }};
3046
3047            result.await.unwrap_or_default()
3048        })
3049    }
3050
3051    #[cfg(any(test, feature = "test-support"))]
3052    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3053        let app_state = Arc::new(AppState {
3054            languages: project.read(cx).languages().clone(),
3055            client: project.read(cx).client(),
3056            user_store: project.read(cx).user_store(),
3057            fs: project.read(cx).fs().clone(),
3058            build_window_options: |_, _, _| Default::default(),
3059            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3060            background_actions: || &[],
3061        });
3062        Self::new(0, project, app_state, cx)
3063    }
3064
3065    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3066        let dock = match position {
3067            DockPosition::Left => &self.left_dock,
3068            DockPosition::Right => &self.right_dock,
3069            DockPosition::Bottom => &self.bottom_dock,
3070        };
3071        let active_panel = dock.read(cx).visible_panel()?;
3072        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3073            dock.read(cx).render_placeholder(cx)
3074        } else {
3075            ChildView::new(dock, cx).into_any()
3076        };
3077
3078        Some(
3079            element
3080                .constrained()
3081                .dynamically(move |constraint, _, cx| match position {
3082                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3083                        Vector2F::new(20., constraint.min.y()),
3084                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3085                    ),
3086                    DockPosition::Bottom => SizeConstraint::new(
3087                        Vector2F::new(constraint.min.x(), 20.),
3088                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3089                    ),
3090                })
3091                .into_any(),
3092        )
3093    }
3094}
3095
3096async fn open_items(
3097    serialized_workspace: Option<SerializedWorkspace>,
3098    workspace: &WeakViewHandle<Workspace>,
3099    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3100    app_state: Arc<AppState>,
3101    mut cx: AsyncAppContext,
3102) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3103    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3104
3105    if let Some(serialized_workspace) = serialized_workspace {
3106        let workspace = workspace.clone();
3107        let restored_items = cx
3108            .update(|cx| {
3109                Workspace::load_workspace(
3110                    workspace,
3111                    serialized_workspace,
3112                    project_paths_to_open
3113                        .iter()
3114                        .map(|(_, project_path)| project_path)
3115                        .cloned()
3116                        .collect(),
3117                    cx,
3118                )
3119            })
3120            .await;
3121
3122        let restored_project_paths = cx.read(|cx| {
3123            restored_items
3124                .iter()
3125                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3126                .collect::<HashSet<_>>()
3127        });
3128
3129        opened_items = restored_items;
3130        project_paths_to_open
3131            .iter_mut()
3132            .for_each(|(_, project_path)| {
3133                if let Some(project_path_to_open) = project_path {
3134                    if restored_project_paths.contains(project_path_to_open) {
3135                        *project_path = None;
3136                    }
3137                }
3138            });
3139    } else {
3140        for _ in 0..project_paths_to_open.len() {
3141            opened_items.push(None);
3142        }
3143    }
3144    assert!(opened_items.len() == project_paths_to_open.len());
3145
3146    let tasks =
3147        project_paths_to_open
3148            .into_iter()
3149            .enumerate()
3150            .map(|(i, (abs_path, project_path))| {
3151                let workspace = workspace.clone();
3152                cx.spawn(|mut cx| {
3153                    let fs = app_state.fs.clone();
3154                    async move {
3155                        let file_project_path = project_path?;
3156                        if fs.is_file(&abs_path).await {
3157                            Some((
3158                                i,
3159                                workspace
3160                                    .update(&mut cx, |workspace, cx| {
3161                                        workspace.open_path(file_project_path, None, true, cx)
3162                                    })
3163                                    .log_err()?
3164                                    .await,
3165                            ))
3166                        } else {
3167                            None
3168                        }
3169                    }
3170                })
3171            });
3172
3173    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3174        .await
3175        .into_iter()
3176    {
3177        if let Some((i, path_open_result)) = maybe_opened_path {
3178            opened_items[i] = Some(path_open_result);
3179        }
3180    }
3181
3182    opened_items
3183}
3184
3185fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3186    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3187
3188    workspace
3189        .update(cx, |workspace, cx| {
3190            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3191                workspace.show_notification_once(0, cx, |cx| {
3192                    cx.add_view(|_| {
3193                        MessageNotification::new("Failed to load any database file.")
3194                            .with_click_message("Click to let us know about this error")
3195                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3196                    })
3197                });
3198            } else {
3199                let backup_path = (*db::BACKUP_DB_PATH).read();
3200                if let Some(backup_path) = backup_path.clone() {
3201                    workspace.show_notification_once(0, cx, move |cx| {
3202                        cx.add_view(move |_| {
3203                            MessageNotification::new(format!(
3204                                "Database file was corrupted. Old database backed up to {}",
3205                                backup_path.display()
3206                            ))
3207                            .with_click_message("Click to show old database in finder")
3208                            .on_click(move |cx| {
3209                                cx.platform().open_url(&backup_path.to_string_lossy())
3210                            })
3211                        })
3212                    });
3213                }
3214            }
3215        })
3216        .log_err();
3217}
3218
3219impl Entity for Workspace {
3220    type Event = Event;
3221}
3222
3223impl View for Workspace {
3224    fn ui_name() -> &'static str {
3225        "Workspace"
3226    }
3227
3228    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3229        let theme = theme::current(cx).clone();
3230        Stack::new()
3231            .with_child(
3232                Flex::column()
3233                    .with_child(self.render_titlebar(&theme, cx))
3234                    .with_child(
3235                        Stack::new()
3236                            .with_child({
3237                                let project = self.project.clone();
3238                                Flex::row()
3239                                    .with_children(self.render_dock(DockPosition::Left, cx))
3240                                    .with_child(
3241                                        Flex::column()
3242                                            .with_child(
3243                                                FlexItem::new(
3244                                                    self.center.render(
3245                                                        &project,
3246                                                        &theme,
3247                                                        &self.follower_states_by_leader,
3248                                                        self.active_call(),
3249                                                        self.active_pane(),
3250                                                        self.zoomed
3251                                                            .as_ref()
3252                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3253                                                            .as_ref(),
3254                                                        &self.app_state,
3255                                                        cx,
3256                                                    ),
3257                                                )
3258                                                .flex(1., true),
3259                                            )
3260                                            .with_children(
3261                                                self.render_dock(DockPosition::Bottom, cx),
3262                                            )
3263                                            .flex(1., true),
3264                                    )
3265                                    .with_children(self.render_dock(DockPosition::Right, cx))
3266                            })
3267                            .with_child(Overlay::new(
3268                                Stack::new()
3269                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3270                                        enum ZoomBackground {}
3271                                        let zoomed = zoomed.upgrade(cx)?;
3272
3273                                        let mut foreground_style;
3274                                        match self.zoomed_position {
3275                                            Some(DockPosition::Left) => {
3276                                                foreground_style =
3277                                                    theme.workspace.zoomed_panel_foreground;
3278                                                foreground_style.margin.left = 0.;
3279                                                foreground_style.margin.top = 0.;
3280                                                foreground_style.margin.bottom = 0.;
3281                                            }
3282                                            Some(DockPosition::Right) => {
3283                                                foreground_style =
3284                                                    theme.workspace.zoomed_panel_foreground;
3285                                                foreground_style.margin.right = 0.;
3286                                                foreground_style.margin.top = 0.;
3287                                                foreground_style.margin.bottom = 0.;
3288                                            }
3289                                            Some(DockPosition::Bottom) => {
3290                                                foreground_style =
3291                                                    theme.workspace.zoomed_panel_foreground;
3292                                                foreground_style.margin.left = 0.;
3293                                                foreground_style.margin.right = 0.;
3294                                                foreground_style.margin.bottom = 0.;
3295                                            }
3296                                            None => {
3297                                                foreground_style =
3298                                                    theme.workspace.zoomed_pane_foreground;
3299                                            }
3300                                        }
3301
3302                                        Some(
3303                                            ChildView::new(&zoomed, cx)
3304                                                .contained()
3305                                                .with_style(foreground_style)
3306                                                .aligned()
3307                                                .contained()
3308                                                .with_style(theme.workspace.zoomed_background)
3309                                                .mouse::<ZoomBackground>(0)
3310                                                .capture_all()
3311                                                .on_down(
3312                                                    MouseButton::Left,
3313                                                    |_, this: &mut Self, cx| {
3314                                                        this.zoom_out(cx);
3315                                                    },
3316                                                ),
3317                                        )
3318                                    }))
3319                                    .with_children(self.modal.as_ref().map(|modal| {
3320                                        ChildView::new(modal, cx)
3321                                            .contained()
3322                                            .with_style(theme.workspace.modal)
3323                                            .aligned()
3324                                            .top()
3325                                    }))
3326                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3327                            ))
3328                            .flex(1.0, true),
3329                    )
3330                    .with_child(ChildView::new(&self.status_bar, cx))
3331                    .contained()
3332                    .with_background_color(theme.workspace.background),
3333            )
3334            .with_children(DragAndDrop::render(cx))
3335            .with_children(self.render_disconnected_overlay(cx))
3336            .into_any_named("workspace")
3337    }
3338
3339    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3340        if cx.is_self_focused() {
3341            cx.focus(&self.active_pane);
3342        }
3343    }
3344}
3345
3346impl ViewId {
3347    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3348        Ok(Self {
3349            creator: message
3350                .creator
3351                .ok_or_else(|| anyhow!("creator is missing"))?,
3352            id: message.id,
3353        })
3354    }
3355
3356    pub(crate) fn to_proto(&self) -> proto::ViewId {
3357        proto::ViewId {
3358            creator: Some(self.creator),
3359            id: self.id,
3360        }
3361    }
3362}
3363
3364pub trait WorkspaceHandle {
3365    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3366}
3367
3368impl WorkspaceHandle for ViewHandle<Workspace> {
3369    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3370        self.read(cx)
3371            .worktrees(cx)
3372            .flat_map(|worktree| {
3373                let worktree_id = worktree.read(cx).id();
3374                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3375                    worktree_id,
3376                    path: f.path.clone(),
3377                })
3378            })
3379            .collect::<Vec<_>>()
3380    }
3381}
3382
3383impl std::fmt::Debug for OpenPaths {
3384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3385        f.debug_struct("OpenPaths")
3386            .field("paths", &self.paths)
3387            .finish()
3388    }
3389}
3390
3391pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3392
3393pub fn activate_workspace_for_project(
3394    cx: &mut AsyncAppContext,
3395    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3396) -> Option<WeakViewHandle<Workspace>> {
3397    for window_id in cx.window_ids() {
3398        let handle = cx
3399            .update_window(window_id, |cx| {
3400                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3401                    let project = workspace_handle.read(cx).project.clone();
3402                    if project.update(cx, &predicate) {
3403                        cx.activate_window();
3404                        return Some(workspace_handle.clone());
3405                    }
3406                }
3407                None
3408            })
3409            .flatten();
3410
3411        if let Some(handle) = handle {
3412            return Some(handle.downgrade());
3413        }
3414    }
3415    None
3416}
3417
3418pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3419    DB.last_workspace().await.log_err().flatten()
3420}
3421
3422#[allow(clippy::type_complexity)]
3423pub fn open_paths(
3424    abs_paths: &[PathBuf],
3425    app_state: &Arc<AppState>,
3426    requesting_window_id: Option<usize>,
3427    cx: &mut AppContext,
3428) -> Task<
3429    Result<(
3430        WeakViewHandle<Workspace>,
3431        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3432    )>,
3433> {
3434    let app_state = app_state.clone();
3435    let abs_paths = abs_paths.to_vec();
3436    cx.spawn(|mut cx| async move {
3437        // Open paths in existing workspace if possible
3438        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3439            project.contains_paths(&abs_paths, cx)
3440        });
3441
3442        if let Some(existing) = existing {
3443            Ok((
3444                existing.clone(),
3445                existing
3446                    .update(&mut cx, |workspace, cx| {
3447                        workspace.open_paths(abs_paths, true, cx)
3448                    })?
3449                    .await,
3450            ))
3451        } else {
3452            Ok(cx
3453                .update(|cx| {
3454                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3455                })
3456                .await)
3457        }
3458    })
3459}
3460
3461pub fn open_new(
3462    app_state: &Arc<AppState>,
3463    cx: &mut AppContext,
3464    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3465) -> Task<()> {
3466    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3467    cx.spawn(|mut cx| async move {
3468        let (workspace, opened_paths) = task.await;
3469
3470        workspace
3471            .update(&mut cx, |workspace, cx| {
3472                if opened_paths.is_empty() {
3473                    init(workspace, cx)
3474                }
3475            })
3476            .log_err();
3477    })
3478}
3479
3480pub fn create_and_open_local_file(
3481    path: &'static Path,
3482    cx: &mut ViewContext<Workspace>,
3483    default_content: impl 'static + Send + FnOnce() -> Rope,
3484) -> Task<Result<Box<dyn ItemHandle>>> {
3485    cx.spawn(|workspace, mut cx| async move {
3486        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3487        if !fs.is_file(path).await {
3488            fs.create_file(path, Default::default()).await?;
3489            fs.save(path, &default_content(), Default::default())
3490                .await?;
3491        }
3492
3493        let mut items = workspace
3494            .update(&mut cx, |workspace, cx| {
3495                workspace.with_local_workspace(cx, |workspace, cx| {
3496                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3497                })
3498            })?
3499            .await?
3500            .await;
3501
3502        let item = items.pop().flatten();
3503        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3504    })
3505}
3506
3507pub fn join_remote_project(
3508    project_id: u64,
3509    follow_user_id: u64,
3510    app_state: Arc<AppState>,
3511    cx: &mut AppContext,
3512) -> Task<Result<()>> {
3513    cx.spawn(|mut cx| async move {
3514        let existing_workspace = cx
3515            .window_ids()
3516            .into_iter()
3517            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3518            .find(|workspace| {
3519                cx.read_window(workspace.window_id(), |cx| {
3520                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3521                })
3522                .unwrap_or(false)
3523            });
3524
3525        let workspace = if let Some(existing_workspace) = existing_workspace {
3526            existing_workspace.downgrade()
3527        } else {
3528            let active_call = cx.read(ActiveCall::global);
3529            let room = active_call
3530                .read_with(&cx, |call, _| call.room().cloned())
3531                .ok_or_else(|| anyhow!("not in a call"))?;
3532            let project = room
3533                .update(&mut cx, |room, cx| {
3534                    room.join_project(
3535                        project_id,
3536                        app_state.languages.clone(),
3537                        app_state.fs.clone(),
3538                        cx,
3539                    )
3540                })
3541                .await?;
3542
3543            let (_, workspace) = cx.add_window(
3544                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3545                |cx| Workspace::new(0, project, app_state.clone(), cx),
3546            );
3547            (app_state.initialize_workspace)(
3548                workspace.downgrade(),
3549                false,
3550                app_state.clone(),
3551                cx.clone(),
3552            )
3553            .await
3554            .log_err();
3555
3556            workspace.downgrade()
3557        };
3558
3559        cx.activate_window(workspace.window_id());
3560        cx.platform().activate(true);
3561
3562        workspace.update(&mut cx, |workspace, cx| {
3563            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3564                let follow_peer_id = room
3565                    .read(cx)
3566                    .remote_participants()
3567                    .iter()
3568                    .find(|(_, participant)| participant.user.id == follow_user_id)
3569                    .map(|(_, p)| p.peer_id)
3570                    .or_else(|| {
3571                        // If we couldn't follow the given user, follow the host instead.
3572                        let collaborator = workspace
3573                            .project()
3574                            .read(cx)
3575                            .collaborators()
3576                            .values()
3577                            .find(|collaborator| collaborator.replica_id == 0)?;
3578                        Some(collaborator.peer_id)
3579                    });
3580
3581                if let Some(follow_peer_id) = follow_peer_id {
3582                    if !workspace.is_being_followed(follow_peer_id) {
3583                        workspace
3584                            .toggle_follow(follow_peer_id, cx)
3585                            .map(|follow| follow.detach_and_log_err(cx));
3586                    }
3587                }
3588            }
3589        })?;
3590
3591        anyhow::Ok(())
3592    })
3593}
3594
3595pub fn restart(_: &Restart, cx: &mut AppContext) {
3596    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3597    cx.spawn(|mut cx| async move {
3598        let mut workspaces = cx
3599            .window_ids()
3600            .into_iter()
3601            .filter_map(|window_id| {
3602                Some(
3603                    cx.root_view(window_id)?
3604                        .clone()
3605                        .downcast::<Workspace>()?
3606                        .downgrade(),
3607                )
3608            })
3609            .collect::<Vec<_>>();
3610
3611        // If multiple windows have unsaved changes, and need a save prompt,
3612        // prompt in the active window before switching to a different window.
3613        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3614
3615        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3616            let answer = cx.prompt(
3617                workspace.window_id(),
3618                PromptLevel::Info,
3619                "Are you sure you want to restart?",
3620                &["Restart", "Cancel"],
3621            );
3622
3623            if let Some(mut answer) = answer {
3624                let answer = answer.next().await;
3625                if answer != Some(0) {
3626                    return Ok(());
3627                }
3628            }
3629        }
3630
3631        // If the user cancels any save prompt, then keep the app open.
3632        for workspace in workspaces {
3633            if !workspace
3634                .update(&mut cx, |workspace, cx| {
3635                    workspace.prepare_to_close(true, cx)
3636                })?
3637                .await?
3638            {
3639                return Ok(());
3640            }
3641        }
3642        cx.platform().restart();
3643        anyhow::Ok(())
3644    })
3645    .detach_and_log_err(cx);
3646}
3647
3648fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3649    let mut parts = value.split(',');
3650    let width: usize = parts.next()?.parse().ok()?;
3651    let height: usize = parts.next()?.parse().ok()?;
3652    Some(vec2f(width as f32, height as f32))
3653}
3654
3655#[cfg(test)]
3656mod tests {
3657    use super::*;
3658    use crate::{
3659        dock::test::{TestPanel, TestPanelEvent},
3660        item::test::{TestItem, TestItemEvent, TestProjectItem},
3661    };
3662    use fs::FakeFs;
3663    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3664    use project::{Project, ProjectEntryId};
3665    use serde_json::json;
3666    use settings::SettingsStore;
3667    use std::{cell::RefCell, rc::Rc};
3668
3669    #[gpui::test]
3670    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3671        init_test(cx);
3672
3673        let fs = FakeFs::new(cx.background());
3674        let project = Project::test(fs, [], cx).await;
3675        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3676
3677        // Adding an item with no ambiguity renders the tab without detail.
3678        let item1 = cx.add_view(window_id, |_| {
3679            let mut item = TestItem::new();
3680            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3681            item
3682        });
3683        workspace.update(cx, |workspace, cx| {
3684            workspace.add_item(Box::new(item1.clone()), cx);
3685        });
3686        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3687
3688        // Adding an item that creates ambiguity increases the level of detail on
3689        // both tabs.
3690        let item2 = cx.add_view(window_id, |_| {
3691            let mut item = TestItem::new();
3692            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3693            item
3694        });
3695        workspace.update(cx, |workspace, cx| {
3696            workspace.add_item(Box::new(item2.clone()), cx);
3697        });
3698        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3699        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3700
3701        // Adding an item that creates ambiguity increases the level of detail only
3702        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3703        // we stop at the highest detail available.
3704        let item3 = cx.add_view(window_id, |_| {
3705            let mut item = TestItem::new();
3706            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3707            item
3708        });
3709        workspace.update(cx, |workspace, cx| {
3710            workspace.add_item(Box::new(item3.clone()), cx);
3711        });
3712        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3713        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3714        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3715    }
3716
3717    #[gpui::test]
3718    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3719        init_test(cx);
3720
3721        let fs = FakeFs::new(cx.background());
3722        fs.insert_tree(
3723            "/root1",
3724            json!({
3725                "one.txt": "",
3726                "two.txt": "",
3727            }),
3728        )
3729        .await;
3730        fs.insert_tree(
3731            "/root2",
3732            json!({
3733                "three.txt": "",
3734            }),
3735        )
3736        .await;
3737
3738        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3739        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3740        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3741        let worktree_id = project.read_with(cx, |project, cx| {
3742            project.worktrees(cx).next().unwrap().read(cx).id()
3743        });
3744
3745        let item1 = cx.add_view(window_id, |cx| {
3746            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3747        });
3748        let item2 = cx.add_view(window_id, |cx| {
3749            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3750        });
3751
3752        // Add an item to an empty pane
3753        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3754        project.read_with(cx, |project, cx| {
3755            assert_eq!(
3756                project.active_entry(),
3757                project
3758                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3759                    .map(|e| e.id)
3760            );
3761        });
3762        assert_eq!(
3763            cx.current_window_title(window_id).as_deref(),
3764            Some("one.txt β€” root1")
3765        );
3766
3767        // Add a second item to a non-empty pane
3768        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3769        assert_eq!(
3770            cx.current_window_title(window_id).as_deref(),
3771            Some("two.txt β€” root1")
3772        );
3773        project.read_with(cx, |project, cx| {
3774            assert_eq!(
3775                project.active_entry(),
3776                project
3777                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3778                    .map(|e| e.id)
3779            );
3780        });
3781
3782        // Close the active item
3783        pane.update(cx, |pane, cx| {
3784            pane.close_active_item(&Default::default(), cx).unwrap()
3785        })
3786        .await
3787        .unwrap();
3788        assert_eq!(
3789            cx.current_window_title(window_id).as_deref(),
3790            Some("one.txt β€” root1")
3791        );
3792        project.read_with(cx, |project, cx| {
3793            assert_eq!(
3794                project.active_entry(),
3795                project
3796                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3797                    .map(|e| e.id)
3798            );
3799        });
3800
3801        // Add a project folder
3802        project
3803            .update(cx, |project, cx| {
3804                project.find_or_create_local_worktree("/root2", true, cx)
3805            })
3806            .await
3807            .unwrap();
3808        assert_eq!(
3809            cx.current_window_title(window_id).as_deref(),
3810            Some("one.txt β€” root1, root2")
3811        );
3812
3813        // Remove a project folder
3814        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3815        assert_eq!(
3816            cx.current_window_title(window_id).as_deref(),
3817            Some("one.txt β€” root2")
3818        );
3819    }
3820
3821    #[gpui::test]
3822    async fn test_close_window(cx: &mut TestAppContext) {
3823        init_test(cx);
3824
3825        let fs = FakeFs::new(cx.background());
3826        fs.insert_tree("/root", json!({ "one": "" })).await;
3827
3828        let project = Project::test(fs, ["root".as_ref()], cx).await;
3829        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3830
3831        // When there are no dirty items, there's nothing to do.
3832        let item1 = cx.add_view(window_id, |_| TestItem::new());
3833        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3834        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3835        assert!(task.await.unwrap());
3836
3837        // When there are dirty untitled items, prompt to save each one. If the user
3838        // cancels any prompt, then abort.
3839        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3840        let item3 = cx.add_view(window_id, |cx| {
3841            TestItem::new()
3842                .with_dirty(true)
3843                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3844        });
3845        workspace.update(cx, |w, cx| {
3846            w.add_item(Box::new(item2.clone()), cx);
3847            w.add_item(Box::new(item3.clone()), cx);
3848        });
3849        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3850        cx.foreground().run_until_parked();
3851        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3852        cx.foreground().run_until_parked();
3853        assert!(!cx.has_pending_prompt(window_id));
3854        assert!(!task.await.unwrap());
3855    }
3856
3857    #[gpui::test]
3858    async fn test_close_pane_items(cx: &mut TestAppContext) {
3859        init_test(cx);
3860
3861        let fs = FakeFs::new(cx.background());
3862
3863        let project = Project::test(fs, None, cx).await;
3864        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3865
3866        let item1 = cx.add_view(window_id, |cx| {
3867            TestItem::new()
3868                .with_dirty(true)
3869                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3870        });
3871        let item2 = cx.add_view(window_id, |cx| {
3872            TestItem::new()
3873                .with_dirty(true)
3874                .with_conflict(true)
3875                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3876        });
3877        let item3 = cx.add_view(window_id, |cx| {
3878            TestItem::new()
3879                .with_dirty(true)
3880                .with_conflict(true)
3881                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3882        });
3883        let item4 = cx.add_view(window_id, |cx| {
3884            TestItem::new()
3885                .with_dirty(true)
3886                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3887        });
3888        let pane = workspace.update(cx, |workspace, cx| {
3889            workspace.add_item(Box::new(item1.clone()), cx);
3890            workspace.add_item(Box::new(item2.clone()), cx);
3891            workspace.add_item(Box::new(item3.clone()), cx);
3892            workspace.add_item(Box::new(item4.clone()), cx);
3893            workspace.active_pane().clone()
3894        });
3895
3896        let close_items = pane.update(cx, |pane, cx| {
3897            pane.activate_item(1, true, true, cx);
3898            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3899            let item1_id = item1.id();
3900            let item3_id = item3.id();
3901            let item4_id = item4.id();
3902            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3903        });
3904        cx.foreground().run_until_parked();
3905
3906        // There's a prompt to save item 1.
3907        pane.read_with(cx, |pane, _| {
3908            assert_eq!(pane.items_len(), 4);
3909            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3910        });
3911        assert!(cx.has_pending_prompt(window_id));
3912
3913        // Confirm saving item 1.
3914        cx.simulate_prompt_answer(window_id, 0);
3915        cx.foreground().run_until_parked();
3916
3917        // Item 1 is saved. There's a prompt to save item 3.
3918        pane.read_with(cx, |pane, cx| {
3919            assert_eq!(item1.read(cx).save_count, 1);
3920            assert_eq!(item1.read(cx).save_as_count, 0);
3921            assert_eq!(item1.read(cx).reload_count, 0);
3922            assert_eq!(pane.items_len(), 3);
3923            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3924        });
3925        assert!(cx.has_pending_prompt(window_id));
3926
3927        // Cancel saving item 3.
3928        cx.simulate_prompt_answer(window_id, 1);
3929        cx.foreground().run_until_parked();
3930
3931        // Item 3 is reloaded. There's a prompt to save item 4.
3932        pane.read_with(cx, |pane, cx| {
3933            assert_eq!(item3.read(cx).save_count, 0);
3934            assert_eq!(item3.read(cx).save_as_count, 0);
3935            assert_eq!(item3.read(cx).reload_count, 1);
3936            assert_eq!(pane.items_len(), 2);
3937            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3938        });
3939        assert!(cx.has_pending_prompt(window_id));
3940
3941        // Confirm saving item 4.
3942        cx.simulate_prompt_answer(window_id, 0);
3943        cx.foreground().run_until_parked();
3944
3945        // There's a prompt for a path for item 4.
3946        cx.simulate_new_path_selection(|_| Some(Default::default()));
3947        close_items.await.unwrap();
3948
3949        // The requested items are closed.
3950        pane.read_with(cx, |pane, cx| {
3951            assert_eq!(item4.read(cx).save_count, 0);
3952            assert_eq!(item4.read(cx).save_as_count, 1);
3953            assert_eq!(item4.read(cx).reload_count, 0);
3954            assert_eq!(pane.items_len(), 1);
3955            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3956        });
3957    }
3958
3959    #[gpui::test]
3960    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3961        init_test(cx);
3962
3963        let fs = FakeFs::new(cx.background());
3964
3965        let project = Project::test(fs, [], cx).await;
3966        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3967
3968        // Create several workspace items with single project entries, and two
3969        // workspace items with multiple project entries.
3970        let single_entry_items = (0..=4)
3971            .map(|project_entry_id| {
3972                cx.add_view(window_id, |cx| {
3973                    TestItem::new()
3974                        .with_dirty(true)
3975                        .with_project_items(&[TestProjectItem::new(
3976                            project_entry_id,
3977                            &format!("{project_entry_id}.txt"),
3978                            cx,
3979                        )])
3980                })
3981            })
3982            .collect::<Vec<_>>();
3983        let item_2_3 = cx.add_view(window_id, |cx| {
3984            TestItem::new()
3985                .with_dirty(true)
3986                .with_singleton(false)
3987                .with_project_items(&[
3988                    single_entry_items[2].read(cx).project_items[0].clone(),
3989                    single_entry_items[3].read(cx).project_items[0].clone(),
3990                ])
3991        });
3992        let item_3_4 = cx.add_view(window_id, |cx| {
3993            TestItem::new()
3994                .with_dirty(true)
3995                .with_singleton(false)
3996                .with_project_items(&[
3997                    single_entry_items[3].read(cx).project_items[0].clone(),
3998                    single_entry_items[4].read(cx).project_items[0].clone(),
3999                ])
4000        });
4001
4002        // Create two panes that contain the following project entries:
4003        //   left pane:
4004        //     multi-entry items:   (2, 3)
4005        //     single-entry items:  0, 1, 2, 3, 4
4006        //   right pane:
4007        //     single-entry items:  1
4008        //     multi-entry items:   (3, 4)
4009        let left_pane = workspace.update(cx, |workspace, cx| {
4010            let left_pane = workspace.active_pane().clone();
4011            workspace.add_item(Box::new(item_2_3.clone()), cx);
4012            for item in single_entry_items {
4013                workspace.add_item(Box::new(item), cx);
4014            }
4015            left_pane.update(cx, |pane, cx| {
4016                pane.activate_item(2, true, true, cx);
4017            });
4018
4019            workspace
4020                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
4021                .unwrap();
4022
4023            left_pane
4024        });
4025
4026        //Need to cause an effect flush in order to respect new focus
4027        workspace.update(cx, |workspace, cx| {
4028            workspace.add_item(Box::new(item_3_4.clone()), cx);
4029            cx.focus(&left_pane);
4030        });
4031
4032        // When closing all of the items in the left pane, we should be prompted twice:
4033        // once for project entry 0, and once for project entry 2. After those two
4034        // prompts, the task should complete.
4035
4036        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4037        cx.foreground().run_until_parked();
4038        left_pane.read_with(cx, |pane, cx| {
4039            assert_eq!(
4040                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4041                &[ProjectEntryId::from_proto(0)]
4042            );
4043        });
4044        cx.simulate_prompt_answer(window_id, 0);
4045
4046        cx.foreground().run_until_parked();
4047        left_pane.read_with(cx, |pane, cx| {
4048            assert_eq!(
4049                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4050                &[ProjectEntryId::from_proto(2)]
4051            );
4052        });
4053        cx.simulate_prompt_answer(window_id, 0);
4054
4055        cx.foreground().run_until_parked();
4056        close.await.unwrap();
4057        left_pane.read_with(cx, |pane, _| {
4058            assert_eq!(pane.items_len(), 0);
4059        });
4060    }
4061
4062    #[gpui::test]
4063    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4064        init_test(cx);
4065
4066        let fs = FakeFs::new(cx.background());
4067
4068        let project = Project::test(fs, [], cx).await;
4069        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4070        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4071
4072        let item = cx.add_view(window_id, |cx| {
4073            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4074        });
4075        let item_id = item.id();
4076        workspace.update(cx, |workspace, cx| {
4077            workspace.add_item(Box::new(item.clone()), cx);
4078        });
4079
4080        // Autosave on window change.
4081        item.update(cx, |item, cx| {
4082            cx.update_global(|settings: &mut SettingsStore, cx| {
4083                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4084                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4085                })
4086            });
4087            item.is_dirty = true;
4088        });
4089
4090        // Deactivating the window saves the file.
4091        cx.simulate_window_activation(None);
4092        deterministic.run_until_parked();
4093        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4094
4095        // Autosave on focus change.
4096        item.update(cx, |item, cx| {
4097            cx.focus_self();
4098            cx.update_global(|settings: &mut SettingsStore, cx| {
4099                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4100                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4101                })
4102            });
4103            item.is_dirty = true;
4104        });
4105
4106        // Blurring the item saves the file.
4107        item.update(cx, |_, cx| cx.blur());
4108        deterministic.run_until_parked();
4109        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4110
4111        // Deactivating the window still saves the file.
4112        cx.simulate_window_activation(Some(window_id));
4113        item.update(cx, |item, cx| {
4114            cx.focus_self();
4115            item.is_dirty = true;
4116        });
4117        cx.simulate_window_activation(None);
4118
4119        deterministic.run_until_parked();
4120        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4121
4122        // Autosave after delay.
4123        item.update(cx, |item, cx| {
4124            cx.update_global(|settings: &mut SettingsStore, cx| {
4125                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4126                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4127                })
4128            });
4129            item.is_dirty = true;
4130            cx.emit(TestItemEvent::Edit);
4131        });
4132
4133        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4134        deterministic.advance_clock(Duration::from_millis(250));
4135        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4136
4137        // After delay expires, the file is saved.
4138        deterministic.advance_clock(Duration::from_millis(250));
4139        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4140
4141        // Autosave on focus change, ensuring closing the tab counts as such.
4142        item.update(cx, |item, cx| {
4143            cx.update_global(|settings: &mut SettingsStore, cx| {
4144                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4145                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4146                })
4147            });
4148            item.is_dirty = true;
4149        });
4150
4151        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4152            .await
4153            .unwrap();
4154        assert!(!cx.has_pending_prompt(window_id));
4155        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4156
4157        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4158        workspace.update(cx, |workspace, cx| {
4159            workspace.add_item(Box::new(item.clone()), cx);
4160        });
4161        item.update(cx, |item, cx| {
4162            item.project_items[0].update(cx, |item, _| {
4163                item.entry_id = None;
4164            });
4165            item.is_dirty = true;
4166            cx.blur();
4167        });
4168        deterministic.run_until_parked();
4169        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4170
4171        // Ensure autosave is prevented for deleted files also when closing the buffer.
4172        let _close_items =
4173            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4174        deterministic.run_until_parked();
4175        assert!(cx.has_pending_prompt(window_id));
4176        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4177    }
4178
4179    #[gpui::test]
4180    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4181        init_test(cx);
4182
4183        let fs = FakeFs::new(cx.background());
4184
4185        let project = Project::test(fs, [], cx).await;
4186        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4187
4188        let item = cx.add_view(window_id, |cx| {
4189            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4190        });
4191        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4192        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4193        let toolbar_notify_count = Rc::new(RefCell::new(0));
4194
4195        workspace.update(cx, |workspace, cx| {
4196            workspace.add_item(Box::new(item.clone()), cx);
4197            let toolbar_notification_count = toolbar_notify_count.clone();
4198            cx.observe(&toolbar, move |_, _, _| {
4199                *toolbar_notification_count.borrow_mut() += 1
4200            })
4201            .detach();
4202        });
4203
4204        pane.read_with(cx, |pane, _| {
4205            assert!(!pane.can_navigate_backward());
4206            assert!(!pane.can_navigate_forward());
4207        });
4208
4209        item.update(cx, |item, cx| {
4210            item.set_state("one".to_string(), cx);
4211        });
4212
4213        // Toolbar must be notified to re-render the navigation buttons
4214        assert_eq!(*toolbar_notify_count.borrow(), 1);
4215
4216        pane.read_with(cx, |pane, _| {
4217            assert!(pane.can_navigate_backward());
4218            assert!(!pane.can_navigate_forward());
4219        });
4220
4221        workspace
4222            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4223            .await
4224            .unwrap();
4225
4226        assert_eq!(*toolbar_notify_count.borrow(), 3);
4227        pane.read_with(cx, |pane, _| {
4228            assert!(!pane.can_navigate_backward());
4229            assert!(pane.can_navigate_forward());
4230        });
4231    }
4232
4233    #[gpui::test]
4234    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4235        init_test(cx);
4236        let fs = FakeFs::new(cx.background());
4237
4238        let project = Project::test(fs, [], cx).await;
4239        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4240
4241        let panel = workspace.update(cx, |workspace, cx| {
4242            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4243            workspace.add_panel(panel.clone(), cx);
4244
4245            workspace
4246                .right_dock()
4247                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4248
4249            panel
4250        });
4251
4252        // Transfer focus from center to panel
4253        workspace.update(cx, |workspace, cx| {
4254            workspace.toggle_panel_focus::<TestPanel>(cx);
4255        });
4256
4257        workspace.read_with(cx, |workspace, cx| {
4258            assert!(workspace.right_dock().read(cx).is_open());
4259            assert!(!panel.is_zoomed(cx));
4260            assert!(panel.has_focus(cx));
4261        });
4262
4263        // Transfer focus from panel to center
4264        workspace.update(cx, |workspace, cx| {
4265            workspace.toggle_panel_focus::<TestPanel>(cx);
4266        });
4267
4268        workspace.read_with(cx, |workspace, cx| {
4269            assert!(workspace.right_dock().read(cx).is_open());
4270            assert!(!panel.is_zoomed(cx));
4271            assert!(!panel.has_focus(cx));
4272        });
4273
4274        // Close the dock
4275        workspace.update(cx, |workspace, cx| {
4276            workspace.toggle_dock(DockPosition::Right, cx);
4277        });
4278
4279        workspace.read_with(cx, |workspace, cx| {
4280            assert!(!workspace.right_dock().read(cx).is_open());
4281            assert!(!panel.is_zoomed(cx));
4282            assert!(!panel.has_focus(cx));
4283        });
4284
4285        // Open the dock
4286        workspace.update(cx, |workspace, cx| {
4287            workspace.toggle_dock(DockPosition::Right, cx);
4288        });
4289
4290        workspace.read_with(cx, |workspace, cx| {
4291            assert!(workspace.right_dock().read(cx).is_open());
4292            assert!(!panel.is_zoomed(cx));
4293            assert!(!panel.has_focus(cx));
4294        });
4295
4296        // Focus and zoom panel
4297        panel.update(cx, |panel, cx| {
4298            cx.focus_self();
4299            panel.set_zoomed(true, cx)
4300        });
4301
4302        workspace.read_with(cx, |workspace, cx| {
4303            assert!(workspace.right_dock().read(cx).is_open());
4304            assert!(panel.is_zoomed(cx));
4305            assert!(panel.has_focus(cx));
4306        });
4307
4308        // Transfer focus to the center closes the dock
4309        workspace.update(cx, |workspace, cx| {
4310            workspace.toggle_panel_focus::<TestPanel>(cx);
4311        });
4312
4313        workspace.read_with(cx, |workspace, cx| {
4314            assert!(!workspace.right_dock().read(cx).is_open());
4315            assert!(panel.is_zoomed(cx));
4316            assert!(!panel.has_focus(cx));
4317        });
4318
4319        // Transfering focus back to the panel keeps it zoomed
4320        workspace.update(cx, |workspace, cx| {
4321            workspace.toggle_panel_focus::<TestPanel>(cx);
4322        });
4323
4324        workspace.read_with(cx, |workspace, cx| {
4325            assert!(workspace.right_dock().read(cx).is_open());
4326            assert!(panel.is_zoomed(cx));
4327            assert!(panel.has_focus(cx));
4328        });
4329
4330        // Close the dock while it is zoomed
4331        workspace.update(cx, |workspace, cx| {
4332            workspace.toggle_dock(DockPosition::Right, cx)
4333        });
4334
4335        workspace.read_with(cx, |workspace, cx| {
4336            assert!(!workspace.right_dock().read(cx).is_open());
4337            assert!(panel.is_zoomed(cx));
4338            assert!(workspace.zoomed.is_none());
4339            assert!(!panel.has_focus(cx));
4340        });
4341
4342        // Opening the dock, when it's zoomed, retains focus
4343        workspace.update(cx, |workspace, cx| {
4344            workspace.toggle_dock(DockPosition::Right, cx)
4345        });
4346
4347        workspace.read_with(cx, |workspace, cx| {
4348            assert!(workspace.right_dock().read(cx).is_open());
4349            assert!(panel.is_zoomed(cx));
4350            assert!(workspace.zoomed.is_some());
4351            assert!(panel.has_focus(cx));
4352        });
4353    }
4354
4355    #[gpui::test]
4356    async fn test_panels(cx: &mut gpui::TestAppContext) {
4357        init_test(cx);
4358        let fs = FakeFs::new(cx.background());
4359
4360        let project = Project::test(fs, [], cx).await;
4361        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4362
4363        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4364            // Add panel_1 on the left, panel_2 on the right.
4365            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4366            workspace.add_panel(panel_1.clone(), cx);
4367            workspace
4368                .left_dock()
4369                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4370            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4371            workspace.add_panel(panel_2.clone(), cx);
4372            workspace
4373                .right_dock()
4374                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4375
4376            let left_dock = workspace.left_dock();
4377            assert_eq!(
4378                left_dock.read(cx).visible_panel().unwrap().id(),
4379                panel_1.id()
4380            );
4381            assert_eq!(
4382                left_dock.read(cx).active_panel_size(cx).unwrap(),
4383                panel_1.size(cx)
4384            );
4385
4386            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4387            assert_eq!(
4388                workspace
4389                    .right_dock()
4390                    .read(cx)
4391                    .visible_panel()
4392                    .unwrap()
4393                    .id(),
4394                panel_2.id()
4395            );
4396
4397            (panel_1, panel_2)
4398        });
4399
4400        // Move panel_1 to the right
4401        panel_1.update(cx, |panel_1, cx| {
4402            panel_1.set_position(DockPosition::Right, cx)
4403        });
4404
4405        workspace.update(cx, |workspace, cx| {
4406            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4407            // Since it was the only panel on the left, the left dock should now be closed.
4408            assert!(!workspace.left_dock().read(cx).is_open());
4409            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4410            let right_dock = workspace.right_dock();
4411            assert_eq!(
4412                right_dock.read(cx).visible_panel().unwrap().id(),
4413                panel_1.id()
4414            );
4415            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4416
4417            // Now we move panel_2Β to the left
4418            panel_2.set_position(DockPosition::Left, cx);
4419        });
4420
4421        workspace.update(cx, |workspace, cx| {
4422            // Since panel_2 was not visible on the right, we don't open the left dock.
4423            assert!(!workspace.left_dock().read(cx).is_open());
4424            // And the right dock is unaffected in it's displaying of panel_1
4425            assert!(workspace.right_dock().read(cx).is_open());
4426            assert_eq!(
4427                workspace
4428                    .right_dock()
4429                    .read(cx)
4430                    .visible_panel()
4431                    .unwrap()
4432                    .id(),
4433                panel_1.id()
4434            );
4435        });
4436
4437        // Move panel_1 back to the left
4438        panel_1.update(cx, |panel_1, cx| {
4439            panel_1.set_position(DockPosition::Left, cx)
4440        });
4441
4442        workspace.update(cx, |workspace, cx| {
4443            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4444            let left_dock = workspace.left_dock();
4445            assert!(left_dock.read(cx).is_open());
4446            assert_eq!(
4447                left_dock.read(cx).visible_panel().unwrap().id(),
4448                panel_1.id()
4449            );
4450            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4451            // And right the dock should be closed as it no longer has any panels.
4452            assert!(!workspace.right_dock().read(cx).is_open());
4453
4454            // Now we move panel_1 to the bottom
4455            panel_1.set_position(DockPosition::Bottom, cx);
4456        });
4457
4458        workspace.update(cx, |workspace, cx| {
4459            // Since panel_1 was visible on the left, we close the left dock.
4460            assert!(!workspace.left_dock().read(cx).is_open());
4461            // The bottom dock is sized based on the panel's default size,
4462            // since the panel orientation changed from vertical to horizontal.
4463            let bottom_dock = workspace.bottom_dock();
4464            assert_eq!(
4465                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4466                panel_1.size(cx),
4467            );
4468            // Close bottom dock and move panel_1 back to the left.
4469            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4470            panel_1.set_position(DockPosition::Left, cx);
4471        });
4472
4473        // Emit activated event on panel 1
4474        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4475
4476        // Now the left dock is open and panel_1 is active and focused.
4477        workspace.read_with(cx, |workspace, cx| {
4478            let left_dock = workspace.left_dock();
4479            assert!(left_dock.read(cx).is_open());
4480            assert_eq!(
4481                left_dock.read(cx).visible_panel().unwrap().id(),
4482                panel_1.id()
4483            );
4484            assert!(panel_1.is_focused(cx));
4485        });
4486
4487        // Emit closed event on panel 2, which is not active
4488        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4489
4490        // Wo don't close the left dock, because panel_2 wasn't the active panel
4491        workspace.read_with(cx, |workspace, cx| {
4492            let left_dock = workspace.left_dock();
4493            assert!(left_dock.read(cx).is_open());
4494            assert_eq!(
4495                left_dock.read(cx).visible_panel().unwrap().id(),
4496                panel_1.id()
4497            );
4498        });
4499
4500        // Emitting a ZoomIn event shows the panel as zoomed.
4501        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4502        workspace.read_with(cx, |workspace, _| {
4503            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4504            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4505        });
4506
4507        // Move panel to another dock while it is zoomed
4508        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4509        workspace.read_with(cx, |workspace, _| {
4510            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4511            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4512        });
4513
4514        // If focus is transferred to another view that's not a panel or another pane, we still show
4515        // the panel as zoomed.
4516        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4517        focus_receiver.update(cx, |_, cx| cx.focus_self());
4518        workspace.read_with(cx, |workspace, _| {
4519            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4520            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4521        });
4522
4523        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4524        workspace.update(cx, |_, cx| cx.focus_self());
4525        workspace.read_with(cx, |workspace, _| {
4526            assert_eq!(workspace.zoomed, None);
4527            assert_eq!(workspace.zoomed_position, None);
4528        });
4529
4530        // If focus is transferred again to another view that's not a panel or a pane, we won't
4531        // show the panel as zoomed because it wasn't zoomed before.
4532        focus_receiver.update(cx, |_, cx| cx.focus_self());
4533        workspace.read_with(cx, |workspace, _| {
4534            assert_eq!(workspace.zoomed, None);
4535            assert_eq!(workspace.zoomed_position, None);
4536        });
4537
4538        // When focus is transferred back to the panel, it is zoomed again.
4539        panel_1.update(cx, |_, cx| cx.focus_self());
4540        workspace.read_with(cx, |workspace, _| {
4541            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4542            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4543        });
4544
4545        // Emitting a ZoomOut event unzooms the panel.
4546        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4547        workspace.read_with(cx, |workspace, _| {
4548            assert_eq!(workspace.zoomed, None);
4549            assert_eq!(workspace.zoomed_position, None);
4550        });
4551
4552        // Emit closed event on panel 1, which is active
4553        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4554
4555        // Now the left dock is closed, because panel_1 was the active panel
4556        workspace.read_with(cx, |workspace, cx| {
4557            let right_dock = workspace.right_dock();
4558            assert!(!right_dock.read(cx).is_open());
4559        });
4560    }
4561
4562    pub fn init_test(cx: &mut TestAppContext) {
4563        cx.foreground().forbid_parking();
4564        cx.update(|cx| {
4565            cx.set_global(SettingsStore::test(cx));
4566            theme::init((), cx);
4567            language::init(cx);
4568            crate::init_settings(cx);
4569        });
4570    }
4571}