lib.rs

   1pub mod pane;
   2pub mod pane_group;
   3pub mod settings;
   4pub mod sidebar;
   5mod status_bar;
   6
   7use anyhow::{anyhow, Result};
   8use client::{Authenticate, ChannelList, Client, UserStore};
   9use gpui::{
  10    action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle,
  11    AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext,
  12    PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle,
  13};
  14use language::LanguageRegistry;
  15use log::error;
  16pub use pane::*;
  17pub use pane_group::*;
  18use postage::{prelude::Stream, watch};
  19use project::{Fs, Project, ProjectPath, Worktree};
  20pub use settings::Settings;
  21use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
  22use status_bar::StatusBar;
  23pub use status_bar::StatusItemView;
  24use std::{
  25    collections::{hash_map::Entry, HashMap},
  26    future::Future,
  27    path::{Path, PathBuf},
  28    sync::Arc,
  29};
  30
  31action!(OpenNew, WorkspaceParams);
  32action!(Save);
  33action!(DebugElements);
  34
  35pub fn init(cx: &mut MutableAppContext) {
  36    cx.add_action(Workspace::save_active_item);
  37    cx.add_action(Workspace::debug_elements);
  38    cx.add_action(Workspace::toggle_sidebar_item);
  39    cx.add_action(Workspace::toggle_sidebar_item_focus);
  40    cx.add_bindings(vec![
  41        Binding::new("cmd-s", Save, None),
  42        Binding::new("cmd-alt-i", DebugElements, None),
  43        Binding::new(
  44            "cmd-shift-!",
  45            ToggleSidebarItem(SidebarItemId {
  46                side: Side::Left,
  47                item_index: 0,
  48            }),
  49            None,
  50        ),
  51        Binding::new(
  52            "cmd-1",
  53            ToggleSidebarItemFocus(SidebarItemId {
  54                side: Side::Left,
  55                item_index: 0,
  56            }),
  57            None,
  58        ),
  59    ]);
  60    pane::init(cx);
  61}
  62
  63pub trait EntryOpener {
  64    fn open(
  65        &self,
  66        worktree: &mut Worktree,
  67        path: ProjectPath,
  68        cx: &mut ModelContext<Worktree>,
  69    ) -> Option<Task<Result<Box<dyn ItemHandle>>>>;
  70}
  71
  72pub trait Item: Entity + Sized {
  73    type View: ItemView;
  74
  75    fn build_view(
  76        handle: ModelHandle<Self>,
  77        settings: watch::Receiver<Settings>,
  78        cx: &mut ViewContext<Self::View>,
  79    ) -> Self::View;
  80
  81    fn project_path(&self) -> Option<ProjectPath>;
  82}
  83
  84pub trait ItemView: View {
  85    fn title(&self, cx: &AppContext) -> String;
  86    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
  87    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
  88    where
  89        Self: Sized,
  90    {
  91        None
  92    }
  93    fn is_dirty(&self, _: &AppContext) -> bool {
  94        false
  95    }
  96    fn has_conflict(&self, _: &AppContext) -> bool {
  97        false
  98    }
  99    fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>>;
 100    fn save_as(
 101        &mut self,
 102        worktree: ModelHandle<Worktree>,
 103        path: &Path,
 104        cx: &mut ViewContext<Self>,
 105    ) -> Task<anyhow::Result<()>>;
 106    fn should_activate_item_on_event(_: &Self::Event) -> bool {
 107        false
 108    }
 109    fn should_close_item_on_event(_: &Self::Event) -> bool {
 110        false
 111    }
 112    fn should_update_tab_on_event(_: &Self::Event) -> bool {
 113        false
 114    }
 115}
 116
 117pub trait ItemHandle: Send + Sync {
 118    fn add_view(
 119        &self,
 120        window_id: usize,
 121        settings: watch::Receiver<Settings>,
 122        cx: &mut MutableAppContext,
 123    ) -> Box<dyn ItemViewHandle>;
 124    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
 125    fn downgrade(&self) -> Box<dyn WeakItemHandle>;
 126    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 127}
 128
 129pub trait WeakItemHandle {
 130    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 131}
 132
 133pub trait ItemViewHandle {
 134    fn title(&self, cx: &AppContext) -> String;
 135    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
 136    fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
 137    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
 138    fn set_parent_pane(&self, pane: &ViewHandle<Pane>, cx: &mut MutableAppContext);
 139    fn id(&self) -> usize;
 140    fn to_any(&self) -> AnyViewHandle;
 141    fn is_dirty(&self, cx: &AppContext) -> bool;
 142    fn has_conflict(&self, cx: &AppContext) -> bool;
 143    fn save(&self, cx: &mut MutableAppContext) -> Result<Task<Result<()>>>;
 144    fn save_as(
 145        &self,
 146        worktree: ModelHandle<Worktree>,
 147        path: &Path,
 148        cx: &mut MutableAppContext,
 149    ) -> Task<anyhow::Result<()>>;
 150}
 151
 152impl<T: Item> ItemHandle for ModelHandle<T> {
 153    fn add_view(
 154        &self,
 155        window_id: usize,
 156        settings: watch::Receiver<Settings>,
 157        cx: &mut MutableAppContext,
 158    ) -> Box<dyn ItemViewHandle> {
 159        Box::new(cx.add_view(window_id, |cx| T::build_view(self.clone(), settings, cx)))
 160    }
 161
 162    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 163        Box::new(self.clone())
 164    }
 165
 166    fn downgrade(&self) -> Box<dyn WeakItemHandle> {
 167        Box::new(self.downgrade())
 168    }
 169
 170    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 171        self.read(cx).project_path()
 172    }
 173}
 174
 175impl ItemHandle for Box<dyn ItemHandle> {
 176    fn add_view(
 177        &self,
 178        window_id: usize,
 179        settings: watch::Receiver<Settings>,
 180        cx: &mut MutableAppContext,
 181    ) -> Box<dyn ItemViewHandle> {
 182        ItemHandle::add_view(self.as_ref(), window_id, settings, cx)
 183    }
 184
 185    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
 186        self.as_ref().boxed_clone()
 187    }
 188
 189    fn downgrade(&self) -> Box<dyn WeakItemHandle> {
 190        self.as_ref().downgrade()
 191    }
 192
 193    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 194        self.as_ref().project_path(cx)
 195    }
 196}
 197
 198impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
 199    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
 200        WeakModelHandle::<T>::upgrade(*self, cx).map(|i| Box::new(i) as Box<dyn ItemHandle>)
 201    }
 202}
 203
 204impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
 205    fn title(&self, cx: &AppContext) -> String {
 206        self.read(cx).title(cx)
 207    }
 208
 209    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
 210        self.read(cx).project_path(cx)
 211    }
 212
 213    fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
 214        Box::new(self.clone())
 215    }
 216
 217    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>> {
 218        self.update(cx, |item, cx| {
 219            cx.add_option_view(|cx| item.clone_on_split(cx))
 220        })
 221        .map(|handle| Box::new(handle) as Box<dyn ItemViewHandle>)
 222    }
 223
 224    fn set_parent_pane(&self, pane: &ViewHandle<Pane>, cx: &mut MutableAppContext) {
 225        pane.update(cx, |_, cx| {
 226            cx.subscribe(self, |pane, item, event, cx| {
 227                if T::should_close_item_on_event(event) {
 228                    pane.close_item(item.id(), cx);
 229                    return;
 230                }
 231                if T::should_activate_item_on_event(event) {
 232                    if let Some(ix) = pane.item_index(&item) {
 233                        pane.activate_item(ix, cx);
 234                        pane.activate(cx);
 235                    }
 236                }
 237                if T::should_update_tab_on_event(event) {
 238                    cx.notify()
 239                }
 240            })
 241            .detach();
 242        });
 243    }
 244
 245    fn save(&self, cx: &mut MutableAppContext) -> Result<Task<Result<()>>> {
 246        self.update(cx, |item, cx| item.save(cx))
 247    }
 248
 249    fn save_as(
 250        &self,
 251        worktree: ModelHandle<Worktree>,
 252        path: &Path,
 253        cx: &mut MutableAppContext,
 254    ) -> Task<anyhow::Result<()>> {
 255        self.update(cx, |item, cx| item.save_as(worktree, path, cx))
 256    }
 257
 258    fn is_dirty(&self, cx: &AppContext) -> bool {
 259        self.read(cx).is_dirty(cx)
 260    }
 261
 262    fn has_conflict(&self, cx: &AppContext) -> bool {
 263        self.read(cx).has_conflict(cx)
 264    }
 265
 266    fn id(&self) -> usize {
 267        self.id()
 268    }
 269
 270    fn to_any(&self) -> AnyViewHandle {
 271        self.into()
 272    }
 273}
 274
 275impl Clone for Box<dyn ItemViewHandle> {
 276    fn clone(&self) -> Box<dyn ItemViewHandle> {
 277        self.boxed_clone()
 278    }
 279}
 280
 281impl Clone for Box<dyn ItemHandle> {
 282    fn clone(&self) -> Box<dyn ItemHandle> {
 283        self.boxed_clone()
 284    }
 285}
 286
 287#[derive(Clone)]
 288pub struct WorkspaceParams {
 289    pub client: Arc<Client>,
 290    pub fs: Arc<dyn Fs>,
 291    pub languages: Arc<LanguageRegistry>,
 292    pub settings: watch::Receiver<Settings>,
 293    pub user_store: ModelHandle<UserStore>,
 294    pub channel_list: ModelHandle<ChannelList>,
 295    pub entry_openers: Arc<[Box<dyn EntryOpener>]>,
 296}
 297
 298impl WorkspaceParams {
 299    #[cfg(any(test, feature = "test-support"))]
 300    pub fn test(cx: &mut MutableAppContext) -> Self {
 301        let languages = LanguageRegistry::new();
 302        let client = Client::new();
 303        let http_client = client::test::FakeHttpClient::new(|_| async move {
 304            Ok(client::http::ServerResponse::new(404))
 305        });
 306        let theme =
 307            gpui::fonts::with_font_cache(cx.font_cache().clone(), || theme::Theme::default());
 308        let settings = Settings::new("Courier", cx.font_cache(), Arc::new(theme)).unwrap();
 309        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 310        Self {
 311            channel_list: cx
 312                .add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
 313            client,
 314            fs: Arc::new(project::FakeFs::new()),
 315            languages: Arc::new(languages),
 316            settings: watch::channel_with(settings).1,
 317            user_store,
 318            entry_openers: Arc::from([]),
 319        }
 320    }
 321}
 322
 323pub struct Workspace {
 324    pub settings: watch::Receiver<Settings>,
 325    client: Arc<Client>,
 326    user_store: ModelHandle<client::UserStore>,
 327    fs: Arc<dyn Fs>,
 328    modal: Option<AnyViewHandle>,
 329    center: PaneGroup,
 330    left_sidebar: Sidebar,
 331    right_sidebar: Sidebar,
 332    panes: Vec<ViewHandle<Pane>>,
 333    active_pane: ViewHandle<Pane>,
 334    status_bar: ViewHandle<StatusBar>,
 335    project: ModelHandle<Project>,
 336    entry_openers: Arc<[Box<dyn EntryOpener>]>,
 337    items: Vec<Box<dyn WeakItemHandle>>,
 338    loading_items: HashMap<
 339        ProjectPath,
 340        postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
 341    >,
 342    _observe_current_user: Task<()>,
 343}
 344
 345impl Workspace {
 346    pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
 347        let project = cx.add_model(|_| {
 348            Project::new(
 349                params.languages.clone(),
 350                params.client.clone(),
 351                params.fs.clone(),
 352            )
 353        });
 354        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 355
 356        let pane = cx.add_view(|_| Pane::new(params.settings.clone()));
 357        let pane_id = pane.id();
 358        cx.observe(&pane, move |me, _, cx| {
 359            let active_entry = me.active_project_path(cx);
 360            me.project
 361                .update(cx, |project, cx| project.set_active_path(active_entry, cx));
 362        })
 363        .detach();
 364        cx.subscribe(&pane, move |me, _, event, cx| {
 365            me.handle_pane_event(pane_id, event, cx)
 366        })
 367        .detach();
 368        cx.focus(&pane);
 369
 370        let status_bar = cx.add_view(|cx| StatusBar::new(&pane, params.settings.clone(), cx));
 371        let mut current_user = params.user_store.read(cx).watch_current_user().clone();
 372        let mut connection_status = params.client.status().clone();
 373        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
 374            current_user.recv().await;
 375            connection_status.recv().await;
 376            let mut stream =
 377                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 378
 379            while stream.recv().await.is_some() {
 380                cx.update(|cx| {
 381                    if let Some(this) = this.upgrade(&cx) {
 382                        this.update(cx, |_, cx| cx.notify());
 383                    }
 384                })
 385            }
 386        });
 387
 388        Workspace {
 389            modal: None,
 390            center: PaneGroup::new(pane.id()),
 391            panes: vec![pane.clone()],
 392            active_pane: pane.clone(),
 393            status_bar,
 394            settings: params.settings.clone(),
 395            client: params.client.clone(),
 396            user_store: params.user_store.clone(),
 397            fs: params.fs.clone(),
 398            left_sidebar: Sidebar::new(Side::Left),
 399            right_sidebar: Sidebar::new(Side::Right),
 400            project,
 401            entry_openers: params.entry_openers.clone(),
 402            items: Default::default(),
 403            loading_items: Default::default(),
 404            _observe_current_user,
 405        }
 406    }
 407
 408    pub fn left_sidebar_mut(&mut self) -> &mut Sidebar {
 409        &mut self.left_sidebar
 410    }
 411
 412    pub fn right_sidebar_mut(&mut self) -> &mut Sidebar {
 413        &mut self.right_sidebar
 414    }
 415
 416    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 417        &self.status_bar
 418    }
 419
 420    pub fn project(&self) -> &ModelHandle<Project> {
 421        &self.project
 422    }
 423
 424    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> &'a [ModelHandle<Worktree>] {
 425        &self.project.read(cx).worktrees()
 426    }
 427
 428    pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
 429        paths.iter().all(|path| self.contains_path(&path, cx))
 430    }
 431
 432    pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
 433        for worktree in self.worktrees(cx) {
 434            let worktree = worktree.read(cx).as_local();
 435            if worktree.map_or(false, |w| w.contains_abs_path(path)) {
 436                return true;
 437            }
 438        }
 439        false
 440    }
 441
 442    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
 443        let futures = self
 444            .worktrees(cx)
 445            .iter()
 446            .filter_map(|worktree| worktree.read(cx).as_local())
 447            .map(|worktree| worktree.scan_complete())
 448            .collect::<Vec<_>>();
 449        async move {
 450            for future in futures {
 451                future.await;
 452            }
 453        }
 454    }
 455
 456    pub fn open_paths(&mut self, abs_paths: &[PathBuf], cx: &mut ViewContext<Self>) -> Task<()> {
 457        let entries = abs_paths
 458            .iter()
 459            .cloned()
 460            .map(|path| self.project_path_for_path(&path, cx))
 461            .collect::<Vec<_>>();
 462
 463        let fs = self.fs.clone();
 464        let tasks = abs_paths
 465            .iter()
 466            .cloned()
 467            .zip(entries.into_iter())
 468            .map(|(abs_path, project_path)| {
 469                cx.spawn(|this, mut cx| {
 470                    let fs = fs.clone();
 471                    async move {
 472                        let project_path = project_path.await?;
 473                        if fs.is_file(&abs_path).await {
 474                            if let Some(entry) =
 475                                this.update(&mut cx, |this, cx| this.open_entry(project_path, cx))
 476                            {
 477                                entry.await;
 478                            }
 479                        }
 480                        Ok(())
 481                    }
 482                })
 483            })
 484            .collect::<Vec<Task<Result<()>>>>();
 485
 486        cx.foreground().spawn(async move {
 487            for task in tasks {
 488                if let Err(error) = task.await {
 489                    log::error!("error opening paths {}", error);
 490                }
 491            }
 492        })
 493    }
 494
 495    fn worktree_for_abs_path(
 496        &self,
 497        abs_path: &Path,
 498        cx: &mut ViewContext<Self>,
 499    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
 500        let abs_path: Arc<Path> = Arc::from(abs_path);
 501        cx.spawn(|this, mut cx| async move {
 502            let mut entry_id = None;
 503            this.read_with(&cx, |this, cx| {
 504                for tree in this.worktrees(cx) {
 505                    if let Some(relative_path) = tree
 506                        .read(cx)
 507                        .as_local()
 508                        .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
 509                    {
 510                        entry_id = Some((tree.clone(), relative_path.into()));
 511                        break;
 512                    }
 513                }
 514            });
 515
 516            if let Some(entry_id) = entry_id {
 517                Ok(entry_id)
 518            } else {
 519                let worktree = this
 520                    .update(&mut cx, |this, cx| this.add_worktree(&abs_path, cx))
 521                    .await?;
 522                Ok((worktree, PathBuf::new()))
 523            }
 524        })
 525    }
 526
 527    fn project_path_for_path(
 528        &self,
 529        abs_path: &Path,
 530        cx: &mut ViewContext<Self>,
 531    ) -> Task<Result<ProjectPath>> {
 532        let entry = self.worktree_for_abs_path(abs_path, cx);
 533        cx.spawn(|_, _| async move {
 534            let (worktree, path) = entry.await?;
 535            Ok(ProjectPath {
 536                worktree_id: worktree.id(),
 537                path: path.into(),
 538            })
 539        })
 540    }
 541
 542    pub fn add_worktree(
 543        &self,
 544        path: &Path,
 545        cx: &mut ViewContext<Self>,
 546    ) -> Task<Result<ModelHandle<Worktree>>> {
 547        self.project
 548            .update(cx, |project, cx| project.add_local_worktree(path, cx))
 549    }
 550
 551    pub fn toggle_modal<V, F>(&mut self, cx: &mut ViewContext<Self>, add_view: F)
 552    where
 553        V: 'static + View,
 554        F: FnOnce(&mut ViewContext<Self>, &mut Self) -> ViewHandle<V>,
 555    {
 556        if self.modal.as_ref().map_or(false, |modal| modal.is::<V>()) {
 557            self.modal.take();
 558            cx.focus_self();
 559        } else {
 560            let modal = add_view(cx, self);
 561            cx.focus(&modal);
 562            self.modal = Some(modal.into());
 563        }
 564        cx.notify();
 565    }
 566
 567    pub fn modal(&self) -> Option<&AnyViewHandle> {
 568        self.modal.as_ref()
 569    }
 570
 571    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
 572        if self.modal.take().is_some() {
 573            cx.focus(&self.active_pane);
 574            cx.notify();
 575        }
 576    }
 577
 578    #[must_use]
 579    pub fn open_entry(
 580        &mut self,
 581        project_path: ProjectPath,
 582        cx: &mut ViewContext<Self>,
 583    ) -> Option<Task<()>> {
 584        let pane = self.active_pane().clone();
 585        if self.activate_or_open_existing_entry(project_path.clone(), &pane, cx) {
 586            return None;
 587        }
 588
 589        let worktree = match self
 590            .project
 591            .read(cx)
 592            .worktree_for_id(project_path.worktree_id)
 593        {
 594            Some(worktree) => worktree,
 595            None => {
 596                log::error!("worktree {} does not exist", project_path.worktree_id);
 597                return None;
 598            }
 599        };
 600
 601        if let Entry::Vacant(entry) = self.loading_items.entry(project_path.clone()) {
 602            let (mut tx, rx) = postage::watch::channel();
 603            entry.insert(rx);
 604
 605            let project_path = project_path.clone();
 606            let entry_openers = self.entry_openers.clone();
 607            cx.as_mut()
 608                .spawn(|mut cx| async move {
 609                    let item = worktree.update(&mut cx, move |worktree, cx| {
 610                        for opener in entry_openers.iter() {
 611                            if let Some(task) = opener.open(worktree, project_path.clone(), cx) {
 612                                return task;
 613                            }
 614                        }
 615
 616                        cx.spawn(|_, _| async move {
 617                            Err(anyhow!("no opener for path {:?} found", project_path))
 618                        })
 619                    });
 620                    *tx.borrow_mut() = Some(item.await.map_err(Arc::new));
 621                })
 622                .detach();
 623        }
 624
 625        let pane = pane.downgrade();
 626        let mut watch = self.loading_items.get(&project_path).unwrap().clone();
 627
 628        Some(cx.spawn(|this, mut cx| async move {
 629            let load_result = loop {
 630                if let Some(load_result) = watch.borrow().as_ref() {
 631                    break load_result.clone();
 632                }
 633                watch.recv().await;
 634            };
 635
 636            this.update(&mut cx, |this, cx| {
 637                this.loading_items.remove(&project_path);
 638                if let Some(pane) = pane.upgrade(&cx) {
 639                    match load_result {
 640                        Ok(item) => {
 641                            // By the time loading finishes, the entry could have been already added
 642                            // to the pane. If it was, we activate it, otherwise we'll store the
 643                            // item and add a new view for it.
 644                            if !this.activate_or_open_existing_entry(project_path, &pane, cx) {
 645                                this.add_item(item, cx);
 646                            }
 647                        }
 648                        Err(error) => {
 649                            log::error!("error opening item: {}", error);
 650                        }
 651                    }
 652                }
 653            })
 654        }))
 655    }
 656
 657    fn activate_or_open_existing_entry(
 658        &mut self,
 659        project_path: ProjectPath,
 660        pane: &ViewHandle<Pane>,
 661        cx: &mut ViewContext<Self>,
 662    ) -> bool {
 663        // If the pane contains a view for this file, then activate
 664        // that item view.
 665        if pane.update(cx, |pane, cx| pane.activate_entry(project_path.clone(), cx)) {
 666            return true;
 667        }
 668
 669        // Otherwise, if this file is already open somewhere in the workspace,
 670        // then add another view for it.
 671        let settings = self.settings.clone();
 672        let mut view_for_existing_item = None;
 673        self.items.retain(|item| {
 674            if let Some(item) = item.upgrade(cx) {
 675                if view_for_existing_item.is_none()
 676                    && item
 677                        .project_path(cx)
 678                        .map_or(false, |item_project_path| item_project_path == project_path)
 679                {
 680                    view_for_existing_item =
 681                        Some(item.add_view(cx.window_id(), settings.clone(), cx.as_mut()));
 682                }
 683                true
 684            } else {
 685                false
 686            }
 687        });
 688        if let Some(view) = view_for_existing_item {
 689            pane.add_item_view(view, cx.as_mut());
 690            true
 691        } else {
 692            false
 693        }
 694    }
 695
 696    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>> {
 697        self.active_pane().read(cx).active_item()
 698    }
 699
 700    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
 701        self.active_item(cx).and_then(|item| item.project_path(cx))
 702    }
 703
 704    pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
 705        if let Some(item) = self.active_item(cx) {
 706            let handle = cx.handle();
 707            if item.project_path(cx.as_ref()).is_none() {
 708                let worktree = self.worktrees(cx).first();
 709                let start_abs_path = worktree
 710                    .and_then(|w| w.read(cx).as_local())
 711                    .map_or(Path::new(""), |w| w.abs_path())
 712                    .to_path_buf();
 713                cx.prompt_for_new_path(&start_abs_path, move |abs_path, cx| {
 714                    if let Some(abs_path) = abs_path {
 715                        cx.spawn(|mut cx| async move {
 716                            let result = match handle
 717                                .update(&mut cx, |this, cx| {
 718                                    this.worktree_for_abs_path(&abs_path, cx)
 719                                })
 720                                .await
 721                            {
 722                                Ok((worktree, path)) => {
 723                                    handle
 724                                        .update(&mut cx, |_, cx| {
 725                                            item.save_as(worktree, &path, cx.as_mut())
 726                                        })
 727                                        .await
 728                                }
 729                                Err(error) => Err(error),
 730                            };
 731
 732                            if let Err(error) = result {
 733                                error!("failed to save item: {:?}, ", error);
 734                            }
 735                        })
 736                        .detach()
 737                    }
 738                });
 739                return;
 740            } else if item.has_conflict(cx.as_ref()) {
 741                const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
 742
 743                cx.prompt(
 744                    PromptLevel::Warning,
 745                    CONFLICT_MESSAGE,
 746                    &["Overwrite", "Cancel"],
 747                    move |answer, cx| {
 748                        if answer == 0 {
 749                            cx.spawn(|mut cx| async move {
 750                                if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
 751                                    error!("failed to save item: {:?}, ", error);
 752                                }
 753                            })
 754                            .detach();
 755                        }
 756                    },
 757                );
 758            } else {
 759                cx.spawn(|_, mut cx| async move {
 760                    if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
 761                        error!("failed to save item: {:?}, ", error);
 762                    }
 763                })
 764                .detach();
 765            }
 766        }
 767    }
 768
 769    pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
 770        let sidebar = match action.0.side {
 771            Side::Left => &mut self.left_sidebar,
 772            Side::Right => &mut self.right_sidebar,
 773        };
 774        sidebar.toggle_item(action.0.item_index);
 775        if let Some(active_item) = sidebar.active_item() {
 776            cx.focus(active_item);
 777        } else {
 778            cx.focus_self();
 779        }
 780        cx.notify();
 781    }
 782
 783    pub fn toggle_sidebar_item_focus(
 784        &mut self,
 785        action: &ToggleSidebarItemFocus,
 786        cx: &mut ViewContext<Self>,
 787    ) {
 788        let sidebar = match action.0.side {
 789            Side::Left => &mut self.left_sidebar,
 790            Side::Right => &mut self.right_sidebar,
 791        };
 792        sidebar.activate_item(action.0.item_index);
 793        if let Some(active_item) = sidebar.active_item() {
 794            if active_item.is_focused(cx) {
 795                cx.focus_self();
 796            } else {
 797                cx.focus(active_item);
 798            }
 799        }
 800        cx.notify();
 801    }
 802
 803    pub fn debug_elements(&mut self, _: &DebugElements, cx: &mut ViewContext<Self>) {
 804        match to_string_pretty(&cx.debug_elements()) {
 805            Ok(json) => {
 806                let kib = json.len() as f32 / 1024.;
 807                cx.as_mut().write_to_clipboard(ClipboardItem::new(json));
 808                log::info!(
 809                    "copied {:.1} KiB of element debug JSON to the clipboard",
 810                    kib
 811                );
 812            }
 813            Err(error) => {
 814                log::error!("error debugging elements: {}", error);
 815            }
 816        };
 817    }
 818
 819    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
 820        let pane = cx.add_view(|_| Pane::new(self.settings.clone()));
 821        let pane_id = pane.id();
 822        cx.observe(&pane, move |me, _, cx| {
 823            let active_entry = me.active_project_path(cx);
 824            me.project
 825                .update(cx, |project, cx| project.set_active_path(active_entry, cx));
 826        })
 827        .detach();
 828        cx.subscribe(&pane, move |me, _, event, cx| {
 829            me.handle_pane_event(pane_id, event, cx)
 830        })
 831        .detach();
 832        self.panes.push(pane.clone());
 833        self.activate_pane(pane.clone(), cx);
 834        pane
 835    }
 836
 837    pub fn add_item<T>(&mut self, item_handle: T, cx: &mut ViewContext<Self>)
 838    where
 839        T: ItemHandle,
 840    {
 841        let view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx);
 842        self.items.push(item_handle.downgrade());
 843        self.active_pane().add_item_view(view, cx.as_mut());
 844    }
 845
 846    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
 847        self.active_pane = pane;
 848        self.status_bar.update(cx, |status_bar, cx| {
 849            status_bar.set_active_pane(&self.active_pane, cx);
 850        });
 851        cx.focus(&self.active_pane);
 852        cx.notify();
 853    }
 854
 855    fn handle_pane_event(
 856        &mut self,
 857        pane_id: usize,
 858        event: &pane::Event,
 859        cx: &mut ViewContext<Self>,
 860    ) {
 861        if let Some(pane) = self.pane(pane_id) {
 862            match event {
 863                pane::Event::Split(direction) => {
 864                    self.split_pane(pane, *direction, cx);
 865                }
 866                pane::Event::Remove => {
 867                    self.remove_pane(pane, cx);
 868                }
 869                pane::Event::Activate => {
 870                    self.activate_pane(pane, cx);
 871                }
 872            }
 873        } else {
 874            error!("pane {} not found", pane_id);
 875        }
 876    }
 877
 878    pub fn split_pane(
 879        &mut self,
 880        pane: ViewHandle<Pane>,
 881        direction: SplitDirection,
 882        cx: &mut ViewContext<Self>,
 883    ) -> ViewHandle<Pane> {
 884        let new_pane = self.add_pane(cx);
 885        self.activate_pane(new_pane.clone(), cx);
 886        if let Some(item) = pane.read(cx).active_item() {
 887            if let Some(clone) = item.clone_on_split(cx.as_mut()) {
 888                new_pane.add_item_view(clone, cx.as_mut());
 889            }
 890        }
 891        self.center
 892            .split(pane.id(), new_pane.id(), direction)
 893            .unwrap();
 894        cx.notify();
 895        new_pane
 896    }
 897
 898    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
 899        if self.center.remove(pane.id()).unwrap() {
 900            self.panes.retain(|p| p != &pane);
 901            self.activate_pane(self.panes.last().unwrap().clone(), cx);
 902        }
 903    }
 904
 905    pub fn panes(&self) -> &[ViewHandle<Pane>] {
 906        &self.panes
 907    }
 908
 909    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
 910        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
 911    }
 912
 913    pub fn active_pane(&self) -> &ViewHandle<Pane> {
 914        &self.active_pane
 915    }
 916
 917    fn render_connection_status(&self) -> Option<ElementBox> {
 918        let theme = &self.settings.borrow().theme;
 919        match &*self.client.status().borrow() {
 920            client::Status::ConnectionError
 921            | client::Status::ConnectionLost
 922            | client::Status::Reauthenticating
 923            | client::Status::Reconnecting { .. }
 924            | client::Status::ReconnectionError { .. } => Some(
 925                Container::new(
 926                    Align::new(
 927                        ConstrainedBox::new(
 928                            Svg::new("icons/offline-14.svg")
 929                                .with_color(theme.workspace.titlebar.icon_color)
 930                                .boxed(),
 931                        )
 932                        .with_width(theme.workspace.titlebar.offline_icon.width)
 933                        .boxed(),
 934                    )
 935                    .boxed(),
 936                )
 937                .with_style(theme.workspace.titlebar.offline_icon.container)
 938                .boxed(),
 939            ),
 940            client::Status::UpgradeRequired => Some(
 941                Label::new(
 942                    "Please update Zed to collaborate".to_string(),
 943                    theme.workspace.titlebar.outdated_warning.text.clone(),
 944                )
 945                .contained()
 946                .with_style(theme.workspace.titlebar.outdated_warning.container)
 947                .aligned()
 948                .boxed(),
 949            ),
 950            _ => None,
 951        }
 952    }
 953
 954    fn render_avatar(&self, cx: &mut RenderContext<Self>) -> ElementBox {
 955        let theme = &self.settings.borrow().theme;
 956        if let Some(avatar) = self
 957            .user_store
 958            .read(cx)
 959            .current_user()
 960            .and_then(|user| user.avatar.clone())
 961        {
 962            ConstrainedBox::new(
 963                Align::new(
 964                    ConstrainedBox::new(
 965                        Image::new(avatar)
 966                            .with_style(theme.workspace.titlebar.avatar)
 967                            .boxed(),
 968                    )
 969                    .with_width(theme.workspace.titlebar.avatar_width)
 970                    .boxed(),
 971                )
 972                .boxed(),
 973            )
 974            .with_width(theme.workspace.right_sidebar.width)
 975            .boxed()
 976        } else {
 977            MouseEventHandler::new::<Authenticate, _, _, _>(0, cx, |state, _| {
 978                let style = if state.hovered {
 979                    &theme.workspace.titlebar.hovered_sign_in_prompt
 980                } else {
 981                    &theme.workspace.titlebar.sign_in_prompt
 982                };
 983                Label::new("Sign in".to_string(), style.text.clone())
 984                    .contained()
 985                    .with_style(style.container)
 986                    .boxed()
 987            })
 988            .on_click(|cx| cx.dispatch_action(Authenticate))
 989            .with_cursor_style(CursorStyle::PointingHand)
 990            .aligned()
 991            .boxed()
 992        }
 993    }
 994}
 995
 996impl Entity for Workspace {
 997    type Event = ();
 998}
 999
