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