pub mod dock; pub mod item; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; // pub mod shared_screen; mod status_bar; mod toolbar; mod workspace_settings; use crate::persistence::model::{ DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }; use anyhow::{anyhow, Result}; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, }; use collections::{HashMap, HashSet}; use dock::{Dock, DockPosition, PanelButtons}; use futures::{ channel::{mpsc, oneshot}, FutureExt, Stream, StreamExt, }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; use lazy_static::lazy_static; use node_runtime::NodeRuntime; use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; pub use pane::*; pub use pane_group::*; use persistence::{ model::{ItemId, WorkspaceLocation}, DB, }; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use serde::Deserialize; use status_bar::StatusBar; use std::{ any::TypeId, borrow::Cow, env, path::{Path, PathBuf}, sync::{atomic::AtomicUsize, Arc}, time::Duration, }; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use uuid::Uuid; lazy_static! { static ref ZED_WINDOW_SIZE: Option> = env::var("ZED_WINDOW_SIZE") .ok() .as_deref() .and_then(parse_pixel_size_env_var); static ref ZED_WINDOW_POSITION: Option> = env::var("ZED_WINDOW_POSITION") .ok() .as_deref() .and_then(parse_pixel_position_env_var); } // pub trait Modal: View { // fn has_focus(&self) -> bool; // fn dismiss_on_event(event: &Self::Event) -> bool; // } // trait ModalHandle { // fn as_any(&self) -> &AnyViewHandle; // fn has_focus(&self, cx: &WindowContext) -> bool; // } // impl ModalHandle for View { // fn as_any(&self) -> &AnyViewHandle { // self // } // fn has_focus(&self, cx: &WindowContext) -> bool { // self.read(cx).has_focus() // } // } // #[derive(Clone, PartialEq)] // pub struct RemoveWorktreeFromProject(pub WorktreeId); // actions!( // workspace, // [ // Open, // NewFile, // NewWindow, // CloseWindow, // CloseInactiveTabsAndPanes, // AddFolderToProject, // Unfollow, // SaveAs, // ReloadActiveItem, // ActivatePreviousPane, // ActivateNextPane, // FollowNextCollaborator, // NewTerminal, // NewCenterTerminal, // ToggleTerminalFocus, // NewSearch, // Feedback, // Restart, // Welcome, // ToggleZoom, // ToggleLeftDock, // ToggleRightDock, // ToggleBottomDock, // CloseAllDocks, // ] // ); // #[derive(Clone, PartialEq)] // pub struct OpenPaths { // pub paths: Vec, // } // #[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, Deserialize, PartialEq)] // pub struct NewFileInDirection(pub SplitDirection); // #[derive(Clone, PartialEq, Debug, Deserialize)] // #[serde(rename_all = "camelCase")] // pub struct SaveAll { // pub save_intent: Option, // } // #[derive(Clone, PartialEq, Debug, Deserialize)] // #[serde(rename_all = "camelCase")] // pub struct Save { // pub save_intent: Option, // } // #[derive(Clone, PartialEq, Debug, Deserialize, Default)] // #[serde(rename_all = "camelCase")] // pub struct CloseAllItemsAndPanes { // pub save_intent: Option, // } #[derive(Deserialize)] pub struct Toast { id: usize, msg: Cow<'static, str>, #[serde(skip)] on_click: Option<(Cow<'static, str>, Arc)>, } // impl Toast { // pub fn new>>(id: usize, msg: I) -> Self { // Toast { // id, // msg: msg.into(), // on_click: None, // } // } // pub fn on_click(mut self, message: M, on_click: F) -> Self // where // M: Into>, // F: Fn(&mut WindowContext) + 'static, // { // self.on_click = Some((message.into(), Arc::new(on_click))); // 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() // } // } // impl Clone for Toast { // fn clone(&self) -> Self { // Toast { // id: self.id, // msg: self.msg.to_owned(), // on_click: self.on_click.clone(), // } // } // } // #[derive(Clone, Deserialize, PartialEq)] // pub struct OpenTerminal { // pub working_directory: PathBuf, // } // impl_actions!( // workspace, // [ // ActivatePane, // ActivatePaneInDirection, // SwapPaneInDirection, // NewFileInDirection, // Toast, // OpenTerminal, // SaveAll, // Save, // CloseAllItemsAndPanes, // ] // ); pub type WorkspaceId = i64; // pub fn init_settings(cx: &mut AppContext) { // settings::register::(cx); // settings::register::(cx); // } // pub fn init(app_state: Arc, cx: &mut AppContext) { // init_settings(cx); // pane::init(cx); // notifications::init(cx); // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { // let mut paths = cx.prompt_for_paths(PathPromptOptions { // files: true, // directories: true, // multiple: true, // }); // if let Some(app_state) = app_state.upgrade() { // cx.spawn(move |mut cx| async move { // if let Some(paths) = paths.recv().await.flatten() { // cx.update(|cx| { // open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) // }); // } // }) // .detach(); // } // } // }); // cx.add_async_action(Workspace::open); // cx.add_async_action(Workspace::follow_next_collaborator); // cx.add_async_action(Workspace::close); // cx.add_async_action(Workspace::close_inactive_items_and_panes); // cx.add_async_action(Workspace::close_all_items_and_panes); // cx.add_global_action(Workspace::close_global); // cx.add_global_action(restart); // cx.add_async_action(Workspace::save_all); // cx.add_action(Workspace::add_folder_to_project); // cx.add_action( // |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext| { // let pane = workspace.active_pane().clone(); // workspace.unfollow(&pane, cx); // }, // ); // cx.add_action( // |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext| { // workspace // .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) // .detach_and_log_err(cx); // }, // ); // cx.add_action( // |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext| { // workspace // .save_active_item(SaveIntent::SaveAs, cx) // .detach_and_log_err(cx); // }, // ); // cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { // workspace.activate_previous_pane(cx) // }); // cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { // workspace.activate_next_pane(cx) // }); // cx.add_action( // |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| { // workspace.activate_pane_in_direction(action.0, cx) // }, // ); // cx.add_action( // |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| { // workspace.swap_pane_in_direction(action.0, cx) // }, // ); // cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { // workspace.toggle_dock(DockPosition::Left, cx); // }); // cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { // workspace.toggle_dock(DockPosition::Right, cx); // }); // cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { // workspace.toggle_dock(DockPosition::Bottom, cx); // }); // cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| { // workspace.close_all_docks(cx); // }); // cx.add_action(Workspace::activate_pane_at_index); // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { // workspace.reopen_closed_item(cx).detach(); // }); // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { // workspace // .go_back(workspace.active_pane().downgrade(), cx) // .detach(); // }); // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { // workspace // .go_forward(workspace.active_pane().downgrade(), cx) // .detach(); // }); // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { // cx.spawn(|workspace, mut cx| async move { // let err = install_cli::install_cli(&cx) // .await // .context("Failed to create CLI symlink"); // workspace.update(&mut cx, |workspace, cx| { // if matches!(err, Err(_)) { // err.notify_err(workspace, cx); // } else { // workspace.show_notification(1, cx, |cx| { // cx.build_view(|_| { // MessageNotification::new("Successfully installed the `zed` binary") // }) // }); // } // }) // }) // .detach(); // }); // } type ProjectItemBuilders = HashMap, AnyModel, &mut ViewContext) -> Box>; pub fn register_project_item(cx: &mut AppContext) { let builders = cx.default_global::(); builders.insert(TypeId::of::(), |project, model, cx| { let item = model.downcast::().unwrap(); Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx))) }); } type FollowableItemBuilder = fn( View, View, ViewId, &mut Option, &mut AppContext, ) -> Option>>>; type FollowableItemBuilders = HashMap< TypeId, ( FollowableItemBuilder, fn(&AnyView) -> Box, ), >; pub fn register_followable_item(cx: &mut AppContext) { let builders = cx.default_global::(); builders.insert( TypeId::of::(), ( |pane, workspace, id, state, cx| { I::from_state_proto(pane, workspace, id, state, cx).map(|task| { cx.executor() .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) }) }, |this| Box::new(this.clone().downcast::().unwrap()), ), ); } type ItemDeserializers = HashMap< Arc, fn( Model, WeakView, WorkspaceId, ItemId, &mut ViewContext, ) -> Task>>, >; pub fn register_deserializable_item(cx: &mut AppContext) { cx.update_global(|deserializers: &mut ItemDeserializers, _cx| { if let Some(serialized_item_kind) = I::serialized_item_kind() { deserializers.insert( Arc::from(serialized_item_kind), |project, workspace, workspace_id, item_id, cx| { let task = I::deserialize(project, workspace, workspace_id, item_id, cx); cx.spawn_on_main(|cx| async { Ok(Box::new(task.await?) as Box<_>) }) }, ); } }); } pub struct AppState { pub languages: Arc, pub client: Arc, pub user_store: Model, pub workspace_store: Model, pub fs: Arc, pub build_window_options: fn(Option, Option, &mut MainThread) -> WindowOptions, pub initialize_workspace: fn( WeakView, bool, Arc, AsyncWindowContext, ) -> Task>, pub node_runtime: Arc, } pub struct WorkspaceStore { workspaces: HashSet>, followers: Vec, client: Arc, _subscriptions: Vec, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] struct Follower { project_id: Option, peer_id: PeerId, } // todo!() // impl AppState { // #[cfg(any(test, feature = "test-support"))] // pub fn test(cx: &mut AppContext) -> Arc { // use node_runtime::FakeNodeRuntime; // use settings::SettingsStore; // if !cx.has_global::() { // cx.set_global(SettingsStore::test(cx)); // } // let fs = fs::FakeFs::new(cx.background().clone()); // let languages = Arc::new(LanguageRegistry::test()); // let http_client = util::http::FakeHttpClient::with_404_response(); // let client = Client::new(http_client.clone(), cx); // let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); // theme::init((), cx); // client::init(&client, cx); // crate::init_settings(cx); // Arc::new(Self { // client, // fs, // languages, // user_store, // // channel_store, // workspace_store, // node_runtime: FakeNodeRuntime::new(), // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), // build_window_options: |_, _, _| Default::default(), // }) // } // } struct DelayedDebouncedEditAction { task: Option>, cancel_channel: Option>, } impl DelayedDebouncedEditAction { fn new() -> DelayedDebouncedEditAction { DelayedDebouncedEditAction { task: None, cancel_channel: None, } } fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) where F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, { if let Some(channel) = self.cancel_channel.take() { _ = channel.send(()); } let (sender, mut receiver) = oneshot::channel::<()>(); self.cancel_channel = Some(sender); let previous_task = self.task.take(); self.task = Some(cx.spawn(move |workspace, mut cx| async move { let mut timer = cx.executor().timer(delay).fuse(); if let Some(previous_task) = previous_task { previous_task.await; } futures::select_biased! { _ = receiver => return, _ = timer => {} } if let Some(result) = workspace .update(&mut cx, |workspace, cx| (func)(workspace, cx)) .log_err() { result.await.log_err(); } })); } } pub enum Event { PaneAdded(View), ContactRequestedJoin(u64), } pub struct Workspace { weak_self: WeakView, // modal: Option, // zoomed: Option, // zoomed_position: Option, center: PaneGroup, left_dock: View, bottom_dock: View, right_dock: View, panes: Vec>, panes_by_item: HashMap>, active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, // status_bar: View, // titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, active_call: Option<(Model, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, } // struct ActiveModal { // view: Box, // previously_focused_view_id: Option, // } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct ViewId { pub creator: PeerId, pub id: u64, } #[derive(Default)] struct FollowerState { leader_id: PeerId, active_view_id: Option, items_by_leader_view_id: HashMap>, } enum WorkspaceBounds {} impl Workspace { pub fn new( workspace_id: WorkspaceId, project: Model, app_state: Arc, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); cx.subscribe(&project, move |this, _, event, cx| { match event { project2::Event::RemoteIdChanged(_) => { this.update_window_title(cx); } project2::Event::CollaboratorLeft(peer_id) => { this.collaborator_left(*peer_id, cx); } project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => { this.update_window_title(cx); this.serialize_workspace(cx); } project2::Event::DisconnectedFromHost => { this.update_window_edited(cx); cx.blur(); } project2::Event::Closed => { // todo!() // cx.remove_window(); } project2::Event::DeletedEntry(entry_id) => { for pane in this.panes.iter() { pane.update(cx, |pane, cx| { pane.handle_deleted_project_item(*entry_id, cx) }); } } project2::Event::Notification(message) => this.show_notification(0, cx, |cx| { cx.build_view(|_| MessageNotification::new(message.clone())) }), _ => {} } cx.notify() }) .detach(); let weak_handle = cx.view().downgrade(); let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); let center_pane = cx.build_view(|cx| { Pane::new( weak_handle.clone(), project.clone(), pane_history_timestamp.clone(), cx, ) }); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); // todo!() // cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); let window_handle = cx.window_handle().downcast::().unwrap(); app_state.workspace_store.update(cx, |store, _| { store.workspaces.insert(window_handle); }); let mut current_user = app_state.user_store.read(cx).watch_current_user(); let mut connection_status = app_state.client.status(); let _observe_current_user = cx.spawn(|this, mut cx| async move { current_user.next().await; connection_status.next().await; let mut stream = Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); while stream.recv().await.is_some() { this.update(&mut cx, |_, cx| cx.notify())?; } anyhow::Ok(()) }); // All leader updates are enqueued and then processed in a single task, so // that each asynchronous operation can be run in order. let (leader_updates_tx, mut leader_updates_rx) = mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); let _apply_leader_updates = cx.spawn(|this, mut cx| async move { while let Some((leader_id, update)) = leader_updates_rx.next().await { Self::process_leader_update(&this, leader_id, update, &mut cx) .await .log_err(); } Ok(()) }); // todo!("replace with a different mechanism") // cx.emit_global(WorkspaceCreated(weak_handle.clone())); let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left)); let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom)); let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right)); let left_dock_buttons = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); let bottom_dock_buttons = cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); let status_bar = cx.build_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); status_bar.add_right_item(right_dock_buttons, cx); status_bar.add_right_item(bottom_dock_buttons, cx); status_bar }); // todo!() // cx.update_default_global::, _, _>(|drag_and_drop, _| { // drag_and_drop.register_container(weak_handle.clone()); // }); let mut active_call = None; if cx.has_global::>() { let call = cx.global::>().clone(); let mut subscriptions = Vec::new(); subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); active_call = Some((call, subscriptions)); } let subscriptions = vec![ cx.observe_fullscreen(|_, _, cx| cx.notify()), cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, mut bounds, display, cx| { // Transform fixed bounds to be stored in terms of the containing display if let WindowBounds::Fixed(mut window_bounds) = bounds { if let Some(screen) = cx.platform().screen_by_id(display) { let screen_bounds = screen.bounds(); window_bounds .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); window_bounds .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); bounds = WindowBounds::Fixed(window_bounds); } } cx.background() .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }), cx.observe(&left_dock, |this, _, cx| { this.serialize_workspace(cx); cx.notify(); }), cx.observe(&bottom_dock, |this, _, cx| { this.serialize_workspace(cx); cx.notify(); }), cx.observe(&right_dock, |this, _, cx| { this.serialize_workspace(cx); cx.notify(); }), ]; cx.defer(|this, cx| this.update_window_title(cx)); Workspace { weak_self: weak_handle.clone(), // modal: None, // zoomed: None, // zoomed_position: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), // last_active_view_id: None, // status_bar, // titlebar_item: None, notifications: Default::default(), left_dock, bottom_dock, right_dock, project: project.clone(), follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, active_call, database_id: workspace_id, app_state, _observe_current_user, _apply_leader_updates, _schedule_serialize: None, leader_updates_tx, subscriptions, pane_history_timestamp, } } fn new_local( abs_paths: Vec, app_state: Arc, requesting_window: Option>, cx: &mut MainThread, ) -> Task<( WeakView, Vec, anyhow::Error>>>, )> { let project_handle = Project::local( app_state.client.clone(), app_state.node_runtime.clone(), app_state.user_store.clone(), app_state.languages.clone(), app_state.fs.clone(), cx, ); cx.spawn_on_main(|mut cx| async move { let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); let paths_to_open = Arc::new(abs_paths); // Get project paths for all of the abs_paths let mut worktree_roots: HashSet> = Default::default(); let mut project_paths: Vec<(PathBuf, Option)> = Vec::with_capacity(paths_to_open.len()); for path in paths_to_open.iter().cloned() { if let Some((worktree, project_entry)) = cx .update(|cx| { Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) })? .await .log_err() { worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok()); project_paths.push((path, Some(project_entry))); } else { project_paths.push((path, None)); } } let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { serialized_workspace.id } else { DB.next_id().await.unwrap_or(0) }; let window = if let Some(window) = requesting_window { cx.update_window(window.into(), |cx| { cx.replace_root_view(|cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); }); window } else { let window_bounds_override = window_bounds_env_override(&cx); let (bounds, display) = if let Some(bounds) = window_bounds_override { (Some(bounds), None) } else { serialized_workspace .as_ref() .and_then(|serialized_workspace| { let display = serialized_workspace.display?; let mut bounds = serialized_workspace.bounds?; // Stored bounds are relative to the containing display. // So convert back to global coordinates if that screen still exists if let WindowBounds::Fixed(mut window_bounds) = bounds { let screen = cx.update(|cx| cx.display_for_uuid(display)).ok()??; let screen_bounds = screen.bounds(); window_bounds.origin.x += screen_bounds.origin.x; window_bounds.origin.y += screen_bounds.origin.y; bounds = WindowBounds::Fixed(window_bounds); } Some((bounds, display)) }) .unzip() }; // Use the serialized workspace to construct the new window let options = cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?; cx.open_window(options, |cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) })? }; (app_state.initialize_workspace)( workspace.downgrade(), serialized_workspace.is_some(), app_state.clone(), cx.clone(), ) .await .log_err(); window.update_root(&mut cx, |cx| cx.activate_window()); let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); let opened_items = open_items( serialized_workspace, &workspace, project_paths, app_state, cx, ) .await .unwrap_or_default(); (workspace, opened_items) }) } pub fn weak_handle(&self) -> WeakView { self.weak_self.clone() } // pub fn left_dock(&self) -> &View { // &self.left_dock // } // pub fn bottom_dock(&self) -> &View { // &self.bottom_dock // } // pub fn right_dock(&self) -> &View { // &self.right_dock // } // pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) // where // T::Event: std::fmt::Debug, // { // self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) // } // pub fn add_panel_with_extra_event_handler( // &mut self, // panel: View, // cx: &mut ViewContext, // handler: F, // ) where // T::Event: std::fmt::Debug, // F: Fn(&mut Self, &View, &T::Event, &mut ViewContext) + 'static, // { // let dock = match panel.position(cx) { // DockPosition::Left => &self.left_dock, // DockPosition::Bottom => &self.bottom_dock, // DockPosition::Right => &self.right_dock, // }; // self.subscriptions.push(cx.subscribe(&panel, { // let mut dock = dock.clone(); // let mut prev_position = panel.position(cx); // move |this, panel, event, cx| { // if T::should_change_position_on_event(event) { // let new_position = panel.read(cx).position(cx); // let mut was_visible = false; // dock.update(cx, |dock, cx| { // prev_position = new_position; // was_visible = dock.is_open() // && dock // .visible_panel() // .map_or(false, |active_panel| active_panel.id() == panel.id()); // dock.remove_panel(&panel, cx); // }); // if panel.is_zoomed(cx) { // this.zoomed_position = Some(new_position); // } // dock = match panel.read(cx).position(cx) { // DockPosition::Left => &this.left_dock, // DockPosition::Bottom => &this.bottom_dock, // DockPosition::Right => &this.right_dock, // } // .clone(); // dock.update(cx, |dock, cx| { // dock.add_panel(panel.clone(), cx); // if was_visible { // dock.set_open(true, cx); // dock.activate_panel(dock.panels_len() - 1, cx); // } // }); // } else if T::should_zoom_in_on_event(event) { // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); // if !panel.has_focus(cx) { // cx.focus(&panel); // } // this.zoomed = Some(panel.downgrade().into_any()); // this.zoomed_position = Some(panel.read(cx).position(cx)); // } else if T::should_zoom_out_on_event(event) { // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); // if this.zoomed_position == Some(prev_position) { // this.zoomed = None; // this.zoomed_position = None; // } // cx.notify(); // } else if T::is_focus_event(event) { // let position = panel.read(cx).position(cx); // this.dismiss_zoomed_items_to_reveal(Some(position), cx); // if panel.is_zoomed(cx) { // this.zoomed = Some(panel.downgrade().into_any()); // this.zoomed_position = Some(position); // } else { // this.zoomed = None; // this.zoomed_position = None; // } // this.update_active_view_for_followers(cx); // cx.notify(); // } else { // handler(this, &panel, event, cx) // } // } // })); // dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); // } // pub fn status_bar(&self) -> &View { // &self.status_bar // } pub fn app_state(&self) -> &Arc { &self.app_state } pub fn user_store(&self) -> &Model { &self.app_state.user_store } pub fn project(&self) -> &Model { &self.project } // pub fn recent_navigation_history( // &self, // limit: Option, // cx: &AppContext, // ) -> Vec<(ProjectPath, Option)> { // let mut abs_paths_opened: HashMap> = HashMap::default(); // let mut history: HashMap, usize)> = HashMap::default(); // for pane in &self.panes { // let pane = pane.read(cx); // pane.nav_history() // .for_each_entry(cx, |entry, (project_path, fs_path)| { // if let Some(fs_path) = &fs_path { // abs_paths_opened // .entry(fs_path.clone()) // .or_default() // .insert(project_path.clone()); // } // let timestamp = entry.timestamp; // match history.entry(project_path) { // hash_map::Entry::Occupied(mut entry) => { // let (_, old_timestamp) = entry.get(); // if ×tamp > old_timestamp { // entry.insert((fs_path, timestamp)); // } // } // hash_map::Entry::Vacant(entry) => { // entry.insert((fs_path, timestamp)); // } // } // }); // } // history // .into_iter() // .sorted_by_key(|(_, (_, timestamp))| *timestamp) // .map(|(project_path, (fs_path, _))| (project_path, fs_path)) // .rev() // .filter(|(history_path, abs_path)| { // let latest_project_path_opened = abs_path // .as_ref() // .and_then(|abs_path| abs_paths_opened.get(abs_path)) // .and_then(|project_paths| { // project_paths // .iter() // .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) // }); // match latest_project_path_opened { // Some(latest_project_path_opened) => latest_project_path_opened == history_path, // None => true, // } // }) // .take(limit.unwrap_or(usize::MAX)) // .collect() // } // fn navigate_history( // &mut self, // pane: WeakView, // mode: NavigationMode, // cx: &mut ViewContext, // ) -> Task> { // let to_load = if let Some(pane) = pane.upgrade(cx) { // cx.focus(&pane); // pane.update(cx, |pane, cx| { // loop { // // Retrieve the weak item handle from the history. // let entry = pane.nav_history_mut().pop(mode, cx)?; // // If the item is still present in this pane, then activate it. // if let Some(index) = entry // .item // .upgrade(cx) // .and_then(|v| pane.index_for_item(v.as_ref())) // { // let prev_active_item_index = pane.active_item_index(); // pane.nav_history_mut().set_mode(mode); // pane.activate_item(index, true, true, cx); // pane.nav_history_mut().set_mode(NavigationMode::Normal); // let mut navigated = prev_active_item_index != pane.active_item_index(); // if let Some(data) = entry.data { // navigated |= pane.active_item()?.navigate(data, cx); // } // if navigated { // break None; // } // } // // If the item is no longer present in this pane, then retrieve its // // project path in order to reopen it. // else { // break pane // .nav_history() // .path_for_item(entry.item.id()) // .map(|(project_path, _)| (project_path, entry)); // } // } // }) // } else { // None // }; // if let Some((project_path, entry)) = to_load { // // If the item was no longer present, then load it again from its previous path. // let task = self.load_path(project_path, cx); // cx.spawn(|workspace, mut cx| async move { // let task = task.await; // let mut navigated = false; // if let Some((project_entry_id, build_item)) = task.log_err() { // let prev_active_item_id = pane.update(&mut cx, |pane, _| { // pane.nav_history_mut().set_mode(mode); // pane.active_item().map(|p| p.id()) // })?; // pane.update(&mut cx, |pane, cx| { // let item = pane.open_item(project_entry_id, true, cx, build_item); // navigated |= Some(item.id()) != prev_active_item_id; // pane.nav_history_mut().set_mode(NavigationMode::Normal); // if let Some(data) = entry.data { // navigated |= item.navigate(data, cx); // } // })?; // } // if !navigated { // workspace // .update(&mut cx, |workspace, cx| { // Self::navigate_history(workspace, pane, mode, cx) // })? // .await?; // } // Ok(()) // }) // } else { // Task::ready(Ok(())) // } // } // pub fn go_back( // &mut self, // pane: WeakView, // cx: &mut ViewContext, // ) -> Task> { // self.navigate_history(pane, NavigationMode::GoingBack, cx) // } // pub fn go_forward( // &mut self, // pane: WeakView, // cx: &mut ViewContext, // ) -> Task> { // self.navigate_history(pane, NavigationMode::GoingForward, cx) // } // pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { // self.navigate_history( // self.active_pane().downgrade(), // NavigationMode::ReopeningClosedItem, // cx, // ) // } // pub fn client(&self) -> &Client { // &self.app_state.client // } // pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { // self.titlebar_item = Some(item); // cx.notify(); // } // pub fn titlebar_item(&self) -> Option { // self.titlebar_item.clone() // } // /// Call the given callback with a workspace whose project is local. // /// // /// If the given workspace has a local project, then it will be passed // /// to the callback. Otherwise, a new empty window will be created. // pub fn with_local_workspace( // &mut self, // cx: &mut ViewContext, // callback: F, // ) -> Task> // where // T: 'static, // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, // { // if self.project.read(cx).is_local() { // Task::Ready(Some(Ok(callback(self, cx)))) // } else { // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); // cx.spawn(|_vh, mut cx| async move { // let (workspace, _) = task.await; // workspace.update(&mut cx, callback) // }) // } // } // pub fn worktrees<'a>( // &self, // cx: &'a AppContext, // ) -> impl 'a + Iterator> { // self.project.read(cx).worktrees(cx) // } // pub fn visible_worktrees<'a>( // &self, // cx: &'a AppContext, // ) -> impl 'a + Iterator> { // self.project.read(cx).visible_worktrees(cx) // } // pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { // let futures = self // .worktrees(cx) // .filter_map(|worktree| worktree.read(cx).as_local()) // .map(|worktree| worktree.scan_complete()) // .collect::>(); // async move { // for future in futures { // future.await; // } // } // } // pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { // cx.spawn(|mut cx| async move { // let window = cx // .windows() // .into_iter() // .find(|window| window.is_active(&cx).unwrap_or(false)); // if let Some(window) = window { // //This can only get called when the window's project connection has been lost // //so we don't need to prompt the user for anything and instead just close the window // window.remove(&mut cx); // } // }) // .detach(); // } // pub fn close( // &mut self, // _: &CloseWindow, // cx: &mut ViewContext, // ) -> Option>> { // let window = cx.window(); // let prepare = self.prepare_to_close(false, cx); // Some(cx.spawn(|_, mut cx| async move { // if prepare.await? { // window.remove(&mut cx); // } // Ok(()) // })) // } // pub fn prepare_to_close( // &mut self, // quitting: bool, // cx: &mut ViewContext, // ) -> Task> { // let active_call = self.active_call().cloned(); // let window = cx.window(); // cx.spawn(|this, mut cx| async move { // let workspace_count = cx // .windows() // .into_iter() // .filter(|window| window.root_is::()) // .count(); // if let Some(active_call) = active_call { // if !quitting // && workspace_count == 1 // && active_call.read_with(&cx, |call, _| call.room().is_some()) // { // let answer = window.prompt( // PromptLevel::Warning, // "Do you want to leave the current call?", // &["Close window and hang up", "Cancel"], // &mut cx, // ); // if let Some(mut answer) = answer { // if answer.next().await == Some(1) { // return anyhow::Ok(false); // } else { // active_call // .update(&mut cx, |call, cx| call.hang_up(cx)) // .await // .log_err(); // } // } // } // } // Ok(this // .update(&mut cx, |this, cx| { // this.save_all_internal(SaveIntent::Close, cx) // })? // .await?) // }) // } // fn save_all( // &mut self, // action: &SaveAll, // cx: &mut ViewContext, // ) -> Option>> { // let save_all = // self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); // Some(cx.foreground().spawn(async move { // save_all.await?; // Ok(()) // })) // } // fn save_all_internal( // &mut self, // mut save_intent: SaveIntent, // cx: &mut ViewContext, // ) -> Task> { // if self.project.read(cx).is_read_only() { // return Task::ready(Ok(true)); // } // let dirty_items = self // .panes // .iter() // .flat_map(|pane| { // pane.read(cx).items().filter_map(|item| { // if item.is_dirty(cx) { // Some((pane.downgrade(), item.boxed_clone())) // } else { // None // } // }) // }) // .collect::>(); // let project = self.project.clone(); // cx.spawn(|workspace, mut cx| async move { // // Override save mode and display "Save all files" prompt // if save_intent == SaveIntent::Close && dirty_items.len() > 1 { // let mut answer = workspace.update(&mut cx, |_, cx| { // let prompt = Pane::file_names_for_prompt( // &mut dirty_items.iter().map(|(_, handle)| handle), // dirty_items.len(), // cx, // ); // cx.prompt( // PromptLevel::Warning, // &prompt, // &["Save all", "Discard all", "Cancel"], // ) // })?; // match answer.next().await { // Some(0) => save_intent = SaveIntent::SaveAll, // Some(1) => save_intent = SaveIntent::Skip, // _ => {} // } // } // for (pane, item) in dirty_items { // let (singleton, project_entry_ids) = // cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); // if singleton || !project_entry_ids.is_empty() { // if let Some(ix) = // pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? // { // if !Pane::save_item( // project.clone(), // &pane, // ix, // &*item, // save_intent, // &mut cx, // ) // .await? // { // return Ok(false); // } // } // } // } // Ok(true) // }) // } // pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { // let mut paths = cx.prompt_for_paths(PathPromptOptions { // files: true, // directories: true, // multiple: true, // }); // Some(cx.spawn(|this, mut cx| async move { // if let Some(paths) = paths.recv().await.flatten() { // if let Some(task) = this // .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) // .log_err() // { // task.await? // } // } // Ok(()) // })) // } // pub fn open_workspace_for_paths( // &mut self, // paths: Vec, // cx: &mut ViewContext, // ) -> Task> { // let window = cx.window().downcast::(); // let is_remote = self.project.read(cx).is_remote(); // let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); // let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); // let close_task = if is_remote || has_worktree || has_dirty_items { // None // } else { // Some(self.prepare_to_close(false, cx)) // }; // let app_state = self.app_state.clone(); // cx.spawn(|_, mut cx| async move { // let window_to_replace = if let Some(close_task) = close_task { // if !close_task.await? { // return Ok(()); // } // window // } else { // None // }; // cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) // .await?; // Ok(()) // }) // } #[allow(clippy::type_complexity)] pub fn open_paths( &mut self, mut abs_paths: Vec, visible: bool, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { log::info!("open paths {abs_paths:?}"); let fs = self.app_state.fs.clone(); // Sort the paths to ensure we add worktrees for parents before their children. abs_paths.sort_unstable(); cx.spawn(move |this, mut cx| async move { let mut tasks = Vec::with_capacity(abs_paths.len()); for abs_path in &abs_paths { let project_path = match this .update(&mut cx, |this, cx| { Workspace::project_path_for_path( this.project.clone(), abs_path, visible, cx, ) }) .log_err() { Some(project_path) => project_path.await.log_err(), None => None, }; let this = this.clone(); let abs_path = abs_path.clone(); let fs = fs.clone(); let task = cx.spawn(move |mut cx| async move { let (worktree, project_path) = project_path?; if fs.is_file(&abs_path).await { Some( this.update(&mut cx, |this, cx| { this.open_path(project_path, None, true, cx) }) .log_err()? .await, ) } else { this.update(&mut cx, |workspace, cx| { let worktree = worktree.read(cx); let worktree_abs_path = worktree.abs_path(); let entry_id = if abs_path == worktree_abs_path.as_ref() { worktree.root_entry() } else { abs_path .strip_prefix(worktree_abs_path.as_ref()) .ok() .and_then(|relative_path| { worktree.entry_for_path(relative_path) }) } .map(|entry| entry.id); if let Some(entry_id) = entry_id { workspace.project.update(cx, |_, cx| { cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id))); }) } }) .log_err()?; None } }); tasks.push(task); } futures::future::join_all(tasks).await }) } // fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { // let mut paths = cx.prompt_for_paths(PathPromptOptions { // files: false, // directories: true, // multiple: true, // }); // cx.spawn(|this, mut cx| async move { // if let Some(paths) = paths.recv().await.flatten() { // let results = this // .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? // .await; // for result in results.into_iter().flatten() { // result.log_err(); // } // } // anyhow::Ok(()) // }) // .detach_and_log_err(cx); // } fn project_path_for_path( project: Model, abs_path: &Path, visible: bool, cx: &mut AppContext, ) -> Task, ProjectPath)>> { let entry = project.update(cx, |project, cx| { project.find_or_create_local_worktree(abs_path, visible, cx) }); cx.spawn(|mut cx| async move { let (worktree, path) = entry.await?; let worktree_id = worktree.update(&mut cx, |t, _| t.id())?; Ok(( worktree, ProjectPath { worktree_id, path: path.into(), }, )) }) } // /// Returns the modal that was toggled closed if it was open. // pub fn toggle_modal( // &mut self, // cx: &mut ViewContext, // build_view: F, // ) -> Option> // where // V: 'static + Modal, // F: FnOnce(&mut Self, &mut ViewContext) -> View, // { // cx.notify(); // // Whatever modal was visible is getting clobbered. If its the same type as V, then return // // it. Otherwise, create a new modal and set it as active. // if let Some(already_open_modal) = self // .dismiss_modal(cx) // .and_then(|modal| modal.downcast::()) // { // cx.focus_self(); // Some(already_open_modal) // } else { // let modal = build_view(self, cx); // cx.subscribe(&modal, |this, _, event, cx| { // if V::dismiss_on_event(event) { // this.dismiss_modal(cx); // } // }) // .detach(); // let previously_focused_view_id = cx.focused_view_id(); // cx.focus(&modal); // self.modal = Some(ActiveModal { // view: Box::new(modal), // previously_focused_view_id, // }); // None // } // } // pub fn modal(&self) -> Option> { // self.modal // .as_ref() // .and_then(|modal| modal.view.as_any().clone().downcast::()) // } // pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { // if let Some(modal) = self.modal.take() { // if let Some(previously_focused_view_id) = modal.previously_focused_view_id { // if modal.view.has_focus(cx) { // cx.window_context().focus(Some(previously_focused_view_id)); // } // } // cx.notify(); // Some(modal.view.as_any().clone()) // } else { // None // } // } pub fn items<'a>( &'a self, cx: &'a AppContext, ) -> impl 'a + Iterator> { self.panes.iter().flat_map(|pane| pane.read(cx).items()) } // pub fn item_of_type(&self, cx: &AppContext) -> Option> { // self.items_of_type(cx).max_by_key(|item| item.id()) // } // pub fn items_of_type<'a, T: Item>( // &'a self, // cx: &'a AppContext, // ) -> impl 'a + Iterator> { // self.panes // .iter() // .flat_map(|pane| pane.read(cx).items_of_type()) // } pub fn active_item(&self, cx: &AppContext) -> Option> { self.active_pane().read(cx).active_item() } // fn active_project_path(&self, cx: &ViewContext) -> Option { // self.active_item(cx).and_then(|item| item.project_path(cx)) // } // pub fn save_active_item( // &mut self, // save_intent: SaveIntent, // cx: &mut ViewContext, // ) -> Task> { // let project = self.project.clone(); // let pane = self.active_pane(); // let item_ix = pane.read(cx).active_item_index(); // let item = pane.read(cx).active_item(); // let pane = pane.downgrade(); // cx.spawn(|_, mut cx| async move { // if let Some(item) = item { // Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) // .await // .map(|_| ()) // } else { // Ok(()) // } // }) // } // pub fn close_inactive_items_and_panes( // &mut self, // _: &CloseInactiveTabsAndPanes, // cx: &mut ViewContext, // ) -> Option>> { // self.close_all_internal(true, SaveIntent::Close, cx) // } // pub fn close_all_items_and_panes( // &mut self, // action: &CloseAllItemsAndPanes, // cx: &mut ViewContext, // ) -> Option>> { // self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) // } // fn close_all_internal( // &mut self, // retain_active_pane: bool, // save_intent: SaveIntent, // cx: &mut ViewContext, // ) -> Option>> { // let current_pane = self.active_pane(); // let mut tasks = Vec::new(); // if retain_active_pane { // if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { // pane.close_inactive_items(&CloseInactiveItems, cx) // }) { // tasks.push(current_pane_close); // }; // } // for pane in self.panes() { // if retain_active_pane && pane.id() == current_pane.id() { // continue; // } // if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { // pane.close_all_items( // &CloseAllItems { // save_intent: Some(save_intent), // }, // cx, // ) // }) { // tasks.push(close_pane_items) // } // } // if tasks.is_empty() { // None // } else { // Some(cx.spawn(|_, _| async move { // for task in tasks { // task.await? // } // Ok(()) // })) // } // } // pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { // let dock = match dock_side { // DockPosition::Left => &self.left_dock, // DockPosition::Bottom => &self.bottom_dock, // DockPosition::Right => &self.right_dock, // }; // let mut focus_center = false; // let mut reveal_dock = false; // dock.update(cx, |dock, cx| { // let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); // let was_visible = dock.is_open() && !other_is_zoomed; // dock.set_open(!was_visible, cx); // if let Some(active_panel) = dock.active_panel() { // if was_visible { // if active_panel.has_focus(cx) { // focus_center = true; // } // } else { // cx.focus(active_panel.as_any()); // reveal_dock = true; // } // } // }); // if reveal_dock { // self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); // } // if focus_center { // cx.focus_self(); // } // cx.notify(); // self.serialize_workspace(cx); // } // pub fn close_all_docks(&mut self, cx: &mut ViewContext) { // let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; // for dock in docks { // dock.update(cx, |dock, cx| { // dock.set_open(false, cx); // }); // } // cx.focus_self(); // cx.notify(); // self.serialize_workspace(cx); // } // /// Transfer focus to the panel of the given type. // pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { // self.focus_or_unfocus_panel::(cx, |_, _| true)? // .as_any() // .clone() // .downcast() // } // /// Focus the panel of the given type if it isn't already focused. If it is // /// already focused, then transfer focus back to the workspace center. // pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { // self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); // } // /// Focus or unfocus the given panel type, depending on the given callback. // fn focus_or_unfocus_panel( // &mut self, // cx: &mut ViewContext, // should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, // ) -> Option> { // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { // if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { // let mut focus_center = false; // let mut reveal_dock = false; // let panel = dock.update(cx, |dock, cx| { // dock.activate_panel(panel_index, cx); // let panel = dock.active_panel().cloned(); // if let Some(panel) = panel.as_ref() { // if should_focus(&**panel, cx) { // dock.set_open(true, cx); // cx.focus(panel.as_any()); // reveal_dock = true; // } else { // // if panel.is_zoomed(cx) { // // dock.set_open(false, cx); // // } // focus_center = true; // } // } // panel // }); // if focus_center { // cx.focus_self(); // } // self.serialize_workspace(cx); // cx.notify(); // return panel; // } // } // None // } // pub fn panel(&self, cx: &WindowContext) -> Option> { // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { // let dock = dock.read(cx); // if let Some(panel) = dock.panel::() { // return Some(panel); // } // } // None // } // fn zoom_out(&mut self, cx: &mut ViewContext) { // for pane in &self.panes { // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); // } // self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); // self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); // self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); // self.zoomed = None; // self.zoomed_position = None; // cx.notify(); // } // #[cfg(any(test, feature = "test-support"))] // pub fn zoomed_view(&self, cx: &AppContext) -> Option { // self.zoomed.and_then(|view| view.upgrade(cx)) // } // fn dismiss_zoomed_items_to_reveal( // &mut self, // dock_to_reveal: Option, // cx: &mut ViewContext, // ) { // // If a center pane is zoomed, unzoom it. // for pane in &self.panes { // if pane != &self.active_pane || dock_to_reveal.is_some() { // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); // } // } // // If another dock is zoomed, hide it. // let mut focus_center = false; // for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { // dock.update(cx, |dock, cx| { // if Some(dock.position()) != dock_to_reveal { // if let Some(panel) = dock.active_panel() { // if panel.is_zoomed(cx) { // focus_center |= panel.has_focus(cx); // dock.set_open(false, cx); // } // } // } // }); // } // if focus_center { // cx.focus_self(); // } // if self.zoomed_position != dock_to_reveal { // self.zoomed = None; // self.zoomed_position = None; // } // cx.notify(); // } fn add_pane(&mut self, cx: &mut ViewContext) -> View { todo!() // let pane = cx.build_view(|cx| { // Pane::new( // self.weak_handle(), // self.project.clone(), // self.pane_history_timestamp.clone(), // cx, // ) // }); // cx.subscribe(&pane, Self::handle_pane_event).detach(); // self.panes.push(pane.clone()); // todo!() // cx.focus(&pane); // cx.emit(Event::PaneAdded(pane.clone())); // pane } // pub fn add_item_to_center( // &mut self, // item: Box, // cx: &mut ViewContext, // ) -> bool { // if let Some(center_pane) = self.last_active_center_pane.clone() { // if let Some(center_pane) = center_pane.upgrade(cx) { // center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); // true // } else { // false // } // } else { // false // } // } // pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { // self.active_pane // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); // } // pub fn split_item( // &mut self, // split_direction: SplitDirection, // item: Box, // cx: &mut ViewContext, // ) { // let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); // new_pane.update(cx, move |new_pane, cx| { // new_pane.add_item(item, true, true, None, cx) // }) // } // pub fn open_abs_path( // &mut self, // abs_path: PathBuf, // visible: bool, // cx: &mut ViewContext, // ) -> Task>> { // cx.spawn(|workspace, mut cx| async move { // let open_paths_task_result = workspace // .update(&mut cx, |workspace, cx| { // workspace.open_paths(vec![abs_path.clone()], visible, cx) // }) // .with_context(|| format!("open abs path {abs_path:?} task spawn"))? // .await; // anyhow::ensure!( // open_paths_task_result.len() == 1, // "open abs path {abs_path:?} task returned incorrect number of results" // ); // match open_paths_task_result // .into_iter() // .next() // .expect("ensured single task result") // { // Some(open_result) => { // open_result.with_context(|| format!("open abs path {abs_path:?} task join")) // } // None => anyhow::bail!("open abs path {abs_path:?} task returned None"), // } // }) // } // pub fn split_abs_path( // &mut self, // abs_path: PathBuf, // visible: bool, // cx: &mut ViewContext, // ) -> Task>> { // let project_path_task = // Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); // cx.spawn(|this, mut cx| async move { // let (_, path) = project_path_task.await?; // this.update(&mut cx, |this, cx| this.split_path(path, cx))? // .await // }) // } pub fn open_path( &mut self, path: impl Into, pane: Option>, focus_item: bool, cx: &mut ViewContext, ) -> Task, anyhow::Error>> { let pane = pane.unwrap_or_else(|| { self.last_active_center_pane.clone().unwrap_or_else(|| { self.panes .first() .expect("There must be an active pane") .downgrade() }) }); let task = self.load_path(path.into(), cx); cx.spawn(move |_, mut cx| async move { let (project_entry_id, build_item) = task.await?; pane.update(&mut cx, |pane, cx| { pane.open_item(project_entry_id, focus_item, cx, build_item) }) }) } // pub fn split_path( // &mut self, // path: impl Into, // cx: &mut ViewContext, // ) -> Task, anyhow::Error>> { // let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { // self.panes // .first() // .expect("There must be an active pane") // .downgrade() // }); // if let Member::Pane(center_pane) = &self.center.root { // if center_pane.read(cx).items_len() == 0 { // return self.open_path(path, Some(pane), true, cx); // } // } // let task = self.load_path(path.into(), cx); // cx.spawn(|this, mut cx| async move { // let (project_entry_id, build_item) = task.await?; // this.update(&mut cx, move |this, cx| -> Option<_> { // let pane = pane.upgrade(cx)?; // let new_pane = this.split_pane(pane, SplitDirection::Right, cx); // new_pane.update(cx, |new_pane, cx| { // Some(new_pane.open_item(project_entry_id, true, cx, build_item)) // }) // }) // .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? // }) // } pub(crate) fn load_path( &mut self, path: ProjectPath, cx: &mut ViewContext, ) -> Task< Result<( ProjectEntryId, impl 'static + Send + FnOnce(&mut ViewContext) -> Box, )>, > { let project = self.project().clone(); let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); cx.spawn(|_, cx| async move { let (project_entry_id, project_item) = project_item.await?; let build_item = cx.update(|cx| { cx.default_global::() .get(&project_item.type_id()) .ok_or_else(|| anyhow!("no item builder for project item")) .cloned() })??; let build_item = move |cx: &mut ViewContext| build_item(project, project_item, cx); Ok((project_entry_id, build_item)) }) } // pub fn open_project_item( // &mut self, // project_item: ModelHandle, // cx: &mut ViewContext, // ) -> View // where // T: ProjectItem, // { // use project::Item as _; // let entry_id = project_item.read(cx).entry_id(cx); // if let Some(item) = entry_id // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) // .and_then(|item| item.downcast()) // { // self.activate_item(&item, cx); // return item; // } // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); // self.add_item(Box::new(item.clone()), cx); // item // } // pub fn split_project_item( // &mut self, // project_item: ModelHandle, // cx: &mut ViewContext, // ) -> View // where // T: ProjectItem, // { // use project::Item as _; // let entry_id = project_item.read(cx).entry_id(cx); // if let Some(item) = entry_id // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) // .and_then(|item| item.downcast()) // { // self.activate_item(&item, cx); // return item; // } // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); // item // } // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { // self.active_pane.update(cx, |pane, cx| { // pane.add_item(Box::new(shared_screen), false, true, None, cx) // }); // } // } // pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { // let result = self.panes.iter().find_map(|pane| { // pane.read(cx) // .index_for_item(item) // .map(|ix| (pane.clone(), ix)) // }); // if let Some((pane, ix)) = result { // pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); // true // } else { // false // } // } // fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { // let panes = self.center.panes(); // if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { // cx.focus(&pane); // } else { // self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); // } // } // pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { // let panes = self.center.panes(); // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { // let next_ix = (ix + 1) % panes.len(); // let next_pane = panes[next_ix].clone(); // cx.focus(&next_pane); // } // } // pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { // let panes = self.center.panes(); // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { // let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); // let prev_pane = panes[prev_ix].clone(); // cx.focus(&prev_pane); // } // } // pub fn activate_pane_in_direction( // &mut self, // direction: SplitDirection, // cx: &mut ViewContext, // ) { // if let Some(pane) = self.find_pane_in_direction(direction, cx) { // cx.focus(pane); // } // } // pub fn swap_pane_in_direction( // &mut self, // direction: SplitDirection, // cx: &mut ViewContext, // ) { // if let Some(to) = self // .find_pane_in_direction(direction, cx) // .map(|pane| pane.clone()) // { // self.center.swap(&self.active_pane.clone(), &to); // cx.notify(); // } // } // fn find_pane_in_direction( // &mut self, // direction: SplitDirection, // cx: &mut ViewContext, // ) -> Option<&View> { // let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { // return None; // }; // let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); // let center = match cursor { // Some(cursor) if bounding_box.contains_point(cursor) => cursor, // _ => bounding_box.center(), // }; // let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; // let target = match direction { // SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), // SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), // SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), // SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), // }; // self.center.pane_at_pixel_position(target) // } // fn handle_pane_focused(&mut self, pane: View, cx: &mut ViewContext) { // if self.active_pane != pane { // self.active_pane = pane.clone(); // self.status_bar.update(cx, |status_bar, cx| { // status_bar.set_active_pane(&self.active_pane, cx); // }); // self.active_item_path_changed(cx); // self.last_active_center_pane = Some(pane.downgrade()); // } // self.dismiss_zoomed_items_to_reveal(None, cx); // if pane.read(cx).is_zoomed() { // self.zoomed = Some(pane.downgrade().into_any()); // } else { // self.zoomed = None; // } // self.zoomed_position = None; // self.update_active_view_for_followers(cx); // cx.notify(); // } fn handle_pane_event( &mut self, pane: View, event: &pane::Event, cx: &mut ViewContext, ) { todo!() // match event { // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), // pane::Event::Split(direction) => { // self.split_and_clone(pane, *direction, cx); // } // pane::Event::Remove => self.remove_pane(pane, cx), // pane::Event::ActivateItem { local } => { // if *local { // self.unfollow(&pane, cx); // } // if &pane == self.active_pane() { // self.active_item_path_changed(cx); // } // } // pane::Event::ChangeItemTitle => { // if pane == self.active_pane { // self.active_item_path_changed(cx); // } // self.update_window_edited(cx); // } // pane::Event::RemoveItem { item_id } => { // self.update_window_edited(cx); // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { // if entry.get().id() == pane.id() { // entry.remove(); // } // } // } // pane::Event::Focus => { // self.handle_pane_focused(pane.clone(), cx); // } // pane::Event::ZoomIn => { // if pane == self.active_pane { // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); // if pane.read(cx).has_focus() { // self.zoomed = Some(pane.downgrade().into_any()); // self.zoomed_position = None; // } // cx.notify(); // } // } // pane::Event::ZoomOut => { // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); // if self.zoomed_position.is_none() { // self.zoomed = None; // } // cx.notify(); // } // } // self.serialize_workspace(cx); } // pub fn split_pane( // &mut self, // pane_to_split: View, // split_direction: SplitDirection, // cx: &mut ViewContext, // ) -> View { // let new_pane = self.add_pane(cx); // self.center // .split(&pane_to_split, &new_pane, split_direction) // .unwrap(); // cx.notify(); // new_pane // } // pub fn split_and_clone( // &mut self, // pane: View, // direction: SplitDirection, // cx: &mut ViewContext, // ) -> Option> { // let item = pane.read(cx).active_item()?; // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { // let new_pane = self.add_pane(cx); // new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); // self.center.split(&pane, &new_pane, direction).unwrap(); // Some(new_pane) // } else { // None // }; // cx.notify(); // maybe_pane_handle // } // pub fn split_pane_with_item( // &mut self, // pane_to_split: WeakView, // split_direction: SplitDirection, // from: WeakView, // item_id_to_move: usize, // cx: &mut ViewContext, // ) { // let Some(pane_to_split) = pane_to_split.upgrade(cx) else { // return; // }; // let Some(from) = from.upgrade(cx) else { // return; // }; // let new_pane = self.add_pane(cx); // self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); // self.center // .split(&pane_to_split, &new_pane, split_direction) // .unwrap(); // cx.notify(); // } // pub fn split_pane_with_project_entry( // &mut self, // pane_to_split: WeakView, // split_direction: SplitDirection, // project_entry: ProjectEntryId, // cx: &mut ViewContext, // ) -> Option>> { // let pane_to_split = pane_to_split.upgrade(cx)?; // let new_pane = self.add_pane(cx); // self.center // .split(&pane_to_split, &new_pane, split_direction) // .unwrap(); // let path = self.project.read(cx).path_for_entry(project_entry, cx)?; // let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); // Some(cx.foreground().spawn(async move { // task.await?; // Ok(()) // })) // } // pub fn move_item( // &mut self, // source: View, // destination: View, // item_id_to_move: usize, // destination_index: usize, // cx: &mut ViewContext, // ) { // let item_to_move = source // .read(cx) // .items() // .enumerate() // .find(|(_, item_handle)| item_handle.id() == item_id_to_move); // if item_to_move.is_none() { // log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); // return; // } // let (item_ix, item_handle) = item_to_move.unwrap(); // let item_handle = item_handle.clone(); // if source != destination { // // Close item from previous pane // source.update(cx, |source, cx| { // source.remove_item(item_ix, false, cx); // }); // } // // This automatically removes duplicate items in the pane // destination.update(cx, |destination, cx| { // destination.add_item(item_handle, true, true, Some(destination_index), cx); // cx.focus_self(); // }); // } // fn remove_pane(&mut self, pane: View, cx: &mut ViewContext) { // if self.center.remove(&pane).unwrap() { // self.force_remove_pane(&pane, cx); // self.unfollow(&pane, cx); // self.last_leaders_by_pane.remove(&pane.downgrade()); // for removed_item in pane.read(cx).items() { // self.panes_by_item.remove(&removed_item.id()); // } // cx.notify(); // } else { // self.active_item_path_changed(cx); // } // } pub fn panes(&self) -> &[View] { &self.panes } pub fn active_pane(&self) -> &View { &self.active_pane } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { self.follower_states.retain(|_, state| { if state.leader_id == peer_id { for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); } false } else { true } }); cx.notify(); } // fn start_following( // &mut self, // leader_id: PeerId, // cx: &mut ViewContext, // ) -> Option>> { // let pane = self.active_pane().clone(); // self.last_leaders_by_pane // .insert(pane.downgrade(), leader_id); // self.unfollow(&pane, cx); // self.follower_states.insert( // pane.clone(), // FollowerState { // leader_id, // active_view_id: None, // items_by_leader_view_id: Default::default(), // }, // ); // cx.notify(); // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); // let project_id = self.project.read(cx).remote_id(); // let request = self.app_state.client.request(proto::Follow { // room_id, // project_id, // leader_id: Some(leader_id), // }); // Some(cx.spawn(|this, mut cx| async move { // let response = request.await?; // this.update(&mut cx, |this, _| { // let state = this // .follower_states // .get_mut(&pane) // .ok_or_else(|| anyhow!("following interrupted"))?; // state.active_view_id = if let Some(active_view_id) = response.active_view_id { // Some(ViewId::from_proto(active_view_id)?) // } else { // None // }; // Ok::<_, anyhow::Error>(()) // })??; // Self::add_views_from_leader( // this.clone(), // leader_id, // vec![pane], // response.views, // &mut cx, // ) // .await?; // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; // Ok(()) // })) // } // pub fn follow_next_collaborator( // &mut self, // _: &FollowNextCollaborator, // cx: &mut ViewContext, // ) -> Option>> { // let collaborators = self.project.read(cx).collaborators(); // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { // let mut collaborators = collaborators.keys().copied(); // for peer_id in collaborators.by_ref() { // if peer_id == leader_id { // break; // } // } // collaborators.next() // } else if let Some(last_leader_id) = // self.last_leaders_by_pane.get(&self.active_pane.downgrade()) // { // if collaborators.contains_key(last_leader_id) { // Some(*last_leader_id) // } else { // None // } // } else { // None // }; // let pane = self.active_pane.clone(); // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) // else { // return None; // }; // if Some(leader_id) == self.unfollow(&pane, cx) { // return None; // } // self.follow(leader_id, cx) // } // pub fn follow( // &mut self, // leader_id: PeerId, // cx: &mut ViewContext, // ) -> Option>> { // let room = ActiveCall::global(cx).read(cx).room()?.read(cx); // let project = self.project.read(cx); // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { // return None; // }; // let other_project_id = match remote_participant.location { // call::ParticipantLocation::External => None, // call::ParticipantLocation::UnsharedProject => None, // call::ParticipantLocation::SharedProject { project_id } => { // if Some(project_id) == project.remote_id() { // None // } else { // Some(project_id) // } // } // }; // // if they are active in another project, follow there. // if let Some(project_id) = other_project_id { // let app_state = self.app_state.clone(); // return Some(crate::join_remote_project( // project_id, // remote_participant.user.id, // app_state, // cx, // )); // } // // if you're already following, find the right pane and focus it. // for (pane, state) in &self.follower_states { // if leader_id == state.leader_id { // cx.focus(pane); // return None; // } // } // // Otherwise, follow. // self.start_following(leader_id, cx) // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { let state = self.follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { item.set_leader_peer_id(None, cx); } if self .follower_states .values() .all(|state| state.leader_id != state.leader_id) { let project_id = self.project.read(cx).remote_id(); let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); self.app_state .client .send(proto::Unfollow { room_id, project_id, leader_id: Some(leader_id), }) .log_err(); } cx.notify(); Some(leader_id) } // pub fn is_being_followed(&self, peer_id: PeerId) -> bool { // self.follower_states // .values() // .any(|state| state.leader_id == peer_id) // } // fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { // // TODO: There should be a better system in place for this // // (https://github.com/zed-industries/zed/issues/1290) // let is_fullscreen = cx.window_is_fullscreen(); // let container_theme = if is_fullscreen { // let mut container_theme = theme.titlebar.container; // container_theme.padding.left = container_theme.padding.right; // container_theme // } else { // theme.titlebar.container // }; // enum TitleBar {} // MouseEventHandler::new::(0, cx, |_, cx| { // Stack::new() // .with_children( // self.titlebar_item // .as_ref() // .map(|item| ChildView::new(item, cx)), // ) // .contained() // .with_style(container_theme) // }) // .on_click(MouseButton::Left, |event, _, cx| { // if event.click_count == 2 { // cx.zoom_window(); // } // }) // .constrained() // .with_height(theme.titlebar.height) // .into_any_named("titlebar") // } // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { // let active_entry = self.active_project_path(cx); // self.project // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); // self.update_window_title(cx); // } fn update_window_title(&mut self, cx: &mut ViewContext) { let project = self.project().read(cx); let mut title = String::new(); if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { let filename = path .path .file_name() .map(|s| s.to_string_lossy()) .or_else(|| { Some(Cow::Borrowed( project .worktree_for_id(path.worktree_id, cx)? .read(cx) .root_name(), )) }); if let Some(filename) = filename { title.push_str(filename.as_ref()); title.push_str(" — "); } } for (i, name) in project.worktree_root_names(cx).enumerate() { if i > 0 { title.push_str(", "); } title.push_str(name); } if title.is_empty() { title = "empty project".to_string(); } if project.is_remote() { title.push_str(" ↙"); } else if project.is_shared() { title.push_str(" ↗"); } todo!() // cx.set_window_title(&title); } fn update_window_edited(&mut self, cx: &mut ViewContext) { let is_edited = !self.project.read(cx).is_read_only() && self .items(cx) .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); if is_edited != self.window_edited { self.window_edited = is_edited; todo!() // cx.set_window_edited(self.window_edited) } } // fn render_disconnected_overlay( // &self, // cx: &mut ViewContext, // ) -> Option> { // if self.project.read(cx).is_read_only() { // enum DisconnectedOverlay {} // Some( // MouseEventHandler::new::(0, cx, |_, cx| { // let theme = &theme::current(cx); // Label::new( // "Your connection to the remote project has been lost.", // theme.workspace.disconnected_overlay.text.clone(), // ) // .aligned() // .contained() // .with_style(theme.workspace.disconnected_overlay.container) // }) // .with_cursor_style(CursorStyle::Arrow) // .capture_all() // .into_any_named("disconnected overlay"), // ) // } else { // None // } // } // fn render_notifications( // &self, // theme: &theme::Workspace, // cx: &AppContext, // ) -> Option> { // if self.notifications.is_empty() { // None // } else { // Some( // Flex::column() // .with_children(self.notifications.iter().map(|(_, _, notification)| { // ChildView::new(notification.as_any(), cx) // .contained() // .with_style(theme.notification) // })) // .constrained() // .with_width(theme.notifications.width) // .contained() // .with_style(theme.notifications.container) // .aligned() // .bottom() // .right() // .into_any(), // ) // } // } // // RPC handlers fn handle_follow( &mut self, follower_project_id: Option, cx: &mut ViewContext, ) -> proto::FollowResponse { todo!() // let client = &self.app_state.client; // let project_id = self.project.read(cx).remote_id(); // let active_view_id = self.active_item(cx).and_then(|i| { // Some( // i.to_followable_item_handle(cx)? // .remote_id(client, cx)? // .to_proto(), // ) // }); // cx.notify(); // self.last_active_view_id = active_view_id.clone(); // proto::FollowResponse { // active_view_id, // views: self // .panes() // .iter() // .flat_map(|pane| { // let leader_id = self.leader_for_pane(pane); // pane.read(cx).items().filter_map({ // let cx = &cx; // move |item| { // let item = item.to_followable_item_handle(cx)?; // if (project_id.is_none() || project_id != follower_project_id) // && item.is_project_item(cx) // { // return None; // } // let id = item.remote_id(client, cx)?.to_proto(); // let variant = item.to_state_proto(cx)?; // Some(proto::View { // id: Some(id), // leader_id, // variant: Some(variant), // }) // } // }) // }) // .collect(), // } } fn handle_update_followers( &mut self, leader_id: PeerId, message: proto::UpdateFollowers, _cx: &mut ViewContext, ) { self.leader_updates_tx .unbounded_send((leader_id, message)) .ok(); } async fn process_leader_update( this: &WeakView, leader_id: PeerId, update: proto::UpdateFollowers, cx: &mut AsyncAppContext, ) -> Result<()> { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { this.update(cx, |this, _| { for (_, state) in &mut this.follower_states { if state.leader_id == leader_id { state.active_view_id = if let Some(active_view_id) = update_active_view.id.clone() { Some(ViewId::from_proto(active_view_id)?) } else { None }; } } anyhow::Ok(()) })??; } proto::update_followers::Variant::UpdateView(update_view) => { let variant = update_view .variant .ok_or_else(|| anyhow!("missing update view variant"))?; let id = update_view .id .ok_or_else(|| anyhow!("missing update view id"))?; let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); for (_, state) in &mut this.follower_states { if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); } } } anyhow::Ok(()) })??; try_join_all(tasks).await.log_err(); } proto::update_followers::Variant::CreateView(view) => { let panes = this.read_with(cx, |this, _| { this.follower_states .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() .collect() })?; Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; } } this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; Ok(()) } // async fn add_views_from_leader( // this: WeakView, // leader_id: PeerId, // panes: Vec>, // views: Vec, // cx: &mut AsyncAppContext, // ) -> Result<()> { // let this = this // .upgrade(cx) // .ok_or_else(|| anyhow!("workspace dropped"))?; // let item_builders = cx.update(|cx| { // cx.default_global::() // .values() // .map(|b| b.0) // .collect::>() // }); // let mut item_tasks_by_pane = HashMap::default(); // for pane in panes { // let mut item_tasks = Vec::new(); // let mut leader_view_ids = Vec::new(); // for view in &views { // let Some(id) = &view.id else { continue }; // let id = ViewId::from_proto(id.clone())?; // let mut variant = view.variant.clone(); // if variant.is_none() { // Err(anyhow!("missing view variant"))?; // } // for build_item in &item_builders { // let task = cx // .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); // if let Some(task) = task { // item_tasks.push(task); // leader_view_ids.push(id); // break; // } else { // assert!(variant.is_some()); // } // } // } // item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); // } // for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { // let items = futures::future::try_join_all(item_tasks).await?; // this.update(cx, |this, cx| { // let state = this.follower_states.get_mut(&pane)?; // for (id, item) in leader_view_ids.into_iter().zip(items) { // item.set_leader_peer_id(Some(leader_id), cx); // state.items_by_leader_view_id.insert(id, item); // } // Some(()) // }); // } // Ok(()) // } // fn update_active_view_for_followers(&mut self, cx: &AppContext) { // let mut is_project_item = true; // let mut update = proto::UpdateActiveView::default(); // if self.active_pane.read(cx).has_focus() { // let item = self // .active_item(cx) // .and_then(|item| item.to_followable_item_handle(cx)); // if let Some(item) = item { // is_project_item = item.is_project_item(cx); // update = proto::UpdateActiveView { // id: item // .remote_id(&self.app_state.client, cx) // .map(|id| id.to_proto()), // leader_id: self.leader_for_pane(&self.active_pane), // }; // } // } // if update.id != self.last_active_view_id { // self.last_active_view_id = update.id.clone(); // self.update_followers( // is_project_item, // proto::update_followers::Variant::UpdateActiveView(update), // cx, // ); // } // } fn update_followers( &self, project_only: bool, update: proto::update_followers::Variant, cx: &mut WindowContext, ) -> Option<()> { let project_id = if project_only { self.project.read(cx).remote_id() } else { None }; self.app_state().workspace_store.update(cx, |store, cx| { store.update_followers(project_id, update, cx) }) } pub fn leader_for_pane(&self, pane: &View) -> Option { self.follower_states.get(pane).map(|state| state.leader_id) } fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { cx.notify(); let call = self.active_call()?; let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(leader_id)?; let mut items_to_activate = Vec::new(); let leader_in_this_app; let leader_in_this_project; match participant.location { call2::ParticipantLocation::SharedProject { project_id } => { leader_in_this_app = true; leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); } call2::ParticipantLocation::UnsharedProject => { leader_in_this_app = true; leader_in_this_project = false; } call2::ParticipantLocation::External => { leader_in_this_app = false; leader_in_this_project = false; } }; for (pane, state) in &self.follower_states { if state.leader_id != leader_id { continue; } if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { if leader_in_this_project || !item.is_project_item(cx) { items_to_activate.push((pane.clone(), item.boxed_clone())); } } else { log::warn!( "unknown view id {:?} for leader {:?}", active_view_id, leader_id ); } continue; } // todo!() // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { // items_to_activate.push((pane.clone(), Box::new(shared_screen))); // } } for (pane, item) in items_to_activate { let pane_was_focused = pane.read(cx).has_focus(); if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); } else { pane.update(cx, |pane, cx| { pane.add_item(item.boxed_clone(), false, false, None, cx) }); } if pane_was_focused { pane.update(cx, |pane, cx| pane.focus_active_item(cx)); } } None } // fn shared_screen_for_peer( // &self, // peer_id: PeerId, // pane: &View, // cx: &mut ViewContext, // ) -> Option> { // let call = self.active_call()?; // let room = call.read(cx).room()?.read(cx); // let participant = room.remote_participant_for_peer_id(peer_id)?; // let track = participant.video_tracks.values().next()?.clone(); // let user = participant.user.clone(); // for item in pane.read(cx).items_of_type::() { // if item.read(cx).peer_id == peer_id { // return Some(item); // } // } // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) // } // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { // if active { // self.update_active_view_for_followers(cx); // cx.background() // .spawn(persistence::DB.update_timestamp(self.database_id())) // .detach(); // } else { // for pane in &self.panes { // pane.update(cx, |pane, cx| { // if let Some(item) = pane.active_item() { // item.workspace_deactivated(cx); // } // if matches!( // settings::get::(cx).autosave, // AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange // ) { // for item in pane.items() { // Pane::autosave_item(item.as_ref(), self.project.clone(), cx) // .detach_and_log_err(cx); // } // } // }); // } // } // } fn active_call(&self) -> Option<&Model> { self.active_call.as_ref().map(|(call, _)| call) } fn on_active_call_event( &mut self, _: Model, event: &call2::room::Event, cx: &mut ViewContext, ) { match event { call2::room::Event::ParticipantLocationChanged { participant_id } | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { self.leader_updated(*participant_id, cx); } _ => {} } } pub fn database_id(&self) -> WorkspaceId { self.database_id } fn location(&self, cx: &AppContext) -> Option { let project = self.project().read(cx); if project.is_local() { Some( project .visible_worktrees(cx) .map(|worktree| worktree.read(cx).abs_path()) .collect::>() .into(), ) } else { None } } // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { // match member { // Member::Axis(PaneAxis { members, .. }) => { // for child in members.iter() { // self.remove_panes(child.clone(), cx) // } // } // Member::Pane(pane) => { // self.force_remove_pane(&pane, cx); // } // } // } fn force_remove_pane(&mut self, pane: &View, cx: &mut ViewContext) { self.panes.retain(|p| p != pane); if true { todo!() // cx.focus(self.panes.last().unwrap()); } if self.last_active_center_pane == Some(pane.downgrade()) { self.last_active_center_pane = None; } cx.notify(); } // fn schedule_serialize(&mut self, cx: &mut ViewContext) { // self._schedule_serialize = Some(cx.spawn(|this, cx| async move { // cx.background().timer(Duration::from_millis(100)).await; // this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) // .ok(); // })); // } fn serialize_workspace(&self, cx: &mut ViewContext) { fn serialize_pane_handle(pane_handle: &View, cx: &AppContext) -> SerializedPane { let (items, active) = { let pane = pane_handle.read(cx); let active_item_id = pane.active_item().map(|item| item.id()); ( pane.items() .filter_map(|item_handle| { Some(SerializedItem { kind: Arc::from(item_handle.serialized_item_kind()?), item_id: item_handle.id(), active: Some(item_handle.id()) == active_item_id, }) }) .collect::>(), pane.has_focus(), ) }; SerializedPane::new(items, active) } fn build_serialized_pane_group( pane_group: &Member, cx: &AppContext, ) -> SerializedPaneGroup { match pane_group { Member::Axis(PaneAxis { axis, members, flexes, bounding_boxes: _, }) => SerializedPaneGroup::Group { axis: *axis, children: members .iter() .map(|member| build_serialized_pane_group(member, cx)) .collect::>(), flexes: Some(flexes.lock().clone()), }, Member::Pane(pane_handle) => { SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) } } } fn build_serialized_docks( this: &Workspace, cx: &mut ViewContext, ) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); let left_active_panel = left_dock.visible_panel().and_then(|panel| { todo!() // Some( // cx.view_ui_name(panel.as_any().window(), panel.id())? // .to_string(), // ) }); let left_dock_zoom = left_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) .unwrap_or(false); let right_dock = this.right_dock.read(cx); let right_visible = right_dock.is_open(); let right_active_panel = right_dock.visible_panel().and_then(|panel| { todo!() // Some( // cx.view_ui_name(panel.as_any().window(), panel.id())? // .to_string(), // ) }); let right_dock_zoom = right_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) .unwrap_or(false); let bottom_dock = this.bottom_dock.read(cx); let bottom_visible = bottom_dock.is_open(); let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { todo!() // Some( // cx.view_ui_name(panel.as_any().window(), panel.id())? // .to_string(), // ) }); let bottom_dock_zoom = bottom_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) .unwrap_or(false); DockStructure { left: DockData { visible: left_visible, active_panel: left_active_panel, zoom: left_dock_zoom, }, right: DockData { visible: right_visible, active_panel: right_active_panel, zoom: right_dock_zoom, }, bottom: DockData { visible: bottom_visible, active_panel: bottom_active_panel, zoom: bottom_dock_zoom, }, } } if let Some(location) = self.location(cx) { // Load bearing special case: // - with_local_workspace() relies on this to not have other stuff open // when you open your log if !location.paths().is_empty() { let center_group = build_serialized_pane_group(&self.center.root, cx); let docks = build_serialized_docks(self, cx); let serialized_workspace = SerializedWorkspace { id: self.database_id, location, center_group, bounds: Default::default(), display: Default::default(), docks, }; cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace)) .detach(); } } } // pub(crate) fn load_workspace( // workspace: WeakView, // serialized_workspace: SerializedWorkspace, // paths_to_open: Vec>, // cx: &mut AppContext, // ) -> Task>>>> { // cx.spawn(|mut cx| async move { // let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { // ( // workspace.project().clone(), // workspace.last_active_center_pane.clone(), // ) // })?; // let mut center_group = None; // let mut center_items = None; // // Traverse the splits tree and add to things // if let Some((group, active_pane, items)) = serialized_workspace // .center_group // .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) // .await // { // center_items = Some(items); // center_group = Some((group, active_pane)) // } // let mut items_by_project_path = cx.read(|cx| { // center_items // .unwrap_or_default() // .into_iter() // .filter_map(|item| { // let item = item?; // let project_path = item.project_path(cx)?; // Some((project_path, item)) // }) // .collect::>() // }); // let opened_items = paths_to_open // .into_iter() // .map(|path_to_open| { // path_to_open // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) // }) // .collect::>(); // // Remove old panes from workspace panes list // workspace.update(&mut cx, |workspace, cx| { // if let Some((center_group, active_pane)) = center_group { // workspace.remove_panes(workspace.center.root.clone(), cx); // // Swap workspace center group // workspace.center = PaneGroup::with_root(center_group); // // Change the focus to the workspace first so that we retrigger focus in on the pane. // cx.focus_self(); // if let Some(active_pane) = active_pane { // cx.focus(&active_pane); // } else { // cx.focus(workspace.panes.last().unwrap()); // } // } else { // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); // if let Some(old_center_handle) = old_center_handle { // cx.focus(&old_center_handle) // } else { // cx.focus_self() // } // } // let docks = serialized_workspace.docks; // workspace.left_dock.update(cx, |dock, cx| { // dock.set_open(docks.left.visible, cx); // if let Some(active_panel) = docks.left.active_panel { // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { // dock.activate_panel(ix, cx); // } // } // dock.active_panel() // .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); // if docks.left.visible && docks.left.zoom { // cx.focus_self() // } // }); // // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something // workspace.right_dock.update(cx, |dock, cx| { // dock.set_open(docks.right.visible, cx); // if let Some(active_panel) = docks.right.active_panel { // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { // dock.activate_panel(ix, cx); // } // } // dock.active_panel() // .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); // if docks.right.visible && docks.right.zoom { // cx.focus_self() // } // }); // workspace.bottom_dock.update(cx, |dock, cx| { // dock.set_open(docks.bottom.visible, cx); // if let Some(active_panel) = docks.bottom.active_panel { // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { // dock.activate_panel(ix, cx); // } // } // dock.active_panel() // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); // if docks.bottom.visible && docks.bottom.zoom { // cx.focus_self() // } // }); // cx.notify(); // })?; // // Serialize ourself to make sure our timestamps and any pane / item changes are replicated // workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; // Ok(opened_items) // }) // } // #[cfg(any(test, feature = "test-support"))] // pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { // use node_runtime::FakeNodeRuntime; // let client = project.read(cx).client(); // let user_store = project.read(cx).user_store(); // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); // let app_state = Arc::new(AppState { // languages: project.read(cx).languages().clone(), // workspace_store, // client, // user_store, // fs: project.read(cx).fs().clone(), // build_window_options: |_, _, _| Default::default(), // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), // node_runtime: FakeNodeRuntime::new(), // }); // Self::new(0, project, app_state, cx) // } // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { // let dock = match position { // DockPosition::Left => &self.left_dock, // DockPosition::Right => &self.right_dock, // DockPosition::Bottom => &self.bottom_dock, // }; // let active_panel = dock.read(cx).visible_panel()?; // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { // dock.read(cx).render_placeholder(cx) // } else { // ChildView::new(dock, cx).into_any() // }; // Some( // element // .constrained() // .dynamically(move |constraint, _, cx| match position { // DockPosition::Left | DockPosition::Right => SizeConstraint::new( // Vector2F::new(20., constraint.min.y()), // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), // ), // DockPosition::Bottom => SizeConstraint::new( // Vector2F::new(constraint.min.x(), 20.), // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), // ), // }) // .into_any(), // ) // } // } } fn window_bounds_env_override(cx: &MainThread) -> Option { let display_origin = cx .update(|cx| Some(cx.displays().first()?.bounds().origin)) .ok()??; ZED_WINDOW_POSITION .zip(*ZED_WINDOW_SIZE) .map(|(position, size)| { WindowBounds::Fixed(Bounds { origin: display_origin + position, size, }) }) } // async fn open_items( // serialized_workspace: Option, // workspace: &WeakView, // mut project_paths_to_open: Vec<(PathBuf, Option)>, // app_state: Arc, // mut cx: AsyncAppContext, // ) -> Result>>>> { // let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); // if let Some(serialized_workspace) = serialized_workspace { // let workspace = workspace.clone(); // let restored_items = cx // .update(|cx| { // Workspace::load_workspace( // workspace, // serialized_workspace, // project_paths_to_open // .iter() // .map(|(_, project_path)| project_path) // .cloned() // .collect(), // cx, // ) // }) // .await?; // let restored_project_paths = cx.read(|cx| { // restored_items // .iter() // .filter_map(|item| item.as_ref()?.project_path(cx)) // .collect::>() // }); // for restored_item in restored_items { // opened_items.push(restored_item.map(Ok)); // } // project_paths_to_open // .iter_mut() // .for_each(|(_, project_path)| { // if let Some(project_path_to_open) = project_path { // if restored_project_paths.contains(project_path_to_open) { // *project_path = None; // } // } // }); // } else { // for _ in 0..project_paths_to_open.len() { // opened_items.push(None); // } // } // assert!(opened_items.len() == project_paths_to_open.len()); // let tasks = // project_paths_to_open // .into_iter() // .enumerate() // .map(|(i, (abs_path, project_path))| { // let workspace = workspace.clone(); // cx.spawn(|mut cx| { // let fs = app_state.fs.clone(); // async move { // let file_project_path = project_path?; // if fs.is_file(&abs_path).await { // Some(( // i, // workspace // .update(&mut cx, |workspace, cx| { // workspace.open_path(file_project_path, None, true, cx) // }) // .log_err()? // .await, // )) // } else { // None // } // } // }) // }); // for maybe_opened_path in futures::future::join_all(tasks.into_iter()) // .await // .into_iter() // { // if let Some((i, path_open_result)) = maybe_opened_path { // opened_items[i] = Some(path_open_result); // } // } // Ok(opened_items) // } // fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; // const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; // const MESSAGE_ID: usize = 2; // if workspace // .read_with(cx, |workspace, cx| { // workspace.has_shown_notification_once::(MESSAGE_ID, cx) // }) // .unwrap_or(false) // { // return; // } // if db::kvp::KEY_VALUE_STORE // .read_kvp(NEW_DOCK_HINT_KEY) // .ok() // .flatten() // .is_some() // { // if !workspace // .read_with(cx, |workspace, cx| { // workspace.has_shown_notification_once::(MESSAGE_ID, cx) // }) // .unwrap_or(false) // { // cx.update(|cx| { // cx.update_global::(|tracker, _| { // let entry = tracker // .entry(TypeId::of::()) // .or_default(); // if !entry.contains(&MESSAGE_ID) { // entry.push(MESSAGE_ID); // } // }); // }); // } // return; // } // cx.spawn(|_| async move { // db::kvp::KEY_VALUE_STORE // .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) // .await // .ok(); // }) // .detach(); // workspace // .update(cx, |workspace, cx| { // workspace.show_notification_once(2, cx, |cx| { // cx.build_view(|_| { // MessageNotification::new_element(|text, _| { // Text::new( // "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", // text, // ) // .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { // let code_span_background_color = settings::get::(cx) // .theme // .editor // .document_highlight_read_background; // cx.scene().push_quad(gpui::Quad { // bounds, // background: Some(code_span_background_color), // border: Default::default(), // corner_radii: (2.0).into(), // }) // }) // .into_any() // }) // .with_click_message("Read more about the new panel system") // .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) // }) // }) // }) // .ok(); // fn notify_if_database_failed(workspace: &WeakView, cx: &mut AsyncAppContext) { // const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; // workspace // .update(cx, |workspace, cx| { // if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { // workspace.show_notification_once(0, cx, |cx| { // cx.build_view(|_| { // MessageNotification::new("Failed to load the database file.") // .with_click_message("Click to let us know about this error") // .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) // }) // }); // } // }) // .log_err(); // } impl EventEmitter for Workspace { type Event = Event; } impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() } } // todo!() // impl Entity for Workspace { // type Event = Event; // fn release(&mut self, cx: &mut AppContext) { // self.app_state.workspace_store.update(cx, |store, _| { // store.workspaces.remove(&self.weak_self); // }) // } // } // impl View for Workspace { // fn ui_name() -> &'static str { // "Workspace" // } // fn render(&mut self, cx: &mut ViewContext) -> AnyElement { // let theme = theme::current(cx).clone(); // Stack::new() // .with_child( // Flex::column() // .with_child(self.render_titlebar(&theme, cx)) // .with_child( // Stack::new() // .with_child({ // let project = self.project.clone(); // Flex::row() // .with_children(self.render_dock(DockPosition::Left, cx)) // .with_child( // Flex::column() // .with_child( // FlexItem::new( // self.center.render( // &project, // &theme, // &self.follower_states, // self.active_call(), // self.active_pane(), // self.zoomed // .as_ref() // .and_then(|zoomed| zoomed.upgrade(cx)) // .as_ref(), // &self.app_state, // cx, // ), // ) // .flex(1., true), // ) // .with_children( // self.render_dock(DockPosition::Bottom, cx), // ) // .flex(1., true), // ) // .with_children(self.render_dock(DockPosition::Right, cx)) // }) // .with_child(Overlay::new( // Stack::new() // .with_children(self.zoomed.as_ref().and_then(|zoomed| { // enum ZoomBackground {} // let zoomed = zoomed.upgrade(cx)?; // let mut foreground_style = // theme.workspace.zoomed_pane_foreground; // if let Some(zoomed_dock_position) = self.zoomed_position { // foreground_style = // theme.workspace.zoomed_panel_foreground; // let margin = foreground_style.margin.top; // let border = foreground_style.border.top; // // Only include a margin and border on the opposite side. // foreground_style.margin.top = 0.; // foreground_style.margin.left = 0.; // foreground_style.margin.bottom = 0.; // foreground_style.margin.right = 0.; // foreground_style.border.top = false; // foreground_style.border.left = false; // foreground_style.border.bottom = false; // foreground_style.border.right = false; // match zoomed_dock_position { // DockPosition::Left => { // foreground_style.margin.right = margin; // foreground_style.border.right = border; // } // DockPosition::Right => { // foreground_style.margin.left = margin; // foreground_style.border.left = border; // } // DockPosition::Bottom => { // foreground_style.margin.top = margin; // foreground_style.border.top = border; // } // } // } // Some( // ChildView::new(&zoomed, cx) // .contained() // .with_style(foreground_style) // .aligned() // .contained() // .with_style(theme.workspace.zoomed_background) // .mouse::(0) // .capture_all() // .on_down( // MouseButton::Left, // |_, this: &mut Self, cx| { // this.zoom_out(cx); // }, // ), // ) // })) // .with_children(self.modal.as_ref().map(|modal| { // // Prevent clicks within the modal from falling // // through to the rest of the workspace. // enum ModalBackground {} // MouseEventHandler::new::( // 0, // cx, // |_, cx| ChildView::new(modal.view.as_any(), cx), // ) // .on_click(MouseButton::Left, |_, _, _| {}) // .contained() // .with_style(theme.workspace.modal) // .aligned() // .top() // })) // .with_children(self.render_notifications(&theme.workspace, cx)), // )) // .provide_resize_bounds::() // .flex(1.0, true), // ) // .with_child(ChildView::new(&self.status_bar, cx)) // .contained() // .with_background_color(theme.workspace.background), // ) // .with_children(DragAndDrop::render(cx)) // .with_children(self.render_disconnected_overlay(cx)) // .into_any_named("workspace") // } // fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { // if cx.is_self_focused() { // cx.focus(&self.active_pane); // } // } // fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext) -> bool { // DragAndDrop::::update_modifiers(e.modifiers, cx) // } // } impl WorkspaceStore { pub fn new(client: Arc, cx: &mut ModelContext) -> Self { Self { workspaces: Default::default(), followers: Default::default(), _subscriptions: vec![], // client.add_request_handler(cx.weak_model(), Self::handle_follow), // client.add_message_handler(cx.weak_model(), Self::handle_unfollow), // client.add_message_handler(cx.weak_model(), Self::handle_update_followers), // ], client, } } pub fn update_followers( &self, project_id: Option, update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { if !cx.has_global::>() { return None; } let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); let follower_ids: Vec<_> = self .followers .iter() .filter_map(|follower| { if follower.project_id == project_id || project_id.is_none() { Some(follower.peer_id.into()) } else { None } }) .collect(); if follower_ids.is_empty() { return None; } self.client .send(proto::UpdateFollowers { room_id, project_id, follower_ids, variant: Some(update), }) .log_err() } pub async fn handle_follow( this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result { this.update(&mut cx, |this, cx| { let follower = Follower { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; let active_project = ActiveCall::global(cx).read(cx).location().cloned(); let mut response = proto::FollowResponse::default(); for workspace in &this.workspaces { workspace .update_root(cx, |workspace, cx| { let handler_response = workspace.handle_follow(follower.project_id, cx); if response.views.is_empty() { response.views = handler_response.views; } else { response.views.extend_from_slice(&handler_response.views); } if let Some(active_view_id) = handler_response.active_view_id.clone() { if response.active_view_id.is_none() || Some(workspace.project.downgrade()) == active_project { response.active_view_id = Some(active_view_id); } } }) .ok(); } if let Err(ix) = this.followers.binary_search(&follower) { this.followers.insert(ix, follower); } Ok(response) })? } async fn handle_unfollow( model: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { model.update(&mut cx, |this, _| { let follower = Follower { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; if let Ok(ix) = this.followers.binary_search(&follower) { this.followers.remove(ix); } Ok(()) })? } async fn handle_update_followers( this: Model, envelope: TypedEnvelope, _: Arc, mut cx: AsyncWindowContext, ) -> Result<()> { // let leader_id = envelope.original_sender_id()?; // let update = envelope.payload; // this.update(&mut cx, |this, cx| { // for workspace in &this.workspaces { // let Some(workspace) = workspace.upgrade() else { // continue; // }; // workspace.update(cx, |workspace, cx| { // let project_id = workspace.project.read(cx).remote_id(); // if update.project_id != project_id && update.project_id.is_some() { // return; // } // workspace.handle_update_followers(leader_id, update.clone(), cx); // }); // } // Ok(()) // })? todo!() } } // impl Entity for WorkspaceStore { // type Event = (); // } impl ViewId { pub(crate) fn from_proto(message: proto::ViewId) -> Result { Ok(Self { creator: message .creator .ok_or_else(|| anyhow!("creator is missing"))?, id: message.id, }) } pub(crate) fn to_proto(&self) -> proto::ViewId { proto::ViewId { creator: Some(self.creator), id: self.id, } } } // pub trait WorkspaceHandle { // fn file_project_paths(&self, cx: &AppContext) -> Vec; // } // impl WorkspaceHandle for View { // fn file_project_paths(&self, cx: &AppContext) -> Vec { // self.read(cx) // .worktrees(cx) // .flat_map(|worktree| { // let worktree_id = worktree.read(cx).id(); // worktree.read(cx).files(true, 0).map(move |f| ProjectPath { // worktree_id, // path: f.path.clone(), // }) // }) // .collect::>() // } // } // impl std::fmt::Debug for OpenPaths { // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // f.debug_struct("OpenPaths") // .field("paths", &self.paths) // .finish() // } // } // pub struct WorkspaceCreated(pub WeakView); pub async fn activate_workspace_for_project( cx: &mut AsyncAppContext, predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, ) -> Option> { cx.run_on_main(move |cx| { for window in cx.windows() { let Some(workspace) = window.downcast::() else { continue; }; let predicate = cx .update_window_root(&workspace, |workspace, cx| { let project = workspace.project.read(cx); if predicate(project, cx) { cx.activate_window(); true } else { false } }) .log_err() .unwrap_or(false); if predicate { return Some(workspace); } } None }) .ok()? .await } pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } // async fn join_channel_internal( // channel_id: u64, // app_state: &Arc, // requesting_window: Option>, // active_call: &ModelHandle, // cx: &mut AsyncAppContext, // ) -> Result { // let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { // let Some(room) = active_call.room().map(|room| room.read(cx)) else { // return (false, None); // }; // let already_in_channel = room.channel_id() == Some(channel_id); // let should_prompt = room.is_sharing_project() // && room.remote_participants().len() > 0 // && !already_in_channel; // let open_room = if already_in_channel { // active_call.room().cloned() // } else { // None // }; // (should_prompt, open_room) // }); // if let Some(room) = open_room { // let task = room.update(cx, |room, cx| { // if let Some((project, host)) = room.most_active_project(cx) { // return Some(join_remote_project(project, host, app_state.clone(), cx)); // } // None // }); // if let Some(task) = task { // task.await?; // } // return anyhow::Ok(true); // } // if should_prompt { // if let Some(workspace) = requesting_window { // if let Some(window) = workspace.update(cx, |cx| cx.window()) { // let answer = window.prompt( // PromptLevel::Warning, // "Leaving this call will unshare your current project.\nDo you want to switch channels?", // &["Yes, Join Channel", "Cancel"], // cx, // ); // if let Some(mut answer) = answer { // if answer.next().await == Some(1) { // return Ok(false); // } // } // } else { // return Ok(false); // unreachable!() hopefully // } // } else { // return Ok(false); // unreachable!() hopefully // } // } // let client = cx.read(|cx| active_call.read(cx).client()); // let mut client_status = client.status(); // // this loop will terminate within client::CONNECTION_TIMEOUT seconds. // 'outer: loop { // let Some(status) = client_status.recv().await else { // return Err(anyhow!("error connecting")); // }; // match status { // Status::Connecting // | Status::Authenticating // | Status::Reconnecting // | Status::Reauthenticating => continue, // Status::Connected { .. } => break 'outer, // Status::SignedOut => return Err(anyhow!("not signed in")), // Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), // Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { // return Err(anyhow!("zed is offline")) // } // } // } // let room = active_call // .update(cx, |active_call, cx| { // active_call.join_channel(channel_id, cx) // }) // .await?; // room.update(cx, |room, _| room.room_update_completed()) // .await; // let task = room.update(cx, |room, cx| { // if let Some((project, host)) = room.most_active_project(cx) { // return Some(join_remote_project(project, host, app_state.clone(), cx)); // } // None // }); // if let Some(task) = task { // task.await?; // return anyhow::Ok(true); // } // anyhow::Ok(false) // } // pub fn join_channel( // channel_id: u64, // app_state: Arc, // requesting_window: Option>, // cx: &mut AppContext, // ) -> Task> { // let active_call = ActiveCall::global(cx); // cx.spawn(|mut cx| async move { // let result = join_channel_internal( // channel_id, // &app_state, // requesting_window, // &active_call, // &mut cx, // ) // .await; // // join channel succeeded, and opened a window // if matches!(result, Ok(true)) { // return anyhow::Ok(()); // } // if requesting_window.is_some() { // return anyhow::Ok(()); // } // // find an existing workspace to focus and show call controls // let mut active_window = activate_any_workspace_window(&mut cx); // if active_window.is_none() { // // no open workspaces, make one to show the error in (blergh) // cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) // .await; // } // active_window = activate_any_workspace_window(&mut cx); // if active_window.is_none() { // return result.map(|_| ()); // unreachable!() assuming new_local always opens a window // } // if let Err(err) = result { // let prompt = active_window.unwrap().prompt( // PromptLevel::Critical, // &format!("Failed to join channel: {}", err), // &["Ok"], // &mut cx, // ); // if let Some(mut prompt) = prompt { // prompt.next().await; // } else { // return Err(err); // } // } // // return ok, we showed the error to the user. // return anyhow::Ok(()); // }) // } // pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { // for window in cx.windows() { // let found = window.update(cx, |cx| { // let is_workspace = cx.root_view().clone().downcast::().is_some(); // if is_workspace { // cx.activate_window(); // } // is_workspace // }); // if found == Some(true) { // return Some(window); // } // } // None // } #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, requesting_window: Option>, cx: &mut AppContext, ) -> Task< anyhow::Result<( WindowHandle, Vec, anyhow::Error>>>, )>, > { let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); cx.spawn(move |mut cx| async move { // Open paths in existing workspace if possible let existing = activate_workspace_for_project(&mut cx, move |project, cx| { project.contains_paths(&abs_paths, cx) }) .await; if let Some(existing) = existing { // // Ok(( // existing.clone(), // cx.update_window_root(&existing, |workspace, cx| { // workspace.open_paths(abs_paths, true, cx) // })? // .await, // )) todo!() } else { // Ok(cx // .update(|cx| { // Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) // }) // .await) todo!() } }) } pub fn open_new( app_state: &Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, ) -> Task<()> { let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); cx.spawn(|mut cx| async move { let (workspace, opened_paths) = task.await; workspace .update(&mut cx, |workspace, cx| { if opened_paths.is_empty() { init(workspace, cx) } }) .log_err(); }) } // pub fn create_and_open_local_file( // path: &'static Path, // cx: &mut ViewContext, // default_content: impl 'static + Send + FnOnce() -> Rope, // ) -> Task>> { // cx.spawn(|workspace, mut cx| async move { // let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?; // if !fs.is_file(path).await { // fs.create_file(path, Default::default()).await?; // fs.save(path, &default_content(), Default::default()) // .await?; // } // let mut items = workspace // .update(&mut cx, |workspace, cx| { // workspace.with_local_workspace(cx, |workspace, cx| { // workspace.open_paths(vec![path.to_path_buf()], false, cx) // }) // })? // .await? // .await; // let item = items.pop().flatten(); // item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? // }) // } // pub fn join_remote_project( // project_id: u64, // follow_user_id: u64, // app_state: Arc, // cx: &mut AppContext, // ) -> Task> { // cx.spawn(|mut cx| async move { // let windows = cx.windows(); // let existing_workspace = windows.into_iter().find_map(|window| { // window.downcast::().and_then(|window| { // window // .read_root_with(&cx, |workspace, cx| { // if workspace.project().read(cx).remote_id() == Some(project_id) { // Some(cx.handle().downgrade()) // } else { // None // } // }) // .unwrap_or(None) // }) // }); // let workspace = if let Some(existing_workspace) = existing_workspace { // existing_workspace // } else { // let active_call = cx.read(ActiveCall::global); // let room = active_call // .read_with(&cx, |call, _| call.room().cloned()) // .ok_or_else(|| anyhow!("not in a call"))?; // let project = room // .update(&mut cx, |room, cx| { // room.join_project( // project_id, // app_state.languages.clone(), // app_state.fs.clone(), // cx, // ) // }) // .await?; // let window_bounds_override = window_bounds_env_override(&cx); // let window = cx.add_window( // (app_state.build_window_options)( // window_bounds_override, // None, // cx.platform().as_ref(), // ), // |cx| Workspace::new(0, project, app_state.clone(), cx), // ); // let workspace = window.root(&cx).unwrap(); // (app_state.initialize_workspace)( // workspace.downgrade(), // false, // app_state.clone(), // cx.clone(), // ) // .await // .log_err(); // workspace.downgrade() // }; // workspace.window().activate(&mut cx); // cx.platform().activate(true); // workspace.update(&mut cx, |workspace, cx| { // if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { // let follow_peer_id = room // .read(cx) // .remote_participants() // .iter() // .find(|(_, participant)| participant.user.id == follow_user_id) // .map(|(_, p)| p.peer_id) // .or_else(|| { // // If we couldn't follow the given user, follow the host instead. // let collaborator = workspace // .project() // .read(cx) // .collaborators() // .values() // .find(|collaborator| collaborator.replica_id == 0)?; // Some(collaborator.peer_id) // }); // if let Some(follow_peer_id) = follow_peer_id { // workspace // .follow(follow_peer_id, cx) // .map(|follow| follow.detach_and_log_err(cx)); // } // } // })?; // anyhow::Ok(()) // }) // } // pub fn restart(_: &Restart, cx: &mut AppContext) { // let should_confirm = settings::get::(cx).confirm_quit; // cx.spawn(|mut cx| async move { // let mut workspace_windows = cx // .windows() // .into_iter() // .filter_map(|window| window.downcast::()) // .collect::>(); // // If multiple windows have unsaved changes, and need a save prompt, // // prompt in the active window before switching to a different window. // workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); // if let (true, Some(window)) = (should_confirm, workspace_windows.first()) { // let answer = window.prompt( // PromptLevel::Info, // "Are you sure you want to restart?", // &["Restart", "Cancel"], // &mut cx, // ); // if let Some(mut answer) = answer { // let answer = answer.next().await; // if answer != Some(0) { // return Ok(()); // } // } // } // // If the user cancels any save prompt, then keep the app open. // for window in workspace_windows { // if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { // workspace.prepare_to_close(true, cx) // }) { // if !should_close.await? { // return Ok(()); // } // } // } // cx.platform().restart(); // anyhow::Ok(()) // }) // .detach_and_log_err(cx); // } fn parse_pixel_position_env_var(value: &str) -> Option> { let mut parts = value.split(','); let x: usize = parts.next()?.parse().ok()?; let y: usize = parts.next()?.parse().ok()?; Some(point((x as f64).into(), (y as f64).into())) } fn parse_pixel_size_env_var(value: &str) -> Option> { let mut parts = value.split(','); let width: usize = parts.next()?.parse().ok()?; let height: usize = parts.next()?.parse().ok()?; Some(size((width as f64).into(), (height as f64).into())) } // #[cfg(test)] // mod tests { // use super::*; // use crate::{ // dock::test::{TestPanel, TestPanelEvent}, // item::test::{TestItem, TestItemEvent, TestProjectItem}, // }; // use fs::FakeFs; // use gpui::{executor::Deterministic, test::EmptyView, TestAppContext}; // use project::{Project, ProjectEntryId}; // use serde_json::json; // use settings::SettingsStore; // use std::{cell::RefCell, rc::Rc}; // #[gpui::test] // async fn test_tab_disambiguation(cx: &mut TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); // let workspace = window.root(cx); // // Adding an item with no ambiguity renders the tab without detail. // let item1 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); // item // }); // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item1.clone()), cx); // }); // item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None)); // // Adding an item that creates ambiguity increases the level of detail on // // both tabs. // let item2 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); // item // }); // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item2.clone()), cx); // }); // item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); // item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); // // Adding an item that creates ambiguity increases the level of detail only // // on the ambiguous tabs. In this case, the ambiguity can't be resolved so // // we stop at the highest detail available. // let item3 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); // item // }); // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item3.clone()), cx); // }); // item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); // item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); // item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); // } // #[gpui::test] // async fn test_tracking_active_path(cx: &mut TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // fs.insert_tree( // "/root1", // json!({ // "one.txt": "", // "two.txt": "", // }), // ) // .await; // fs.insert_tree( // "/root2", // json!({ // "three.txt": "", // }), // ) // .await; // let project = Project::test(fs, ["root1".as_ref()], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); // let workspace = window.root(cx); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // let worktree_id = project.read_with(cx, |project, cx| { // project.worktrees(cx).next().unwrap().read(cx).id() // }); // let item1 = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) // }); // let item2 = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) // }); // // Add an item to an empty pane // workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx)); // project.read_with(cx, |project, cx| { // assert_eq!( // project.active_entry(), // project // .entry_for_path(&(worktree_id, "one.txt").into(), cx) // .map(|e| e.id) // ); // }); // assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1")); // // Add a second item to a non-empty pane // workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); // assert_eq!(window.current_title(cx).as_deref(), Some("two.txt — root1")); // project.read_with(cx, |project, cx| { // assert_eq!( // project.active_entry(), // project // .entry_for_path(&(worktree_id, "two.txt").into(), cx) // .map(|e| e.id) // ); // }); // // Close the active item // pane.update(cx, |pane, cx| { // pane.close_active_item(&Default::default(), cx).unwrap() // }) // .await // .unwrap(); // assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1")); // project.read_with(cx, |project, cx| { // assert_eq!( // project.active_entry(), // project // .entry_for_path(&(worktree_id, "one.txt").into(), cx) // .map(|e| e.id) // ); // }); // // Add a project folder // project // .update(cx, |project, cx| { // project.find_or_create_local_worktree("/root2", true, cx) // }) // .await // .unwrap(); // assert_eq!( // window.current_title(cx).as_deref(), // Some("one.txt — root1, root2") // ); // // Remove a project folder // project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); // assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root2")); // } // #[gpui::test] // async fn test_close_window(cx: &mut TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // fs.insert_tree("/root", json!({ "one": "" })).await; // let project = Project::test(fs, ["root".as_ref()], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); // let workspace = window.root(cx); // // When there are no dirty items, there's nothing to do. // let item1 = window.build_view(cx, |_| TestItem::new()); // workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); // let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); // assert!(task.await.unwrap()); // // When there are dirty untitled items, prompt to save each one. If the user // // cancels any prompt, then abort. // let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true)); // let item3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // workspace.update(cx, |w, cx| { // w.add_item(Box::new(item2.clone()), cx); // w.add_item(Box::new(item3.clone()), cx); // }); // let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); // cx.foreground().run_until_parked(); // window.simulate_prompt_answer(2, cx); // cancel save all // cx.foreground().run_until_parked(); // window.simulate_prompt_answer(2, cx); // cancel save all // cx.foreground().run_until_parked(); // assert!(!window.has_pending_prompt(cx)); // assert!(!task.await.unwrap()); // } // #[gpui::test] // async fn test_close_pane_items(cx: &mut TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, None, cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // let item1 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // let item2 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_conflict(true) // .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) // }); // let item3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_conflict(true) // .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) // }); // let item4 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new_untitled(cx)]) // }); // let pane = workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item1.clone()), cx); // workspace.add_item(Box::new(item2.clone()), cx); // workspace.add_item(Box::new(item3.clone()), cx); // workspace.add_item(Box::new(item4.clone()), cx); // workspace.active_pane().clone() // }); // let close_items = pane.update(cx, |pane, cx| { // pane.activate_item(1, true, true, cx); // assert_eq!(pane.active_item().unwrap().id(), item2.id()); // let item1_id = item1.id(); // let item3_id = item3.id(); // let item4_id = item4.id(); // pane.close_items(cx, SaveIntent::Close, move |id| { // [item1_id, item3_id, item4_id].contains(&id) // }) // }); // cx.foreground().run_until_parked(); // assert!(window.has_pending_prompt(cx)); // // Ignore "Save all" prompt // window.simulate_prompt_answer(2, cx); // cx.foreground().run_until_parked(); // // There's a prompt to save item 1. // pane.read_with(cx, |pane, _| { // assert_eq!(pane.items_len(), 4); // assert_eq!(pane.active_item().unwrap().id(), item1.id()); // }); // // Confirm saving item 1. // window.simulate_prompt_answer(0, cx); // cx.foreground().run_until_parked(); // // Item 1 is saved. There's a prompt to save item 3. // pane.read_with(cx, |pane, cx| { // assert_eq!(item1.read(cx).save_count, 1); // assert_eq!(item1.read(cx).save_as_count, 0); // assert_eq!(item1.read(cx).reload_count, 0); // assert_eq!(pane.items_len(), 3); // assert_eq!(pane.active_item().unwrap().id(), item3.id()); // }); // assert!(window.has_pending_prompt(cx)); // // Cancel saving item 3. // window.simulate_prompt_answer(1, cx); // cx.foreground().run_until_parked(); // // Item 3 is reloaded. There's a prompt to save item 4. // pane.read_with(cx, |pane, cx| { // assert_eq!(item3.read(cx).save_count, 0); // assert_eq!(item3.read(cx).save_as_count, 0); // assert_eq!(item3.read(cx).reload_count, 1); // assert_eq!(pane.items_len(), 2); // assert_eq!(pane.active_item().unwrap().id(), item4.id()); // }); // assert!(window.has_pending_prompt(cx)); // // Confirm saving item 4. // window.simulate_prompt_answer(0, cx); // cx.foreground().run_until_parked(); // // There's a prompt for a path for item 4. // cx.simulate_new_path_selection(|_| Some(Default::default())); // close_items.await.unwrap(); // // The requested items are closed. // pane.read_with(cx, |pane, cx| { // assert_eq!(item4.read(cx).save_count, 0); // assert_eq!(item4.read(cx).save_as_count, 1); // assert_eq!(item4.read(cx).reload_count, 0); // assert_eq!(pane.items_len(), 1); // assert_eq!(pane.active_item().unwrap().id(), item2.id()); // }); // } // #[gpui::test] // async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // // Create several workspace items with single project entries, and two // // workspace items with multiple project entries. // let single_entry_items = (0..=4) // .map(|project_entry_id| { // window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new( // project_entry_id, // &format!("{project_entry_id}.txt"), // cx, // )]) // }) // }) // .collect::>(); // let item_2_3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_singleton(false) // .with_project_items(&[ // single_entry_items[2].read(cx).project_items[0].clone(), // single_entry_items[3].read(cx).project_items[0].clone(), // ]) // }); // let item_3_4 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_singleton(false) // .with_project_items(&[ // single_entry_items[3].read(cx).project_items[0].clone(), // single_entry_items[4].read(cx).project_items[0].clone(), // ]) // }); // // Create two panes that contain the following project entries: // // left pane: // // multi-entry items: (2, 3) // // single-entry items: 0, 1, 2, 3, 4 // // right pane: // // single-entry items: 1 // // multi-entry items: (3, 4) // let left_pane = workspace.update(cx, |workspace, cx| { // let left_pane = workspace.active_pane().clone(); // workspace.add_item(Box::new(item_2_3.clone()), cx); // for item in single_entry_items { // workspace.add_item(Box::new(item), cx); // } // left_pane.update(cx, |pane, cx| { // pane.activate_item(2, true, true, cx); // }); // workspace // .split_and_clone(left_pane.clone(), SplitDirection::Right, cx) // .unwrap(); // left_pane // }); // //Need to cause an effect flush in order to respect new focus // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item_3_4.clone()), cx); // cx.focus(&left_pane); // }); // // When closing all of the items in the left pane, we should be prompted twice: // // once for project entry 0, and once for project entry 2. After those two // // prompts, the task should complete. // let close = left_pane.update(cx, |pane, cx| { // pane.close_items(cx, SaveIntent::Close, move |_| true) // }); // cx.foreground().run_until_parked(); // // Discard "Save all" prompt // window.simulate_prompt_answer(2, cx); // cx.foreground().run_until_parked(); // left_pane.read_with(cx, |pane, cx| { // assert_eq!( // pane.active_item().unwrap().project_entry_ids(cx).as_slice(), // &[ProjectEntryId::from_proto(0)] // ); // }); // window.simulate_prompt_answer(0, cx); // cx.foreground().run_until_parked(); // left_pane.read_with(cx, |pane, cx| { // assert_eq!( // pane.active_item().unwrap().project_entry_ids(cx).as_slice(), // &[ProjectEntryId::from_proto(2)] // ); // }); // window.simulate_prompt_answer(0, cx); // cx.foreground().run_until_parked(); // close.await.unwrap(); // left_pane.read_with(cx, |pane, _| { // assert_eq!(pane.items_len(), 0); // }); // } // #[gpui::test] // async fn test_autosave(deterministic: Arc, cx: &mut gpui::TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // let item = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // let item_id = item.id(); // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item.clone()), cx); // }); // // Autosave on window change. // item.update(cx, |item, cx| { // cx.update_global(|settings: &mut SettingsStore, cx| { // settings.update_user_settings::(cx, |settings| { // settings.autosave = Some(AutosaveSetting::OnWindowChange); // }) // }); // item.is_dirty = true; // }); // // Deactivating the window saves the file. // window.simulate_deactivation(cx); // deterministic.run_until_parked(); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 1)); // // Autosave on focus change. // item.update(cx, |item, cx| { // cx.focus_self(); // cx.update_global(|settings: &mut SettingsStore, cx| { // settings.update_user_settings::(cx, |settings| { // settings.autosave = Some(AutosaveSetting::OnFocusChange); // }) // }); // item.is_dirty = true; // }); // // Blurring the item saves the file. // item.update(cx, |_, cx| cx.blur()); // deterministic.run_until_parked(); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); // // Deactivating the window still saves the file. // window.simulate_activation(cx); // item.update(cx, |item, cx| { // cx.focus_self(); // item.is_dirty = true; // }); // window.simulate_deactivation(cx); // deterministic.run_until_parked(); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); // // Autosave after delay. // item.update(cx, |item, cx| { // cx.update_global(|settings: &mut SettingsStore, cx| { // settings.update_user_settings::(cx, |settings| { // settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); // }) // }); // item.is_dirty = true; // cx.emit(TestItemEvent::Edit); // }); // // Delay hasn't fully expired, so the file is still dirty and unsaved. // deterministic.advance_clock(Duration::from_millis(250)); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); // // After delay expires, the file is saved. // deterministic.advance_clock(Duration::from_millis(250)); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); // // Autosave on focus change, ensuring closing the tab counts as such. // item.update(cx, |item, cx| { // cx.update_global(|settings: &mut SettingsStore, cx| { // settings.update_user_settings::(cx, |settings| { // settings.autosave = Some(AutosaveSetting::OnFocusChange); // }) // }); // item.is_dirty = true; // }); // pane.update(cx, |pane, cx| { // pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) // }) // .await // .unwrap(); // assert!(!window.has_pending_prompt(cx)); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item.clone()), cx); // }); // item.update(cx, |item, cx| { // item.project_items[0].update(cx, |item, _| { // item.entry_id = None; // }); // item.is_dirty = true; // cx.blur(); // }); // deterministic.run_until_parked(); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // // Ensure autosave is prevented for deleted files also when closing the buffer. // let _close_items = pane.update(cx, |pane, cx| { // pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) // }); // deterministic.run_until_parked(); // assert!(window.has_pending_prompt(cx)); // item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // } // #[gpui::test] // async fn test_pane_navigation(cx: &mut gpui::TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // let item = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); // let toolbar_notify_count = Rc::new(RefCell::new(0)); // workspace.update(cx, |workspace, cx| { // workspace.add_item(Box::new(item.clone()), cx); // let toolbar_notification_count = toolbar_notify_count.clone(); // cx.observe(&toolbar, move |_, _, _| { // *toolbar_notification_count.borrow_mut() += 1 // }) // .detach(); // }); // pane.read_with(cx, |pane, _| { // assert!(!pane.can_navigate_backward()); // assert!(!pane.can_navigate_forward()); // }); // item.update(cx, |item, cx| { // item.set_state("one".to_string(), cx); // }); // // Toolbar must be notified to re-render the navigation buttons // assert_eq!(*toolbar_notify_count.borrow(), 1); // pane.read_with(cx, |pane, _| { // assert!(pane.can_navigate_backward()); // assert!(!pane.can_navigate_forward()); // }); // workspace // .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx)) // .await // .unwrap(); // assert_eq!(*toolbar_notify_count.borrow(), 3); // pane.read_with(cx, |pane, _| { // assert!(!pane.can_navigate_backward()); // assert!(pane.can_navigate_forward()); // }); // } // #[gpui::test] // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // let panel = workspace.update(cx, |workspace, cx| { // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); // workspace.add_panel(panel.clone(), cx); // workspace // .right_dock() // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); // panel // }); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // pane.update(cx, |pane, cx| { // let item = cx.build_view(|_| TestItem::new()); // pane.add_item(Box::new(item), true, true, None, cx); // }); // // Transfer focus from center to panel // workspace.update(cx, |workspace, cx| { // workspace.toggle_panel_focus::(cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(!panel.is_zoomed(cx)); // assert!(panel.has_focus(cx)); // }); // // Transfer focus from panel to center // workspace.update(cx, |workspace, cx| { // workspace.toggle_panel_focus::(cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(!panel.is_zoomed(cx)); // assert!(!panel.has_focus(cx)); // }); // // Close the dock // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(!workspace.right_dock().read(cx).is_open()); // assert!(!panel.is_zoomed(cx)); // assert!(!panel.has_focus(cx)); // }); // // Open the dock // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(!panel.is_zoomed(cx)); // assert!(panel.has_focus(cx)); // }); // // Focus and zoom panel // panel.update(cx, |panel, cx| { // cx.focus_self(); // panel.set_zoomed(true, cx) // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(panel.is_zoomed(cx)); // assert!(panel.has_focus(cx)); // }); // // Transfer focus to the center closes the dock // workspace.update(cx, |workspace, cx| { // workspace.toggle_panel_focus::(cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(!workspace.right_dock().read(cx).is_open()); // assert!(panel.is_zoomed(cx)); // assert!(!panel.has_focus(cx)); // }); // // Transferring focus back to the panel keeps it zoomed // workspace.update(cx, |workspace, cx| { // workspace.toggle_panel_focus::(cx); // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(panel.is_zoomed(cx)); // assert!(panel.has_focus(cx)); // }); // // Close the dock while it is zoomed // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx) // }); // workspace.read_with(cx, |workspace, cx| { // assert!(!workspace.right_dock().read(cx).is_open()); // assert!(panel.is_zoomed(cx)); // assert!(workspace.zoomed.is_none()); // assert!(!panel.has_focus(cx)); // }); // // Opening the dock, when it's zoomed, retains focus // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx) // }); // workspace.read_with(cx, |workspace, cx| { // assert!(workspace.right_dock().read(cx).is_open()); // assert!(panel.is_zoomed(cx)); // assert!(workspace.zoomed.is_some()); // assert!(panel.has_focus(cx)); // }); // // Unzoom and close the panel, zoom the active pane. // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx) // }); // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); // // Opening a dock unzooms the pane. // workspace.update(cx, |workspace, cx| { // workspace.toggle_dock(DockPosition::Right, cx) // }); // workspace.read_with(cx, |workspace, cx| { // let pane = pane.read(cx); // assert!(!pane.is_zoomed()); // assert!(!pane.has_focus()); // assert!(workspace.right_dock().read(cx).is_open()); // assert!(workspace.zoomed.is_none()); // }); // } // #[gpui::test] // async fn test_panels(cx: &mut gpui::TestAppContext) { // init_test(cx); // let fs = FakeFs::new(cx.background()); // let project = Project::test(fs, [], cx).await; // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // // Add panel_1 on the left, panel_2 on the right. // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left)); // workspace.add_panel(panel_1.clone(), cx); // workspace // .left_dock() // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right)); // workspace.add_panel(panel_2.clone(), cx); // workspace // .right_dock() // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); // let left_dock = workspace.left_dock(); // assert_eq!( // left_dock.read(cx).visible_panel().unwrap().id(), // panel_1.id() // ); // assert_eq!( // left_dock.read(cx).active_panel_size(cx).unwrap(), // panel_1.size(cx) // ); // left_dock.update(cx, |left_dock, cx| { // left_dock.resize_active_panel(Some(1337.), cx) // }); // assert_eq!( // workspace // .right_dock() // .read(cx) // .visible_panel() // .unwrap() // .id(), // panel_2.id() // ); // (panel_1, panel_2) // }); // // Move panel_1 to the right // panel_1.update(cx, |panel_1, cx| { // panel_1.set_position(DockPosition::Right, cx) // }); // workspace.update(cx, |workspace, cx| { // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. // // Since it was the only panel on the left, the left dock should now be closed. // assert!(!workspace.left_dock().read(cx).is_open()); // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); // let right_dock = workspace.right_dock(); // assert_eq!( // right_dock.read(cx).visible_panel().unwrap().id(), // panel_1.id() // ); // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // // Now we move panel_2 to the left // panel_2.set_position(DockPosition::Left, cx); // }); // workspace.update(cx, |workspace, cx| { // // Since panel_2 was not visible on the right, we don't open the left dock. // assert!(!workspace.left_dock().read(cx).is_open()); // // And the right dock is unaffected in it's displaying of panel_1 // assert!(workspace.right_dock().read(cx).is_open()); // assert_eq!( // workspace // .right_dock() // .read(cx) // .visible_panel() // .unwrap() // .id(), // panel_1.id() // ); // }); // // Move panel_1 back to the left // panel_1.update(cx, |panel_1, cx| { // panel_1.set_position(DockPosition::Left, cx) // }); // workspace.update(cx, |workspace, cx| { // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. // let left_dock = workspace.left_dock(); // assert!(left_dock.read(cx).is_open()); // assert_eq!( // left_dock.read(cx).visible_panel().unwrap().id(), // panel_1.id() // ); // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); // // And right the dock should be closed as it no longer has any panels. // assert!(!workspace.right_dock().read(cx).is_open()); // // Now we move panel_1 to the bottom // panel_1.set_position(DockPosition::Bottom, cx); // }); // workspace.update(cx, |workspace, cx| { // // Since panel_1 was visible on the left, we close the left dock. // assert!(!workspace.left_dock().read(cx).is_open()); // // The bottom dock is sized based on the panel's default size, // // since the panel orientation changed from vertical to horizontal. // let bottom_dock = workspace.bottom_dock(); // assert_eq!( // bottom_dock.read(cx).active_panel_size(cx).unwrap(), // panel_1.size(cx), // ); // // Close bottom dock and move panel_1 back to the left. // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); // panel_1.set_position(DockPosition::Left, cx); // }); // // Emit activated event on panel 1 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); // // Now the left dock is open and panel_1 is active and focused. // workspace.read_with(cx, |workspace, cx| { // let left_dock = workspace.left_dock(); // assert!(left_dock.read(cx).is_open()); // assert_eq!( // left_dock.read(cx).visible_panel().unwrap().id(), // panel_1.id() // ); // assert!(panel_1.is_focused(cx)); // }); // // Emit closed event on panel 2, which is not active // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); // // Wo don't close the left dock, because panel_2 wasn't the active panel // workspace.read_with(cx, |workspace, cx| { // let left_dock = workspace.left_dock(); // assert!(left_dock.read(cx).is_open()); // assert_eq!( // left_dock.read(cx).visible_panel().unwrap().id(), // panel_1.id() // ); // }); // // Emitting a ZoomIn event shows the panel as zoomed. // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); // }); // // Move panel to another dock while it is zoomed // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); // }); // // If focus is transferred to another view that's not a panel or another pane, we still show // // the panel as zoomed. // let focus_receiver = window.build_view(cx, |_| EmptyView); // focus_receiver.update(cx, |_, cx| cx.focus_self()); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); // }); // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. // workspace.update(cx, |_, cx| cx.focus_self()); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, None); // assert_eq!(workspace.zoomed_position, None); // }); // // If focus is transferred again to another view that's not a panel or a pane, we won't // // show the panel as zoomed because it wasn't zoomed before. // focus_receiver.update(cx, |_, cx| cx.focus_self()); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, None); // assert_eq!(workspace.zoomed_position, None); // }); // // When focus is transferred back to the panel, it is zoomed again. // panel_1.update(cx, |_, cx| cx.focus_self()); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); // }); // // Emitting a ZoomOut event unzooms the panel. // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, None); // assert_eq!(workspace.zoomed_position, None); // }); // // Emit closed event on panel 1, which is active // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); // // Now the left dock is closed, because panel_1 was the active panel // workspace.read_with(cx, |workspace, cx| { // let right_dock = workspace.right_dock(); // assert!(!right_dock.read(cx).is_open()); // }); // } // pub fn init_test(cx: &mut TestAppContext) { // cx.foreground().forbid_parking(); // cx.update(|cx| { // cx.set_global(SettingsStore::test(cx)); // theme::init((), cx); // language::init(cx); // crate::init_settings(cx); // Project::init_settings(cx); // }); // } // }