1000impl View for Workspace {
1001    fn ui_name() -> &'static str {
1002        "Workspace"
1003    }
1004
1005    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
1006        let settings = self.settings.borrow();
1007        let theme = &settings.theme;
1008        Container::new(
1009            Flex::column()
1010                .with_child(
1011                    ConstrainedBox::new(
1012                        Container::new(
1013                            Stack::new()
1014                                .with_child(
1015                                    Align::new(
1016                                        Label::new(
1017                                            "zed".into(),
1018                                            theme.workspace.titlebar.title.clone(),
1019                                        )
1020                                        .boxed(),
1021                                    )
1022                                    .boxed(),
1023                                )
1024                                .with_child(
1025                                    Align::new(
1026                                        Flex::row()
1027                                            .with_children(self.render_connection_status())
1028                                            .with_child(self.render_avatar(cx))
1029                                            .boxed(),
1030                                    )
1031                                    .right()
1032                                    .boxed(),
1033                                )
1034                                .boxed(),
1035                        )
1036                        .with_style(theme.workspace.titlebar.container)
1037                        .boxed(),
1038                    )
1039                    .with_height(32.)
1040                    .named("titlebar"),
1041                )
1042                .with_child(
1043                    Expanded::new(
1044                        1.0,
1045                        Stack::new()
1046                            .with_child({
1047                                let mut content = Flex::row();
1048                                content.add_child(self.left_sidebar.render(&settings, cx));
1049                                if let Some(element) =
1050                                    self.left_sidebar.render_active_item(&settings, cx)
1051                                {
1052                                    content.add_child(Flexible::new(0.8, element).boxed());
1053                                }
1054                                content.add_child(
1055                                    Flex::column()
1056                                        .with_child(
1057                                            Expanded::new(1.0, self.center.render(&settings.theme))
1058                                                .boxed(),
1059                                        )
1060                                        .with_child(ChildView::new(self.status_bar.id()).boxed())
1061                                        .expanded(1.)
1062                                        .boxed(),
1063                                );
1064                                if let Some(element) =
1065                                    self.right_sidebar.render_active_item(&settings, cx)
1066                                {
1067                                    content.add_child(Flexible::new(0.8, element).boxed());
1068                                }
1069                                content.add_child(self.right_sidebar.render(&settings, cx));
1070                                content.boxed()
1071                            })
1072                            .with_children(
1073                                self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()),
1074                            )
1075                            .boxed(),
1076                    )
1077                    .boxed(),
1078                )
1079                .boxed(),
1080        )
1081        .with_background_color(settings.theme.workspace.background)
1082        .named("workspace")
1083    }
1084
1085    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
1086        cx.focus(&self.active_pane);
1087    }
1088}
1089
1090pub trait WorkspaceHandle {
1091    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
1092}
1093
1094impl WorkspaceHandle for ViewHandle<Workspace> {
1095    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
1096        self.read(cx)
1097            .worktrees(cx)
1098            .iter()
1099            .flat_map(|worktree| {
1100                let worktree_id = worktree.id();
1101                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
1102                    worktree_id,
1103                    path: f.path.clone(),
1104                })
1105            })
1106            .collect::<Vec<_>>()
1107    }
1108}