pub mod dock;pub mod item;mod modal_layer;pub mod notifications;pub mod pane;pub mod pane_group;mod persistence;pub mod searchable;pub mod shared_screen;mod status_bar;pub mod tasks;mod theme_preview;mod toolbar;mod workspace_settings; use anyhow::{anyhow, Context as _, Result};use call::{call_settings::CallSettings, ActiveCall};use client::{ proto::{self, ErrorCode, PanelId, PeerId}, ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,};use collections::{hash_map, HashMap, HashSet};use derive_more::{Deref, DerefMut};use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};use futures::{ channel::{ mpsc::{self, UnboundedReceiver, UnboundedSender}, oneshot, }, future::try_join_all, Future, FutureExt, StreamExt,};use gpui::{ action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size, transparent_black, Action, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId, EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle, WindowId, WindowOptions,};pub use item::{ FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,};use itertools::Itertools;use language::{LanguageRegistry, Rope};pub use modal_layer::*;use node_runtime::NodeRuntime;use notifications::{ simple_message_notification::MessageNotification, DetachAndPromptErr, NotificationHandle,};pub use pane::*;pub use pane_group::*;pub use persistence::{ model::{ItemId, LocalPaths, SerializedWorkspaceLocation}, WorkspaceDb, DB as WORKSPACE_DB,};use persistence::{ model::{SerializedSshProject, SerializedWorkspace}, SerializedWindowBounds, DB,};use postage::stream::Stream;use project::{ DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,};use remote::{ssh_session::ConnectionIdentifier, SshClientDelegate, SshConnectionOptions};use serde::Deserialize;use session::AppSession;use settings::Settings;use shared_screen::SharedScreen;use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement,};use status_bar::StatusBar;pub use status_bar::StatusItemView;use std::{ any::TypeId, borrow::Cow, cell::RefCell, cmp, collections::hash_map::DefaultHasher, env, hash::{Hash, Hasher}, path::{Path, PathBuf}, rc::Rc, sync::{atomic::AtomicUsize, Arc, LazyLock, Weak}, time::Duration,};use task::SpawnInTerminal;use theme::{ActiveTheme, SystemAppearance, ThemeSettings};pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};pub use ui;use ui::prelude::*;use util::{paths::SanitizedPath, ResultExt, TryFutureExt};use uuid::Uuid;pub use workspace_settings::{ AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,}; use crate::notifications::NotificationId;use crate::persistence::{ model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup}, SerializedAxis,}; static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| { env::var("ZED_WINDOW_SIZE") .ok() .as_deref() .and_then(parse_pixel_size_env_var)}); static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| { env::var("ZED_WINDOW_POSITION") .ok() .as_deref() .and_then(parse_pixel_position_env_var)}); #[derive(Clone, PartialEq)]pub struct RemoveWorktreeFromProject(pub WorktreeId); actions!(assistant, [ShowConfiguration]); actions!( workspace, [ ActivateNextPane, ActivatePreviousPane, AddFolderToProject, ClearAllNotifications, CloseAllDocks, CloseWindow, CopyPath, CopyRelativePath, Feedback, FollowNextCollaborator, NewCenterTerminal, NewFile, NewFileSplitVertical, NewFileSplitHorizontal, NewSearch, NewTerminal, NewWindow, Open, OpenInTerminal, ReloadActiveItem, SaveAs, SaveWithoutFormat, ToggleBottomDock, ToggleCenteredLayout, ToggleLeftDock, ToggleRightDock, ToggleZoom, Unfollow, Welcome, ]); #[derive(Clone, PartialEq)]pub struct OpenPaths { pub paths: Vec<PathBuf>,} #[derive(Clone, Deserialize, PartialEq)]pub struct ActivatePane(pub usize); #[derive(Clone, Deserialize, PartialEq)]pub struct ActivatePaneInDirection(pub SplitDirection); #[derive(Clone, Deserialize, PartialEq)]pub struct SwapPaneInDirection(pub SplitDirection); #[derive(Clone, PartialEq, Debug, Deserialize)]#[serde(rename_all = "camelCase")]pub struct SaveAll { pub save_intent: Option<SaveIntent>,} #[derive(Clone, PartialEq, Debug, Deserialize)]#[serde(rename_all = "camelCase")]pub struct Save { pub save_intent: Option<SaveIntent>,} #[derive(Clone, PartialEq, Debug, Deserialize, Default)]#[serde(rename_all = "camelCase")]pub struct CloseAllItemsAndPanes { pub save_intent: Option<SaveIntent>,} #[derive(Clone, PartialEq, Debug, Deserialize, Default)]#[serde(rename_all = "camelCase")]pub struct CloseInactiveTabsAndPanes { pub save_intent: Option<SaveIntent>,} #[derive(Clone, Deserialize, PartialEq)]pub struct SendKeystrokes(pub String); #[derive(Clone, Deserialize, PartialEq, Default)]pub struct Reload { pub binary_path: Option<PathBuf>,} action_as!(project_symbols, ToggleProjectSymbols as Toggle); #[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)]pub struct ToggleFileFinder { #[serde(default)] pub separate_history: bool,} impl_action_as!(file_finder, ToggleFileFinder as Toggle); impl_actions!( workspace, [ ActivatePane, ActivatePaneInDirection, CloseAllItemsAndPanes, CloseInactiveTabsAndPanes, OpenTerminal, Reload, Save, SaveAll, SwapPaneInDirection, SendKeystrokes, ]); #[derive(PartialEq, Eq, Debug)]pub enum CloseIntent { /// Quit the program entirely. Quit, /// Close a window. CloseWindow, /// Replace the workspace in an existing window. ReplaceWindow,} #[derive(Clone)]pub struct Toast { id: NotificationId, msg: Cow<'static, str>, autohide: bool, on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,} impl Toast { pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self { Toast { id, msg: msg.into(), on_click: None, autohide: false, } } pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self where M: Into<Cow<'static, str>>, F: Fn(&mut WindowContext) + 'static, { self.on_click = Some((message.into(), Arc::new(on_click))); self } pub fn autohide(mut self) -> Self { self.autohide = true; self }} impl PartialEq for Toast { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.msg == other.msg && self.on_click.is_some() == other.on_click.is_some() }} #[derive(Debug, Default, Clone, Deserialize, PartialEq)]pub struct OpenTerminal { pub working_directory: PathBuf,} #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]pub struct WorkspaceId(i64); impl StaticColumnCount for WorkspaceId {}impl Bind for WorkspaceId { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { self.0.bind(statement, start_index) }}impl Column for WorkspaceId { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { i64::column(statement, start_index) .map(|(i, next_index)| (Self(i), next_index)) .with_context(|| format!("Failed to read WorkspaceId at index {start_index}")) }}impl From<WorkspaceId> for i64 { fn from(val: WorkspaceId) -> Self { val.0 }} pub fn init_settings(cx: &mut AppContext) { WorkspaceSettings::register(cx); ItemSettings::register(cx); PreviewTabsSettings::register(cx); TabBarSettings::register(cx);} pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); theme_preview::init(cx); cx.on_action(Workspace::close_global); cx.on_action(reload); cx.on_action({ let app_state = Arc::downgrade(&app_state); move |_: &Open, cx: &mut AppContext| { let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, }); if let Some(app_state) = app_state.upgrade() { cx.spawn(move |cx| async move { match Flatten::flatten(paths.await.map_err(|e| e.into())) { Ok(Some(paths)) => { cx.update(|cx| { open_paths(&paths, app_state, OpenOptions::default(), cx) .detach_and_log_err(cx) }) .ok(); } Ok(None) => {} Err(err) => { cx.update(|cx| { if let Some(workspace_window) = cx .active_window() .and_then(|window| window.downcast::<Workspace>()) { workspace_window .update(cx, |workspace, cx| { workspace.show_portal_error(err.to_string(), cx); }) .ok(); } }) .ok(); } }; }) .detach(); } } });} #[derive(Clone, Default, Deref, DerefMut)]struct ProjectItemOpeners(Vec<ProjectItemOpener>); type ProjectItemOpener = fn( &Model<Project>, &ProjectPath, &mut WindowContext,) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>; type WorkspaceItemBuilder = Box<dyn FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>>; impl Global for ProjectItemOpeners {} /// Registers a [ProjectItem] for the app. When opening a file, all the registered/// items will get a chance to open the file, starting from the project item that/// was added last.pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) { let builders = cx.default_global::<ProjectItemOpeners>(); builders.push(|project, project_path, cx| { let project_item = <I::Item as project::ProjectItem>::try_open(project, project_path, cx)?; let project = project.clone(); Some(cx.spawn(|cx| async move { let project_item = project_item.await?; let project_entry_id: Option<ProjectEntryId> = project_item.read_with(&cx, project::ProjectItem::entry_id)?; let build_workspace_item = Box::new(|cx: &mut ViewContext<Pane>| { Box::new(cx.new_view(|cx| I::for_project_item(project, project_item, cx))) as Box<dyn ItemHandle> }) as Box<_>; Ok((project_entry_id, build_workspace_item)) })) });} #[derive(Default)]pub struct FollowableViewRegistry(HashMap<TypeId, FollowableViewDescriptor>); struct FollowableViewDescriptor { from_state_proto: fn( View<Workspace>, ViewId, &mut Option<proto::view::Variant>, &mut WindowContext, ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>, to_followable_view: fn(&AnyView) -> Box<dyn FollowableItemHandle>,} impl Global for FollowableViewRegistry {} impl FollowableViewRegistry { pub fn register<I: FollowableItem>(cx: &mut AppContext) { cx.default_global::<Self>().0.insert( TypeId::of::<I>(), FollowableViewDescriptor { from_state_proto: |workspace, id, state, cx| { I::from_state_proto(workspace, id, state, cx).map(|task| { cx.foreground_executor() .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) }) }, to_followable_view: |view| Box::new(view.clone().downcast::<I>().unwrap()), }, ); } pub fn from_state_proto( workspace: View<Workspace>, view_id: ViewId, mut state: Option<proto::view::Variant>, cx: &mut WindowContext, ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>> { cx.update_default_global(|this: &mut Self, cx| { this.0.values().find_map(|descriptor| { (descriptor.from_state_proto)(workspace.clone(), view_id, &mut state, cx) }) }) } pub fn to_followable_view( view: impl Into<AnyView>, cx: &AppContext, ) -> Option<Box<dyn FollowableItemHandle>> { let this = cx.try_global::<Self>()?; let view = view.into(); let descriptor = this.0.get(&view.entity_type())?; Some((descriptor.to_followable_view)(&view)) }} #[derive(Copy, Clone)]struct SerializableItemDescriptor { deserialize: fn( Model<Project>, WeakView<Workspace>, WorkspaceId, ItemId, &mut ViewContext<Pane>, ) -> Task<Result<Box<dyn ItemHandle>>>, cleanup: fn(WorkspaceId, Vec<ItemId>, &mut WindowContext) -> Task<Result<()>>, view_to_serializable_item: fn(AnyView) -> Box<dyn SerializableItemHandle>,} #[derive(Default)]struct SerializableItemRegistry { descriptors_by_kind: HashMap<Arc<str>, SerializableItemDescriptor>, descriptors_by_type: HashMap<TypeId, SerializableItemDescriptor>,} impl Global for SerializableItemRegistry {} impl SerializableItemRegistry { fn deserialize( item_kind: &str, project: Model<Project>, workspace: WeakView<Workspace>, workspace_id: WorkspaceId, item_item: ItemId, cx: &mut ViewContext<Pane>, ) -> Task<Result<Box<dyn ItemHandle>>> { let Some(descriptor) = Self::descriptor(item_kind, cx) else { return Task::ready(Err(anyhow!( "cannot deserialize {}, descriptor not found", item_kind ))); }; (descriptor.deserialize)(project, workspace, workspace_id, item_item, cx) } fn cleanup(Lines 1–500 of 8160. Load more
pub mod dock; pub mod item; mod modal_layer; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; pub mod shared_screen; mod status_bar; pub mod tasks; mod theme_preview; mod toolbar; mod workspace_settings; use anyhow::{anyhow, Context as _, Result}; use call::{call_settings::CallSettings, ActiveCall}; use client::{ proto::{self, ErrorCode, PanelId, PeerId}, ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use derive_more::{Deref, DerefMut}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE}; use futures::{ channel::{ mpsc::{self, UnboundedReceiver, UnboundedSender}, oneshot, }, future::try_join_all, Future, FutureExt, StreamExt, }; use gpui::{ action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size, transparent_black, Action, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId, EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle, WindowId, WindowOptions, }; pub use item::{ FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle, }; use itertools::Itertools; use language::{LanguageRegistry, Rope}; pub use modal_layer::*; use node_runtime::NodeRuntime; use notifications::{ simple_message_notification::MessageNotification, DetachAndPromptErr, NotificationHandle, }; pub use pane::*; pub use pane_group::*; pub use persistence::{ model::{ItemId, LocalPaths, SerializedWorkspaceLocation}, WorkspaceDb, DB as WORKSPACE_DB, }; use persistence::{ model::{SerializedSshProject, SerializedWorkspace}, SerializedWindowBounds, DB, }; use postage::stream::Stream; use project::{ DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId, }; use remote::{ssh_session::ConnectionIdentifier, SshClientDelegate, SshConnectionOptions}; use serde::Deserialize; use session::AppSession; use settings::Settings; use shared_screen::SharedScreen; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ any::TypeId, borrow::Cow, cell::RefCell, cmp, collections::hash_map::DefaultHasher, env, hash::{Hash, Hasher}, path::{Path, PathBuf}, rc::Rc, sync::{atomic::AtomicUsize, Arc, LazyLock, Weak}, time::Duration, }; use task::SpawnInTerminal; use theme::{ActiveTheme, SystemAppearance, ThemeSettings}; pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; use ui::prelude::*; use util::{paths::SanitizedPath, ResultExt, TryFutureExt}; use uuid::Uuid; pub use workspace_settings::{ AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings, }; use crate::notifications::NotificationId; use crate::persistence::{ model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup}, SerializedAxis, }; static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| { env::var("ZED_WINDOW_SIZE") .ok() .as_deref() .and_then(parse_pixel_size_env_var) }); static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| { env::var("ZED_WINDOW_POSITION") .ok() .as_deref() .and_then(parse_pixel_position_env_var) }); #[derive(Clone, PartialEq)] pub struct RemoveWorktreeFromProject(pub WorktreeId); actions!(assistant, [ShowConfiguration]); actions!( workspace, [ ActivateNextPane, ActivatePreviousPane, AddFolderToProject, ClearAllNotifications, CloseAllDocks, CloseWindow, CopyPath, CopyRelativePath, Feedback, FollowNextCollaborator, NewCenterTerminal, NewFile, NewFileSplitVertical, NewFileSplitHorizontal, NewSearch, NewTerminal, NewWindow, Open, OpenInTerminal, ReloadActiveItem, SaveAs, SaveWithoutFormat, ToggleBottomDock, ToggleCenteredLayout, ToggleLeftDock, ToggleRightDock, ToggleZoom, Unfollow, Welcome, ] ); #[derive(Clone, PartialEq)] pub struct OpenPaths { pub paths: Vec<PathBuf>, } #[derive(Clone, Deserialize, PartialEq)] pub struct ActivatePane(pub usize); #[derive(Clone, Deserialize, PartialEq)] pub struct ActivatePaneInDirection(pub SplitDirection); #[derive(Clone, Deserialize, PartialEq)] pub struct SwapPaneInDirection(pub SplitDirection); #[derive(Clone, PartialEq, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SaveAll { pub save_intent: Option<SaveIntent>, } #[derive(Clone, PartialEq, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Save { pub save_intent: Option<SaveIntent>, } #[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct CloseAllItemsAndPanes { pub save_intent: Option<SaveIntent>, } #[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct CloseInactiveTabsAndPanes { pub save_intent: Option<SaveIntent>, } #[derive(Clone, Deserialize, PartialEq)] pub struct SendKeystrokes(pub String); #[derive(Clone, Deserialize, PartialEq, Default)] pub struct Reload { pub binary_path: Option<PathBuf>, } action_as!(project_symbols, ToggleProjectSymbols as Toggle); #[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)] pub struct ToggleFileFinder { #[serde(default)] pub separate_history: bool, } impl_action_as!(file_finder, ToggleFileFinder as Toggle); impl_actions!( workspace, [ ActivatePane, ActivatePaneInDirection, CloseAllItemsAndPanes, CloseInactiveTabsAndPanes, OpenTerminal, Reload, Save, SaveAll, SwapPaneInDirection, SendKeystrokes, ] ); #[derive(PartialEq, Eq, Debug)] pub enum CloseIntent { /// Quit the program entirely. Quit, /// Close a window. CloseWindow, /// Replace the workspace in an existing window. ReplaceWindow, } #[derive(Clone)] pub struct Toast { id: NotificationId, msg: Cow<'static, str>, autohide: bool, on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>, } impl Toast { pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self { Toast { id, msg: msg.into(), on_click: None, autohide: false, } } pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self where M: Into<Cow<'static, str>>, F: Fn(&mut WindowContext) + 'static, { self.on_click = Some((message.into(), Arc::new(on_click))); self } pub fn autohide(mut self) -> Self { self.autohide = true; self } } impl PartialEq for Toast { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.msg == other.msg && self.on_click.is_some() == other.on_click.is_some() } } #[derive(Debug, Default, Clone, Deserialize, PartialEq)] pub struct OpenTerminal { pub working_directory: PathBuf, } #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct WorkspaceId(i64); impl StaticColumnCount for WorkspaceId {} impl Bind for WorkspaceId { fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> { self.0.bind(statement, start_index) } } impl Column for WorkspaceId { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { i64::column(statement, start_index) .map(|(i, next_index)| (Self(i), next_index)) .with_context(|| format!("Failed to read WorkspaceId at index {start_index}")) } } impl From<WorkspaceId> for i64 { fn from(val: WorkspaceId) -> Self { val.0 } } pub fn init_settings(cx: &mut AppContext) { WorkspaceSettings::register(cx); ItemSettings::register(cx); PreviewTabsSettings::register(cx); TabBarSettings::register(cx); } pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); theme_preview::init(cx); cx.on_action(Workspace::close_global); cx.on_action(reload); cx.on_action({ let app_state = Arc::downgrade(&app_state); move |_: &Open, cx: &mut AppContext| { let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, }); if let Some(app_state) = app_state.upgrade() { cx.spawn(move |cx| async move { match Flatten::flatten(paths.await.map_err(|e| e.into())) { Ok(Some(paths)) => { cx.update(|cx| { open_paths(&paths, app_state, OpenOptions::default(), cx) .detach_and_log_err(cx) }) .ok(); } Ok(None) => {} Err(err) => { cx.update(|cx| { if let Some(workspace_window) = cx .active_window() .and_then(|window| window.downcast::<Workspace>()) { workspace_window .update(cx, |workspace, cx| { workspace.show_portal_error(err.to_string(), cx); }) .ok(); } }) .ok(); } }; }) .detach(); } } }); } #[derive(Clone, Default, Deref, DerefMut)] struct ProjectItemOpeners(Vec<ProjectItemOpener>); type ProjectItemOpener = fn( &Model<Project>, &ProjectPath, &mut WindowContext, ) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>; type WorkspaceItemBuilder = Box<dyn FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>>; impl Global for ProjectItemOpeners {} /// Registers a [ProjectItem] for the app. When opening a file, all the registered /// items will get a chance to open the file, starting from the project item that /// was added last. pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) { let builders = cx.default_global::<ProjectItemOpeners>(); builders.push(|project, project_path, cx| { let project_item = <I::Item as project::ProjectItem>::try_open(project, project_path, cx)?; let project = project.clone(); Some(cx.spawn(|cx| async move { let project_item = project_item.await?; let project_entry_id: Option<ProjectEntryId> = project_item.read_with(&cx, project::ProjectItem::entry_id)?; let build_workspace_item = Box::new(|cx: &mut ViewContext<Pane>| { Box::new(cx.new_view(|cx| I::for_project_item(project, project_item, cx))) as Box<dyn ItemHandle> }) as Box<_>; Ok((project_entry_id, build_workspace_item)) })) }); } #[derive(Default)] pub struct FollowableViewRegistry(HashMap<TypeId, FollowableViewDescriptor>); struct FollowableViewDescriptor { from_state_proto: fn( View<Workspace>, ViewId, &mut Option<proto::view::Variant>, &mut WindowContext, ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>, to_followable_view: fn(&AnyView) -> Box<dyn FollowableItemHandle>, } impl Global for FollowableViewRegistry {} impl FollowableViewRegistry { pub fn register<I: FollowableItem>(cx: &mut AppContext) { cx.default_global::<Self>().0.insert( TypeId::of::<I>(), FollowableViewDescriptor { from_state_proto: |workspace, id, state, cx| { I::from_state_proto(workspace, id, state, cx).map(|task| { cx.foreground_executor() .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) }) }, to_followable_view: |view| Box::new(view.clone().downcast::<I>().unwrap()), }, ); } pub fn from_state_proto( workspace: View<Workspace>, view_id: ViewId, mut state: Option<proto::view::Variant>, cx: &mut WindowContext, ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>> { cx.update_default_global(|this: &mut Self, cx| { this.0.values().find_map(|descriptor| { (descriptor.from_state_proto)(workspace.clone(), view_id, &mut state, cx) }) }) } pub fn to_followable_view( view: impl Into<AnyView>, cx: &AppContext, ) -> Option<Box<dyn FollowableItemHandle>> { let this = cx.try_global::<Self>()?; let view = view.into(); let descriptor = this.0.get(&view.entity_type())?; Some((descriptor.to_followable_view)(&view)) } } #[derive(Copy, Clone)] struct SerializableItemDescriptor { deserialize: fn( Model<Project>, WeakView<Workspace>, WorkspaceId, ItemId, &mut ViewContext<Pane>, ) -> Task<Result<Box<dyn ItemHandle>>>, cleanup: fn(WorkspaceId, Vec<ItemId>, &mut WindowContext) -> Task<Result<()>>, view_to_serializable_item: fn(AnyView) -> Box<dyn SerializableItemHandle>, } #[derive(Default)] struct SerializableItemRegistry { descriptors_by_kind: HashMap<Arc<str>, SerializableItemDescriptor>, descriptors_by_type: HashMap<TypeId, SerializableItemDescriptor>, } impl Global for SerializableItemRegistry {} impl SerializableItemRegistry { fn deserialize( item_kind: &str, project: Model<Project>, workspace: WeakView<Workspace>, workspace_id: WorkspaceId, item_item: ItemId, cx: &mut ViewContext<Pane>, ) -> Task<Result<Box<dyn ItemHandle>>> { let Some(descriptor) = Self::descriptor(item_kind, cx) else { return Task::ready(Err(anyhow!( "cannot deserialize {}, descriptor not found", item_kind ))); }; (descriptor.deserialize)(project, workspace, workspace_id, item_item, cx) } fn cleanup(