workspace.rs

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